From e116f46982ce747b767a0a5c912504a135ab787e Mon Sep 17 00:00:00 2001 From: "Andrew S. Brown" Date: Tue, 10 Jul 2018 18:04:28 -0700 Subject: [PATCH 001/499] Remove old circle.yml file --- circle.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 7cfe8890..00000000 --- a/circle.yml +++ /dev/null @@ -1,22 +0,0 @@ -dependencies: - pre: - - curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg - - sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg - - sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list' - - sudo apt-get install apt-transport-https - - sudo apt-get update - - sudo apt-get install dotnet-sdk-2.0.0 - - sudo apt-get install xsltproc - override: - - dotnet restore - - dotnet build src/LaunchDarkly.Common -f netstandard1.6 - - dotnet build src/LaunchDarkly.Common -f netstandard2.0 - - dotnet build src/LaunchDarkly.Xamarin -f netstandard1.6 - - dotnet build src/LaunchDarkly.Xamarin -f netstandard2.0 -test: - override: - - dotnet add tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj package dotnet-xunit - - dotnet restore - - mkdir -p $CIRCLE_TEST_REPORTS/junit - - cd tests/LaunchDarkly.Xamarin.Tests; dotnet xunit -xml xunit.xml - - xsltproc -o $CIRCLE_TEST_REPORTS/junit/junit.xml tests/LaunchDarkly.Xamarin.Tests/xunit-to-junit.xslt tests/LaunchDarkly.Xamarin.Tests/xunit.xml From a03fa0d1b37b01ba7faca546f348f393393fe8a0 Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Thu, 12 Jul 2018 11:24:57 -0700 Subject: [PATCH 002/499] Send back flagVersion in events when it is present (#5) Also allow users to have empty string keys --- src/LaunchDarkly.Xamarin/FeatureFlag.cs | 48 ++-- src/LaunchDarkly.Xamarin/LdClient.cs | 12 +- .../FeatureFlagTests.cs | 29 +++ .../FlagCacheManagerTests.cs | 212 +++++++++--------- .../LdClientTests.cs | 86 +++---- 5 files changed, 210 insertions(+), 177 deletions(-) create mode 100644 tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlag.cs b/src/LaunchDarkly.Xamarin/FeatureFlag.cs index 7d847c02..bd37ecbf 100644 --- a/src/LaunchDarkly.Xamarin/FeatureFlag.cs +++ b/src/LaunchDarkly.Xamarin/FeatureFlag.cs @@ -1,25 +1,27 @@ using System; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using LaunchDarkly.Common; - -namespace LaunchDarkly.Xamarin -{ - public class FeatureFlag : IEquatable - { - public JToken value; - public int version; - public bool trackEvents; - public int? variation; - public long? debugEventsUntilDate; - - public bool Equals(FeatureFlag otherFlag) - { + +namespace LaunchDarkly.Xamarin +{ + public class FeatureFlag : IEquatable + { + public JToken value; + public int version; + public int? flagVersion; + public bool trackEvents; + public int? variation; + public long? debugEventsUntilDate; + + public bool Equals(FeatureFlag otherFlag) + { return JToken.DeepEquals(value, otherFlag.value) && version == otherFlag.version + && flagVersion == otherFlag.flagVersion && trackEvents == otherFlag.trackEvents && variation == otherFlag.variation - && debugEventsUntilDate == otherFlag.debugEventsUntilDate; - } + && debugEventsUntilDate == otherFlag.debugEventsUntilDate; + } } internal class FeatureFlagEvent : IFlagEventProperties @@ -39,10 +41,10 @@ public FeatureFlagEvent(string key, FeatureFlag featureFlag) _featureFlag = featureFlag; _key = key; } - - public string Key => _key; - public int Version => _featureFlag.version; - public bool TrackEvents => _featureFlag.trackEvents; - public long? DebugEventsUntilDate => _featureFlag.debugEventsUntilDate; - } -} + + public string Key => _key; + public int Version => _featureFlag.flagVersion ?? _featureFlag.version; + public bool TrackEvents => _featureFlag.trackEvents; + public long? DebugEventsUntilDate => _featureFlag.debugEventsUntilDate; + } +} diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 1c8261f8..d7824bdc 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -67,8 +67,8 @@ public sealed class LdClient : ILdMobileClient deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); - // If you pass in a null user or user with an empty key, one will be assigned to them. - if (user == null || String.IsNullOrEmpty(user.Key)) + // If you pass in a null user or user with a null key, one will be assigned to them. + if (user == null || user.Key == null) { User = UserWithUniqueKey(user); } @@ -427,7 +427,7 @@ public async Task IdentifyAsync(User user) } User userWithKey = null; - if (String.IsNullOrEmpty(user.Key)) + if (user.Key == null) { userWithKey = UserWithUniqueKey(user); } @@ -503,11 +503,11 @@ void IDisposable.Dispose() void Dispose(bool disposing) { - if (disposing) + if (disposing) { - Log.InfoFormat("The mobile client is being disposed"); + Log.InfoFormat("The mobile client is being disposed"); updateProcessor.Dispose(); - eventProcessor.Dispose(); + eventProcessor.Dispose(); } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs new file mode 100644 index 00000000..bd990c54 --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class FeatureFlagEventTests + { + [Fact] + public void ReturnsFlagVersionAsVersion() + { + var flag = new FeatureFlag(); + flag.flagVersion = 123; + flag.version = 456; + var flagEvent = new FeatureFlagEvent("my-flag", flag); + Assert.Equal(123, flagEvent.Version); + } + + [Fact] + public void FallsBackToVersionAsVersion() + { + var flag = new FeatureFlag(); + flag.version = 456; + var flagEvent = new FeatureFlagEvent("my-flag", flag); + Assert.Equal(456, flagEvent.Version); + } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs index c326ceed..3b0df506 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs @@ -1,106 +1,106 @@ -using System; -using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class FlagCacheManagerTests - { - IUserFlagCache deviceCache = new UserFlagInMemoryCache(); - IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); - FeatureFlagListenerManager listenerManager = new FeatureFlagListenerManager(); - - User user = User.WithKey("someKey"); - - IFlagCacheManager ManagerWithCachedFlags() - { - var flagCacheManager = new FlagCacheManager(deviceCache, inMemoryCache, listenerManager, user); - flagCacheManager.CacheFlagsFromService(JSONReader.StubbedFlagsDictionary(), user); - return flagCacheManager; - } - - [Fact] - public void CacheFlagsShouldStoreFlagsInDeviceCache() - { - var flagCacheManager = ManagerWithCachedFlags(); - var cachedDeviceFlags = deviceCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); - } - - [Fact] - public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() - { - var flagCacheManager = ManagerWithCachedFlags(); - var cachedDeviceFlags = inMemoryCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); - } - - [Fact] - public void ShouldBeAbleToRemoveFlagForUser() - { - var manager = ManagerWithCachedFlags(); - manager.RemoveFlagForUser("int-key", user); - Assert.Null(manager.FlagForUser("int-key", user)); - } - - [Fact] - public void ShouldBeAbleToUpdateFlagForUser() - { - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(5); - updatedFeatureFlag.version = 12; - flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); - var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); - Assert.Equal(5, updatedFlagFromCache.value.ToObject()); - Assert.Equal(12, updatedFeatureFlag.version); - } - - [Fact] - public void UpdateFlagUpdatesTheFlagOnListenerManager() - { - var listener = new TestListener(); - listenerManager.RegisterListener(listener, "int-flag"); - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(7); - flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); - - Assert.Equal(7, listener.FeatureFlags["int-flag"].ToObject()); - } - - [Fact] - public void RemoveFlagTellsListenerManagerToTellListenersFlagWasDeleted() - { - var listener = new TestListener(); - listenerManager.RegisterListener(listener, "int-flag"); - listener.FeatureFlags["int-flag"] = JToken.FromObject(1); - Assert.True(listener.FeatureFlags.ContainsKey("int-flag")); - - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(7); - flagCacheManager.RemoveFlagForUser("int-flag", user); - - Assert.False(listener.FeatureFlags.ContainsKey("int-flag")); - } - - [Fact] - public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() - { - var flagCacheManager = ManagerWithCachedFlags(); - var listener = new TestListener(); - listenerManager.RegisterListener(listener, "int-flag"); - - var updatedFlags = JSONReader.UpdatedStubbedFlagsDictionary(); - flagCacheManager.CacheFlagsFromService(updatedFlags, user); - - Assert.Equal(5, listener.FeatureFlags["int-flag"].ToObject()); - } - } -} +using System; +using LaunchDarkly.Client; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class FlagCacheManagerTests + { + IUserFlagCache deviceCache = new UserFlagInMemoryCache(); + IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); + FeatureFlagListenerManager listenerManager = new FeatureFlagListenerManager(); + + User user = User.WithKey("someKey"); + + IFlagCacheManager ManagerWithCachedFlags() + { + var flagCacheManager = new FlagCacheManager(deviceCache, inMemoryCache, listenerManager, user); + flagCacheManager.CacheFlagsFromService(JSONReader.StubbedFlagsDictionary(), user); + return flagCacheManager; + } + + [Fact] + public void CacheFlagsShouldStoreFlagsInDeviceCache() + { + var flagCacheManager = ManagerWithCachedFlags(); + var cachedDeviceFlags = deviceCache.RetrieveFlags(user); + Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); + Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); + Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); + } + + [Fact] + public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() + { + var flagCacheManager = ManagerWithCachedFlags(); + var cachedDeviceFlags = inMemoryCache.RetrieveFlags(user); + Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); + Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); + Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); + } + + [Fact] + public void ShouldBeAbleToRemoveFlagForUser() + { + var manager = ManagerWithCachedFlags(); + manager.RemoveFlagForUser("int-key", user); + Assert.Null(manager.FlagForUser("int-key", user)); + } + + [Fact] + public void ShouldBeAbleToUpdateFlagForUser() + { + var flagCacheManager = ManagerWithCachedFlags(); + var updatedFeatureFlag = new FeatureFlag(); + updatedFeatureFlag.value = JToken.FromObject(5); + updatedFeatureFlag.version = 12; + flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); + var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); + Assert.Equal(5, updatedFlagFromCache.value.ToObject()); + Assert.Equal(12, updatedFeatureFlag.version); + } + + [Fact] + public void UpdateFlagUpdatesTheFlagOnListenerManager() + { + var listener = new TestListener(); + listenerManager.RegisterListener(listener, "int-flag"); + var flagCacheManager = ManagerWithCachedFlags(); + var updatedFeatureFlag = new FeatureFlag(); + updatedFeatureFlag.value = JToken.FromObject(7); + flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); + + Assert.Equal(7, listener.FeatureFlags["int-flag"].ToObject()); + } + + [Fact] + public void RemoveFlagTellsListenerManagerToTellListenersFlagWasDeleted() + { + var listener = new TestListener(); + listenerManager.RegisterListener(listener, "int-flag"); + listener.FeatureFlags["int-flag"] = JToken.FromObject(1); + Assert.True(listener.FeatureFlags.ContainsKey("int-flag")); + + var flagCacheManager = ManagerWithCachedFlags(); + var updatedFeatureFlag = new FeatureFlag(); + updatedFeatureFlag.value = JToken.FromObject(7); + flagCacheManager.RemoveFlagForUser("int-flag", user); + + Assert.False(listener.FeatureFlags.ContainsKey("int-flag")); + } + + [Fact] + public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() + { + var flagCacheManager = ManagerWithCachedFlags(); + var listener = new TestListener(); + listenerManager.RegisterListener(listener, "int-flag"); + + var updatedFlags = JSONReader.UpdatedStubbedFlagsDictionary(); + flagCacheManager.CacheFlagsFromService(updatedFlags, user); + + Assert.Equal(5, listener.FeatureFlags["int-flag"].ToObject()); + } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 37c493cb..be383811 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -1,61 +1,61 @@ using System; -using System.Collections.Generic; -using LaunchDarkly.Client; -using Newtonsoft.Json; +using System.Collections.Generic; +using LaunchDarkly.Client; +using Newtonsoft.Json; using Xunit; namespace LaunchDarkly.Xamarin.Tests { public class DefaultLdClientTests - { - static readonly string appKey = "some app key"; - static readonly string flagKey = "some flag key"; - - LdClient Client() + { + static readonly string appKey = "some app key"; + static readonly string flagKey = "some flag key"; + + LdClient Client() { if (LdClient.Instance == null) { User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var configuration = StubbedConfigAndUserBuilder.Config(user, appKey); + var configuration = StubbedConfigAndUserBuilder.Config(user, appKey); return LdClient.Init(configuration, user); } - return LdClient.Instance; - } - + return LdClient.Instance; + } + [Fact] - public void CanCreateClientWithConfigAndUser() - { + public void CanCreateClientWithConfigAndUser() + { Assert.NotNull(Client()); } [Fact] - public void DefaultBoolVariationFlag() - { + public void DefaultBoolVariationFlag() + { Assert.False(Client().BoolVariation(flagKey)); } [Fact] public void DefaultStringVariationFlag() - { - Assert.Equal(String.Empty, Client().StringVariation(flagKey, String.Empty)); + { + Assert.Equal(String.Empty, Client().StringVariation(flagKey, String.Empty)); } [Fact] public void DefaultFloatVariationFlag() - { + { Assert.Equal(0, Client().FloatVariation(flagKey)); } [Fact] public void DefaultIntVariationFlag() - { + { Assert.Equal(0, Client().IntVariation(flagKey)); } [Fact] public void DefaultJSONVariationFlag() - { + { Assert.Null(Client().JsonVariation(flagKey, null)); } @@ -63,7 +63,7 @@ public void DefaultJSONVariationFlag() public void DefaultAllFlagsShouldBeEmpty() { var client = Client(); - client.Identify(User.WithKey("some other user key with no flags")); + client.Identify(User.WithKey("some other user key with no flags")); Assert.Equal(0, client.AllFlags().Count); client.Identify(User.WithKey("user1Key")); } @@ -74,23 +74,23 @@ public void DefaultValueReturnedIfTypeBackIsDifferent() var client = Client(); Assert.Equal(0, client.IntVariation("string-flag", 0)); Assert.False(client.BoolVariation("float-flag", false)); - } - + } + [Fact] - public void IdentifyUpdatesTheUser() + public void IdentifyUpdatesTheUser() { - var client = Client(); - var updatedUser = User.WithKey("some new key"); - client.Identify(updatedUser); - Assert.Equal(client.User, updatedUser); + var client = Client(); + var updatedUser = User.WithKey("some new key"); + client.Identify(updatedUser); + Assert.Equal(client.User, updatedUser); } [Fact] - public void SharedClientIsTheOnlyClientAvailable() + public void SharedClientIsTheOnlyClientAvailable() { - var client = Client(); - var config = Configuration.Default(appKey); - Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); + var client = Client(); + var config = Configuration.Default(appKey); + Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); } [Fact] @@ -112,27 +112,27 @@ public void ConnectionManagerShouldKnowIfOnlineOrNot() connMgr.Connect(true); Assert.False(client.IsOffline()); connMgr.Connect(false); - Assert.False(client.Online); + Assert.False(client.Online); } [Fact] public void ConnectionChangeShouldStopUpdateProcessor() - { + { var client = Client(); var connMgr = client.Config.ConnectionManager as MockConnectionManager; connMgr.ConnectionChanged += (bool obj) => client.Online = obj; connMgr.Connect(false); var mockUpdateProc = client.Config.MobileUpdateProcessor as MockPollingProcessor; - Assert.False(mockUpdateProc.IsRunning); + Assert.False(mockUpdateProc.IsRunning); } [Fact] - public void UserWithMissingKeyWillHaveUniqueKeySet() + public void UserWithNullKeyWillHaveUniqueKeySet() { LdClient.Instance = null; - var userWithoutKey = User.WithKey(String.Empty); - var config = StubbedConfigAndUserBuilder.Config(userWithoutKey, "someOtherAppKey"); - var client = LdClient.Init(config, userWithoutKey); + var userWithNullKey = User.WithKey(null); + var config = StubbedConfigAndUserBuilder.Config(userWithNullKey, "someOtherAppKey"); + var client = LdClient.Init(config, userWithNullKey); Assert.Equal(MockDeviceInfo.key, client.User.Key); LdClient.Instance = null; } @@ -140,8 +140,10 @@ public void UserWithMissingKeyWillHaveUniqueKeySet() [Fact] public void IdentifyWithUserMissingKeyUsesUniqueGeneratedKey() { + var client = Client(); LdClient.Instance.Identify(User.WithKey("a new user's key")); - LdClient.Instance.Identify(User.WithKey(String.Empty)); + var userWithNullKey = User.WithKey(null); + LdClient.Instance.Identify(userWithNullKey); Assert.Equal(MockDeviceInfo.key, LdClient.Instance.User.Key); LdClient.Instance = null; } @@ -200,6 +202,6 @@ public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerMan client.UnregisterFeatureFlagListener("user2-flag", listener); listenerMgr.FlagWasUpdated("user2-flag", 12); Assert.NotEqual(12, listener.FeatureFlags["user2-flag"]); - } + } } } From 56165a0dac1298df45e55c4b728557e05b366b96 Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Thu, 12 Jul 2018 21:00:38 -0700 Subject: [PATCH 003/499] Send default feature flag event when flag.value is null (#6) Also return default value instead of null value. --- src/LaunchDarkly.Xamarin/LdClient.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index d7824bdc..189202b8 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -334,13 +334,21 @@ JToken Variation(string featureKey, JToken defaultValue) if (flag != null) { featureFlagEvent = new FeatureFlagEvent(featureKey, flag); - featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, - User, - flag.variation, - flag.value, - defaultValue); + var value = flag.value; + if (value == null) { + featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, + User, + defaultValue); + value = defaultValue; + } else { + featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, + User, + flag.variation, + flag.value, + defaultValue); + } eventProcessor.SendEvent(featureRequestEvent); - return flag.value; + return value; } Log.InfoFormat("Unknown feature flag {0}; returning default value", From d0ca44e9ba7d3ea747bf988fef82513c64507abc Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Fri, 13 Jul 2018 07:15:51 -0700 Subject: [PATCH 004/499] Create License.txt --- License.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 License.txt diff --git a/License.txt b/License.txt new file mode 100644 index 00000000..f8503553 --- /dev/null +++ b/License.txt @@ -0,0 +1,13 @@ +Copyright 2018 Catamorphic, Co. + +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. From 2644d7f5e063e91ee52471af200a8a0cb66fbac9 Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Fri, 13 Jul 2018 15:10:02 -0700 Subject: [PATCH 005/499] Add test that we return default value for off variation (#7) --- .../FeatureFlagsFromService.json | 5 +++++ tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs | 7 +++++++ .../MobilePollingProcessorTests.cs | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json index cb9f23d4..37a9b008 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json +++ b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json @@ -29,5 +29,10 @@ "version": 456, "variation": 1, "trackEvents": false + }, + "off-flag": { + "value": null, + "version": 456, + "trackEvents": false } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index be383811..25f9f7cf 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -76,6 +76,13 @@ public void DefaultValueReturnedIfTypeBackIsDifferent() Assert.False(client.BoolVariation("float-flag", false)); } + [Fact] + public void DefaultValueReturnedIfFlagIsOff() + { + var client = Client(); + Assert.Equal(123, client.IntVariation("off-flag", 123)); + } + [Fact] public void IdentifyUpdatesTheUser() { diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs index a946fd5f..3a9552ce 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs @@ -32,7 +32,7 @@ public void StartWaitsUntilFlagCacheFilled() var initTask = processor.Start(); var unused = initTask.Wait(TimeSpan.FromSeconds(1)); var flags = mockFlagCacheManager.FlagsForUser(user); - Assert.Equal(5, flags.Count); + Assert.Equal(6, flags.Count); } } } From 7fdf036084335bb3b7a7f01fe866e66d6ed619f9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Jul 2018 15:39:59 -0700 Subject: [PATCH 006/499] misc cleanup of project files --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 7 ------- .../LaunchDarkly.Xamarin.Tests.csproj | 5 ----- 2 files changed, 12 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 2adb9c96..2f398394 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -4,7 +4,6 @@ netstandard1.6;netstandard2.0;net45 true ..\..\LaunchDarkly.Xamarin.snk - netstandard2.0 @@ -24,12 +23,6 @@ - - - - - - diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 33b64dce..a4a01263 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -27,11 +27,6 @@ - - - - - Always From 8924b5cb7ce7347d03666eef2bb816d249c88980 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Jul 2018 15:40:22 -0700 Subject: [PATCH 007/499] rm usage that won't work in older target frameworks --- src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs b/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs index 40bdc3a2..1cd8c9b9 100644 --- a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs @@ -129,7 +129,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT break; } - return Task.CompletedTask; + return Task.FromResult(true); } void PatchFeatureFlag(string flagKey, FeatureFlag featureFlag) From b3287b691aa74423c65abc3c22c312b609be60fa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Jul 2018 15:54:26 -0700 Subject: [PATCH 008/499] version 1.0.0-beta8 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 2f398394..2d110ff1 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,6 +1,7 @@ + 1.0.0-beta8 netstandard1.6;netstandard2.0;net45 true ..\..\LaunchDarkly.Xamarin.snk From dcc7f9fe007546862578ccf2ddb6696b337231d9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Jul 2018 16:01:23 -0700 Subject: [PATCH 009/499] version 1.0.0-beta9 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 2d110ff1..702070f3 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta8 + 1.0.0-beta9 netstandard1.6;netstandard2.0;net45 true ..\..\LaunchDarkly.Xamarin.snk From c2f674a49f67482d139de5ce0b582559d49b43c5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 17 Jul 2018 16:53:38 -0700 Subject: [PATCH 010/499] clean up unnecessary static references and make tests stable --- src/LaunchDarkly.Xamarin/LdClient.cs | 25 ++++++------ .../LdClientTests.cs | 39 ++++++++++--------- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 27 +++++++++++++ 3 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 189202b8..c404b742 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -148,7 +148,7 @@ public static LdClient Init(Configuration config, User user) if (Instance.Online) { - StartUpdateProcessor(); + Instance.StartUpdateProcessor(); } return Instance; @@ -173,7 +173,7 @@ public static Task InitAsync(Configuration config, User user) if (Instance.Online) { - Task t = StartUpdateProcessorAsync(); + Task t = Instance.StartUpdateProcessorAsync(); return t.ContinueWith((result) => Instance); } else @@ -192,16 +192,15 @@ static void CreateInstance(Configuration configuration, User user) Instance.Version); } - static void StartUpdateProcessor() + void StartUpdateProcessor() { - var initTask = Instance.updateProcessor.Start(); - var configuration = Instance.Config as Configuration; - var unused = initTask.Wait(configuration.StartWaitTime); + var initTask = updateProcessor.Start(); + var unused = initTask.Wait(Config.StartWaitTime); } - static Task StartUpdateProcessorAsync() + Task StartUpdateProcessorAsync() { - return Instance.updateProcessor.Start(); + return updateProcessor.Start(); } void SetupConnectionManager() @@ -300,7 +299,7 @@ JToken VariationWithType(string featureKey, JToken defaultValue, JTokenType? jto { Log.ErrorFormat("Expected type: {0} but got {1} when evaluating FeatureFlag: {2}. Returning default", jtokenType, - returnedFlagValue.GetType(), + returnedFlagValue.Type, featureKey); return defaultValue; @@ -403,8 +402,12 @@ public void Track(string eventName) /// public bool Initialized() { - bool isInited = Instance != null; - return isInited && Online; + //bool isInited = Instance != null; + //return isInited && Online; + // TODO: This method needs to be fixed to actually check whether the update processor has initialized. + // The previous logic (above) was meaningless because this method is not static, so by definition you + // do have a client instance if we've gotten here. But that doesn't mean it is initialized. + return Online; } /// diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 25f9f7cf..c1b38d9d 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -13,14 +13,9 @@ public class DefaultLdClientTests LdClient Client() { - if (LdClient.Instance == null) - { - User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var configuration = StubbedConfigAndUserBuilder.Config(user, appKey); - return LdClient.Init(configuration, user); - } - - return LdClient.Instance; + User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); + var configuration = StubbedConfigAndUserBuilder.Config(user, appKey); + return TestUtil.CreateClient(configuration, user); } [Fact] @@ -95,9 +90,20 @@ public void IdentifyUpdatesTheUser() [Fact] public void SharedClientIsTheOnlyClientAvailable() { - var client = Client(); - var config = Configuration.Default(appKey); - Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); + lock (TestUtil.ClientInstanceLock) + { + User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); + var config = StubbedConfigAndUserBuilder.Config(user, appKey); + var client = LdClient.Init(config, user); + try + { + Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); + } + finally + { + LdClient.Instance = null; + } + } } [Fact] @@ -136,23 +142,20 @@ public void ConnectionChangeShouldStopUpdateProcessor() [Fact] public void UserWithNullKeyWillHaveUniqueKeySet() { - LdClient.Instance = null; var userWithNullKey = User.WithKey(null); var config = StubbedConfigAndUserBuilder.Config(userWithNullKey, "someOtherAppKey"); - var client = LdClient.Init(config, userWithNullKey); + var client = TestUtil.CreateClient(config, userWithNullKey); Assert.Equal(MockDeviceInfo.key, client.User.Key); - LdClient.Instance = null; } [Fact] public void IdentifyWithUserMissingKeyUsesUniqueGeneratedKey() { var client = Client(); - LdClient.Instance.Identify(User.WithKey("a new user's key")); + client.Identify(User.WithKey("a new user's key")); var userWithNullKey = User.WithKey(null); - LdClient.Instance.Identify(userWithNullKey); - Assert.Equal(MockDeviceInfo.key, LdClient.Instance.User.Key); - LdClient.Instance = null; + client.Identify(userWithNullKey); + Assert.Equal(MockDeviceInfo.key, client.User.Key); } [Fact] diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs new file mode 100644 index 00000000..8624f1b0 --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin.Tests +{ + class TestUtil + { + // Any tests that are going to access the static LdClient.Instance must hold this lock, + // to avoid interfering with tests that use CreateClient. + public static readonly object ClientInstanceLock = new object(); + + // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can + // instantiate their own independent clients. Application code cannot do this because + // the LdClient.Instance setter has internal scope. + public static LdClient CreateClient(Configuration config, User user) + { + lock (ClientInstanceLock) + { + LdClient client = LdClient.Init(config, user); + LdClient.Instance = null; + return client; + } + } + } +} From 27c0a396b28454a55910e1cd161b7ca74286dcfe Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 17 Jul 2018 17:37:30 -0700 Subject: [PATCH 011/499] break out and simplify basic flag evaluation tests --- .../LdClientEvaluationTests.cs | 137 ++++++++++++++++++ .../LdClientTests.cs | 75 +--------- .../StubbedConfigAndUserBuilder.cs | 22 --- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 39 ++++- 4 files changed, 177 insertions(+), 96 deletions(-) create mode 100644 tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs new file mode 100644 index 00000000..78d9fe9a --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text; +using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class LdClientEvaluationTests + { + static readonly string appKey = "some app key"; + static readonly string nonexistentFlagKey = "some flag key"; + static readonly User user = User.WithKey("userkey"); + + private static LdClient ClientWithFlagsJson(string flagsJson) + { + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); + return TestUtil.CreateClient(config, user); + } + + [Fact] + public void BoolVariationReturnsValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true)); + var client = ClientWithFlagsJson(flagsJson); + + Assert.True(client.BoolVariation("flag-key", false)); + } + + [Fact] + public void BoolVariationReturnsDefaultForUnknownFlag() + { + var client = ClientWithFlagsJson("{}"); + Assert.False(client.BoolVariation(nonexistentFlagKey)); + } + + [Fact] + public void IntVariationReturnsValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3)); + var client = ClientWithFlagsJson(flagsJson); + + Assert.Equal(3, client.IntVariation("flag-key", 0)); + } + + [Fact] + public void IntVariationReturnsDefaultForUnknownFlag() + { + var client = ClientWithFlagsJson("{}"); + Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); + } + + [Fact] + public void FloatVariationReturnsValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f)); + var client = ClientWithFlagsJson(flagsJson); + + Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); + } + + [Fact] + public void FloatVariationReturnsDefaultForUnknownFlag() + { + var client = ClientWithFlagsJson("{}"); + Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); + } + + [Fact] + public void StringVariationReturnsValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + var client = ClientWithFlagsJson(flagsJson); + + Assert.Equal("string value", client.StringVariation("flag-key", "")); + } + + [Fact] + public void StringVariationReturnsDefaultForUnknownFlag() + { + var client = ClientWithFlagsJson("{}"); + Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); + } + + [Fact] + public void JsonVariationReturnsValue() + { + var jsonValue = new JObject { { "thing", new JValue("stuff") } }; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); + var client = ClientWithFlagsJson(flagsJson); + + var defaultValue = new JValue(3); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", defaultValue)); + } + + [Fact] + public void JsonVariationReturnsDefaultForUnknownFlag() + { + var client = ClientWithFlagsJson("{}"); + Assert.Null(client.JsonVariation(nonexistentFlagKey, null)); + } + + [Fact] + public void AllFlagsReturnsAllFlagValues() + { + var flagsJson = @"{""flag1"":{""value"":""a""},""flag2"":{""value"":""b""}}"; + var client = ClientWithFlagsJson(flagsJson); + + var result = client.AllFlags(); + Assert.Equal(2, result.Count); + Assert.Equal(new JValue("a"), result["flag1"]); + Assert.Equal(new JValue("b"), result["flag2"]); + } + + [Fact] + public void DefaultValueReturnedIfValueTypeIsDifferent() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); + var client = TestUtil.CreateClient(config, user); + + Assert.Equal(3, client.IntVariation("flag-key", 3)); + } + + [Fact] + public void DefaultValueReturnedIfFlagValueIsNull() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", null); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); + var client = TestUtil.CreateClient(config, user); + + Assert.Equal(3, client.IntVariation("flag-key", 3)); + } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index c1b38d9d..41b2794d 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using LaunchDarkly.Client; -using Newtonsoft.Json; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -9,12 +7,11 @@ namespace LaunchDarkly.Xamarin.Tests public class DefaultLdClientTests { static readonly string appKey = "some app key"; - static readonly string flagKey = "some flag key"; LdClient Client() { User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var configuration = StubbedConfigAndUserBuilder.Config(user, appKey); + var configuration = TestUtil.ConfigWithFlagsJson(user, appKey, JSONReader.FeatureFlagJSON()); return TestUtil.CreateClient(configuration, user); } @@ -24,60 +21,6 @@ public void CanCreateClientWithConfigAndUser() Assert.NotNull(Client()); } - [Fact] - public void DefaultBoolVariationFlag() - { - Assert.False(Client().BoolVariation(flagKey)); - } - - [Fact] - public void DefaultStringVariationFlag() - { - Assert.Equal(String.Empty, Client().StringVariation(flagKey, String.Empty)); - } - - [Fact] - public void DefaultFloatVariationFlag() - { - Assert.Equal(0, Client().FloatVariation(flagKey)); - } - - [Fact] - public void DefaultIntVariationFlag() - { - Assert.Equal(0, Client().IntVariation(flagKey)); - } - - [Fact] - public void DefaultJSONVariationFlag() - { - Assert.Null(Client().JsonVariation(flagKey, null)); - } - - [Fact] - public void DefaultAllFlagsShouldBeEmpty() - { - var client = Client(); - client.Identify(User.WithKey("some other user key with no flags")); - Assert.Equal(0, client.AllFlags().Count); - client.Identify(User.WithKey("user1Key")); - } - - [Fact] - public void DefaultValueReturnedIfTypeBackIsDifferent() - { - var client = Client(); - Assert.Equal(0, client.IntVariation("string-flag", 0)); - Assert.False(client.BoolVariation("float-flag", false)); - } - - [Fact] - public void DefaultValueReturnedIfFlagIsOff() - { - var client = Client(); - Assert.Equal(123, client.IntVariation("off-flag", 123)); - } - [Fact] public void IdentifyUpdatesTheUser() { @@ -93,7 +36,7 @@ public void SharedClientIsTheOnlyClientAvailable() lock (TestUtil.ClientInstanceLock) { User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var config = StubbedConfigAndUserBuilder.Config(user, appKey); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, "{}"); var client = LdClient.Init(config, user); try { @@ -105,17 +48,7 @@ public void SharedClientIsTheOnlyClientAvailable() } } } - - [Fact] - public void CanFetchFlagFromInMemoryCache() - { - var client = Client(); - bool boolFlag = client.BoolVariation("boolean-flag", true); - Assert.True(boolFlag); - int intFlag = client.IntVariation("int-flag", 0); - Assert.Equal(15, intFlag); - } - + [Fact] public void ConnectionManagerShouldKnowIfOnlineOrNot() { @@ -143,7 +76,7 @@ public void ConnectionChangeShouldStopUpdateProcessor() public void UserWithNullKeyWillHaveUniqueKeySet() { var userWithNullKey = User.WithKey(null); - var config = StubbedConfigAndUserBuilder.Config(userWithNullKey, "someOtherAppKey"); + var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, "someOtherAppKey", "{}"); var client = TestUtil.CreateClient(config, userWithNullKey); Assert.Equal(MockDeviceInfo.key, client.User.Key); } diff --git a/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs b/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs index 3e5a2beb..5622e9d3 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs @@ -7,28 +7,6 @@ namespace LaunchDarkly.Xamarin.Tests { public static class StubbedConfigAndUserBuilder { - public static Configuration Config(User user, string appKey) - { - var stubbedFlagCache = JSONReader.StubbedFlagCache(user); - - // overriding the default implementation of dependencies for testing purposes - var mockOnlineConnectionManager = new MockConnectionManager(true); - var mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); - var mockPollingProcessor = new MockPollingProcessor(); - var mockPersister = new MockPersister(); - var mockDeviceInfo = new MockDeviceInfo(); - var featureFlagListener = new FeatureFlagListenerManager(); - - Configuration configuration = Configuration.Default(appKey) - .WithFlagCacheManager(mockFlagCacheManager) - .WithConnectionManager(mockOnlineConnectionManager) - .WithUpdateProcessor(mockPollingProcessor) - .WithPersister(mockPersister) - .WithDeviceInfo(mockDeviceInfo) - .WithFeatureFlagListenerManager(featureFlagListener); - return configuration; - } - public static User UserWithAllPropertiesFilledIn(string key) { var user = User.WithKey(key); diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 8624f1b0..3db8735e 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -1,7 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin.Tests { @@ -23,5 +23,38 @@ public static LdClient CreateClient(Configuration config, User user) return client; } } + + public static string JsonFlagsWithSingleFlag(string flagKey, JToken value) + { + JObject fo = new JObject { { "value", value } }; + JObject o = new JObject { { flagKey, fo } }; + return JsonConvert.SerializeObject(o); + } + + public static Configuration ConfigWithFlagsJson(User user, string appKey, string flagsJson) + { + var flags = JsonConvert.DeserializeObject>(flagsJson); + IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); + if (user != null && user.Key != null) + { + stubbedFlagCache.CacheFlagsForUser(flags, user); + } + + var mockOnlineConnectionManager = new MockConnectionManager(true); + var mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); + var mockPollingProcessor = new MockPollingProcessor(); + var mockPersister = new MockPersister(); + var mockDeviceInfo = new MockDeviceInfo(); + var featureFlagListener = new FeatureFlagListenerManager(); + + Configuration configuration = Configuration.Default(appKey) + .WithFlagCacheManager(mockFlagCacheManager) + .WithConnectionManager(mockOnlineConnectionManager) + .WithUpdateProcessor(mockPollingProcessor) + .WithPersister(mockPersister) + .WithDeviceInfo(mockDeviceInfo) + .WithFeatureFlagListenerManager(featureFlagListener); + return configuration; + } } } From e4f61094487e439064a30e0cf50c8bd02479f4c6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 17 Jul 2018 18:06:07 -0700 Subject: [PATCH 012/499] get rid of test fixture files --- .../FeatureFlag.json | 33 ------------ .../FeatureFlagsFromService.json | 38 -------------- .../FlagCacheManagerTests.cs | 20 +++++--- .../LaunchDarkly.Xamarin.Tests/JSONReader.cs | 50 ------------------- .../LaunchDarkly.Xamarin.Tests.csproj | 8 --- .../LdClientTests.cs | 2 +- .../MobilePollingProcessorTests.cs | 10 +++- .../MobileStreamingProcessorTests.cs | 9 +++- .../MockFeatureFlagRequestor.cs | 10 +++- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 7 ++- .../UserFlagCacheTests.cs | 17 +++---- 11 files changed, 49 insertions(+), 155 deletions(-) delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/FeatureFlag.json delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/JSONReader.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlag.json b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlag.json deleted file mode 100644 index 0363fa5a..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlag.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "boolean-flag": { - "value": true, - "version": 123, - "variation": 0, - "trackEvents": true, - "debugEventsUntilDate": 1525196668210 - }, - "json-flag": { - "value": {"some-key": "some-value"}, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "float-flag": { - "value": 3.14159, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "int-flag": { - "value": 5, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "string-flag": { - "value": "string value", - "version": 456, - "variation": 1, - "trackEvents": false - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json deleted file mode 100644 index 37a9b008..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagsFromService.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "boolean-flag": { - "value": true, - "version": 123, - "variation": 0, - "trackEvents": true, - "debugEventsUntilDate": 1525196668210 - }, - "json-flag": { - "value": {"some-key": "a new value"}, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "float-flag": { - "value": 13.14159, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "int-flag": { - "value": 15, - "version": 456, - "variation": 1, - "trackEvents": false - }, - "string-flag": { - "value": "markw@magenic.com", - "version": 456, - "variation": 1, - "trackEvents": false - }, - "off-flag": { - "value": null, - "version": 456, - "trackEvents": false - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs index 3b0df506..574cbfd0 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs @@ -1,5 +1,4 @@ -using System; -using LaunchDarkly.Client; +using LaunchDarkly.Client; using Newtonsoft.Json.Linq; using Xunit; @@ -7,6 +6,12 @@ namespace LaunchDarkly.Xamarin.Tests { public class FlagCacheManagerTests { + private const string initialFlagsJson = "{" + + "\"int-flag\":{\"value\":15}," + + "\"float-flag\":{\"value\":13.5}," + + "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + + "}"; + IUserFlagCache deviceCache = new UserFlagInMemoryCache(); IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); FeatureFlagListenerManager listenerManager = new FeatureFlagListenerManager(); @@ -16,7 +21,8 @@ public class FlagCacheManagerTests IFlagCacheManager ManagerWithCachedFlags() { var flagCacheManager = new FlagCacheManager(deviceCache, inMemoryCache, listenerManager, user); - flagCacheManager.CacheFlagsFromService(JSONReader.StubbedFlagsDictionary(), user); + var flags = TestUtil.DecodeFlagsJson(initialFlagsJson); + flagCacheManager.CacheFlagsFromService(flags, user); return flagCacheManager; } @@ -27,7 +33,7 @@ public void CacheFlagsShouldStoreFlagsInDeviceCache() var cachedDeviceFlags = deviceCache.RetrieveFlags(user); Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); + Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.ToObject()); } [Fact] @@ -37,7 +43,7 @@ public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() var cachedDeviceFlags = inMemoryCache.RetrieveFlags(user); Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.14159, cachedDeviceFlags["float-flag"].value.ToObject()); + Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.ToObject()); } [Fact] @@ -97,8 +103,8 @@ public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() var listener = new TestListener(); listenerManager.RegisterListener(listener, "int-flag"); - var updatedFlags = JSONReader.UpdatedStubbedFlagsDictionary(); - flagCacheManager.CacheFlagsFromService(updatedFlags, user); + var newFlagsJson = "{\"int-flag\":{\"value\":5}}"; + flagCacheManager.CacheFlagsFromService(TestUtil.DecodeFlagsJson(newFlagsJson), user); Assert.Equal(5, listener.FeatureFlags["int-flag"].ToObject()); } diff --git a/tests/LaunchDarkly.Xamarin.Tests/JSONReader.cs b/tests/LaunchDarkly.Xamarin.Tests/JSONReader.cs deleted file mode 100644 index 52469e89..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/JSONReader.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using LaunchDarkly.Client; -using Newtonsoft.Json; - -namespace LaunchDarkly.Xamarin.Tests -{ - public static class JSONReader - { - public static string FeatureFlagJSON() - { - return JSONTextFromFile("FeatureFlag.json"); - } - - public static string FeatureFlagJSONFromService() - { - return JSONTextFromFile("FeatureFlagsFromService.json"); - } - - public static string JSONTextFromFile(string filename) - { - return System.IO.File.ReadAllText(filename); - } - - internal static IUserFlagCache StubbedFlagCache(User user) - { - // stub json into the FlagCache - IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); - if (String.IsNullOrEmpty(user.Key)) - return stubbedFlagCache; - - stubbedFlagCache.CacheFlagsForUser(StubbedFlagsDictionary(), user); - return stubbedFlagCache; - } - - internal static IDictionary StubbedFlagsDictionary() - { - var text = FeatureFlagJSONFromService(); - var flags = JsonConvert.DeserializeObject>(text); - return flags; - } - - internal static IDictionary UpdatedStubbedFlagsDictionary() - { - var text = FeatureFlagJSON(); - var flags = JsonConvert.DeserializeObject>(text); - return flags; - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index a4a01263..903ade3f 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -27,12 +27,4 @@ - - - Always - - - Always - - diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 41b2794d..09990da8 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -11,7 +11,7 @@ public class DefaultLdClientTests LdClient Client() { User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var configuration = TestUtil.ConfigWithFlagsJson(user, appKey, JSONReader.FeatureFlagJSON()); + var configuration = TestUtil.ConfigWithFlagsJson(user, appKey, "{}"); return TestUtil.CreateClient(configuration, user); } diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs index 3a9552ce..1728fa34 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs @@ -6,12 +6,18 @@ namespace LaunchDarkly.Xamarin.Tests { public class MobilePollingProcessorTests { + private const string flagsJson = "{" + + "\"int-flag\":{\"value\":15}," + + "\"float-flag\":{\"value\":13.5}," + + "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + + "}"; + IFlagCacheManager mockFlagCacheManager; User user; IMobileUpdateProcessor Processor() { - var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(); + var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); var stubbedFlagCache = new UserFlagInMemoryCache(); mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); user = User.WithKey("user1Key"); @@ -32,7 +38,7 @@ public void StartWaitsUntilFlagCacheFilled() var initTask = processor.Start(); var unused = initTask.Wait(TimeSpan.FromSeconds(1)); var flags = mockFlagCacheManager.FlagsForUser(user); - Assert.Equal(6, flags.Count); + Assert.Equal(3, flags.Count); } } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs index dccd107d..83f99c41 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs @@ -11,6 +11,12 @@ namespace LaunchDarkly.Xamarin.Tests { public class MobileStreamingProcessorTests { + private const string initialFlagsJson = "{" + + "\"int-flag\":{\"value\":15,\"version\":100}," + + "\"float-flag\":{\"value\":13.5,\"version\":100}," + + "\"string-flag\":{\"value\":\"markw@magenic.com\",\"version\":100}" + + "}"; + User user = User.WithKey("user key"); EventSourceMock mockEventSource; TestEventSourceFactory eventSourceFactory; @@ -150,8 +156,7 @@ string DeleteFlagWithLowerVersion() void PUTMessageSentToProcessor() { - string jsonData = JSONReader.FeatureFlagJSONFromService(); - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(jsonData, null), "put"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(initialFlagsJson, null), "put"); mockEventSource.RaiseMessageRcvd(eventArgs); } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs b/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs index 09cc07c6..59b6c964 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs @@ -4,6 +4,13 @@ namespace LaunchDarkly.Xamarin.Tests { internal class MockFeatureFlagRequestor : IFeatureFlagRequestor { + private readonly string _jsonFlags; + + public MockFeatureFlagRequestor(string jsonFlags) + { + _jsonFlags = jsonFlags; + } + public void Dispose() { @@ -11,8 +18,7 @@ public void Dispose() public Task FeatureFlagsAsync() { - var jsonText = JSONReader.FeatureFlagJSONFromService(); - var response = new WebResponse(200, jsonText, null); + var response = new WebResponse(200, _jsonFlags, null); return Task.FromResult(response); } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 3db8735e..25a3da41 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -31,9 +31,14 @@ public static string JsonFlagsWithSingleFlag(string flagKey, JToken value) return JsonConvert.SerializeObject(o); } + public static IDictionary DecodeFlagsJson(string flagsJson) + { + return JsonConvert.DeserializeObject>(flagsJson); + } + public static Configuration ConfigWithFlagsJson(User user, string appKey, string flagsJson) { - var flags = JsonConvert.DeserializeObject>(flagsJson); + var flags = DecodeFlagsJson(flagsJson); IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); if (user != null && user.Key != null) { diff --git a/tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs b/tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs index 86683063..4bfffcaa 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using LaunchDarkly.Client; -using Newtonsoft.Json; +using LaunchDarkly.Client; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -16,14 +12,13 @@ public class UserFlagCacheTests [Fact] public void CanCacheFlagsInMemory() { - var text = JSONReader.FeatureFlagJSON(); - var flags = JsonConvert.DeserializeObject>(text); + var jsonFlags = @"{""flag1"":{""value"":1},""flag2"":{""value"":2}}"; + var flags = TestUtil.DecodeFlagsJson(jsonFlags); inMemoryCache.CacheFlagsForUser(flags, user1); var flagsRetrieved = inMemoryCache.RetrieveFlags(user1); - Assert.Equal(flags.Count, flagsRetrieved.Count); - var secondFlag = flags.Values.ToList()[1]; - var secondFlagRetrieved = flagsRetrieved.Values.ToList()[1]; - Assert.Equal(secondFlag, secondFlagRetrieved); + Assert.Equal(2, flagsRetrieved.Count); + Assert.Equal(flags["flag1"], flagsRetrieved["flag1"]); + Assert.Equal(flags["flag2"], flagsRetrieved["flag2"]); } } } From 5ef9ea2abc3463e37e30aa401ce2618d6f143ab2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 17 Jul 2018 18:17:14 -0700 Subject: [PATCH 013/499] fix default value logic --- src/LaunchDarkly.Xamarin/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index c404b742..6d017d34 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -334,7 +334,7 @@ JToken Variation(string featureKey, JToken defaultValue) { featureFlagEvent = new FeatureFlagEvent(featureKey, flag); var value = flag.value; - if (value == null) { + if (value == null || value.Type == JTokenType.Null) { featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, User, defaultValue); From c43c4bb324478bf44f2f01964746410ceecaff93 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 18 Jul 2018 15:43:54 -0700 Subject: [PATCH 014/499] add tests for event generation --- src/LaunchDarkly.Xamarin/Configuration.cs | 14 ++ src/LaunchDarkly.Xamarin/Factory.cs | 6 +- .../LdClientEventTests.cs | 137 ++++++++++++++++++ .../MockEventProcessor.cs | 19 +++ tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 2 +- 5 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs create mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index c278b592..8a584731 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net.Http; using Common.Logging; +using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { @@ -125,6 +126,7 @@ public class Configuration : IMobileConfiguration internal IFlagCacheManager FlagCacheManager { get; set; } internal IConnectionManager ConnectionManager { get; set; } + internal IEventProcessor EventProcessor { get; set; } internal IMobileUpdateProcessor MobileUpdateProcessor { get; set; } internal ISimplePersistance Persister { get; set; } internal IDeviceInfo DeviceInfo { get; set; } @@ -567,6 +569,18 @@ public static Configuration WithConnectionManager(this Configuration configurati return configuration; } + /// + /// Sets the IEventProcessor instance, used internally for stubbing mock instances. + /// + /// Configuration. + /// Event processor. + /// the same Configuration instance + public static Configuration WithEventProcessor(this Configuration configuration, IEventProcessor eventProcessor) + { + configuration.EventProcessor = eventProcessor; + return configuration; + } + /// /// Determines whether to use the Report method for networking requests /// diff --git a/src/LaunchDarkly.Xamarin/Factory.cs b/src/LaunchDarkly.Xamarin/Factory.cs index 56eb4369..5fb139c0 100644 --- a/src/LaunchDarkly.Xamarin/Factory.cs +++ b/src/LaunchDarkly.Xamarin/Factory.cs @@ -72,8 +72,12 @@ internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration confi return updateProcessor; } - internal static IEventProcessor CreateEventProcessor(IBaseConfiguration configuration) + internal static IEventProcessor CreateEventProcessor(Configuration configuration) { + if (configuration.EventProcessor != null) + { + return configuration.EventProcessor; + } if (configuration.Offline) { Log.InfoFormat("Was configured to be offline, starting service with NullEventProcessor"); diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs new file mode 100644 index 00000000..5e8c58ce --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs @@ -0,0 +1,137 @@ +using LaunchDarkly.Client; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class LdClientEventTests + { + private static readonly User user = User.WithKey("userkey"); + private MockEventProcessor eventProcessor = new MockEventProcessor(); + + public LdClient MakeClient(User user, string flagsJson) + { + Configuration config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); + config.WithEventProcessor(eventProcessor); + return TestUtil.CreateClient(config, user); + } + + [Fact] + public void IdentifySendsIdentifyEvent() + { + using (LdClient client = MakeClient(user, "{}")) + { + User user1 = User.WithKey("userkey1"); + client.Identify(user1); + Assert.Collection(eventProcessor.Events, e => + { + IdentifyEvent ie = Assert.IsType(e); + Assert.Equal(user1.Key, ie.User.Key); + }); + } + } + + [Fact] + public void TrackSendsCustomEvent() + { + using (LdClient client = MakeClient(user, "{}")) + { + JToken data = new JValue("hi"); + client.Track("eventkey", data); + Assert.Collection(eventProcessor.Events, e => + { + CustomEvent ce = Assert.IsType(e); + Assert.Equal("eventkey", ce.Key); + Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(data, ce.JsonData); + }); + } + } + + [Fact] + public void VariationSendsFeatureEventForValidFlag() + { + string flagsJson = @"{""flag"":{ + ""value"":""a"",""variation"":1,""version"":1000, + ""trackEvents"":true, ""debugEventsUntilDate"":2000 }}"; + using (LdClient client = MakeClient(user, flagsJson)) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("a", result); + Assert.Collection(eventProcessor.Events, e => + { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + Assert.True(fe.TrackEvents); + Assert.Equal(2000, fe.DebugEventsUntilDate); + }); + } + } + + [Fact] + public void FeatureEventUsesFlagVersionIfProvided() + { + string flagsJson = @"{""flag"":{ + ""value"":""a"",""variation"":1,""version"":1000, + ""flagVersion"":1500 }}"; + using (LdClient client = MakeClient(user, flagsJson)) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("a", result); + Assert.Collection(eventProcessor.Events, e => + { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1500, fe.Version); + Assert.Equal("b", fe.Default); + }); + } + } + + [Fact] + public void VariationSendsFeatureEventForDefaultValue() + { + string flagsJson = @"{""flag"":{ + ""value"":null,""variation"":null,""version"":1000 }}"; + using (LdClient client = MakeClient(user, flagsJson)) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("b", result); + Assert.Collection(eventProcessor.Events, e => + { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + }); + } + } + + [Fact] + public void VariationSendsFeatureEventForUnknownFlag() + { + using (LdClient client = MakeClient(user, "{}")) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("b", result); + Assert.Collection(eventProcessor.Events, e => + { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Null(fe.Version); + Assert.Equal("b", fe.Default); + }); + } + } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs b/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs new file mode 100644 index 00000000..1f0d3bc5 --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class MockEventProcessor : IEventProcessor + { + public List Events = new List(); + + public void SendEvent(Event e) + { + Events.Add(e); + } + + public void Flush() { } + + public void Dispose() { } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 25a3da41..224c7bb2 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Xamarin.Tests { - class TestUtil + public class TestUtil { // Any tests that are going to access the static LdClient.Instance must hold this lock, // to avoid interfering with tests that use CreateClient. From 537886f0413a0acff47438cbc3c5789b8a778fc4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 19 Jul 2018 11:44:58 -0700 Subject: [PATCH 015/499] throw exception if user is null; assign unique key if key is null or empty --- src/LaunchDarkly.Xamarin/LdClient.cs | 92 ++++++++----------- .../LdClientTests.cs | 48 +++++++++- 2 files changed, 84 insertions(+), 56 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 6d017d34..f55c443d 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -58,7 +58,16 @@ public sealed class LdClient : ILdMobileClient LdClient() { } LdClient(Configuration configuration, User user) - { + { + if (configuration == null) + { + throw new ArgumentNullException("configuration"); + } + if (user == null) + { + throw new ArgumentNullException("user"); + } + Config = configuration; connectionLock = new SemaphoreSlim(1, 1); @@ -67,8 +76,8 @@ public sealed class LdClient : ILdMobileClient deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); - // If you pass in a null user or user with a null key, one will be assigned to them. - if (user == null || user.Key == null) + // If you pass in a user with a null or blank key, one will be assigned to them. + if (String.IsNullOrEmpty(user.Key)) { User = UserWithUniqueKey(user); } @@ -91,7 +100,7 @@ public sealed class LdClient : ILdMobileClient /// /// This constructor will wait and block on the current thread until initialization and the /// first response from the LaunchDarkly service is returned, if you would rather this happen - /// in an async fashion you can use + /// in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more specific /// to instantiate the single instance of LdClient @@ -99,7 +108,8 @@ public sealed class LdClient : ILdMobileClient /// /// The singleton LdClient instance. /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static LdClient Init(string mobileKey, User user) { var config = Configuration.Default(mobileKey); @@ -110,7 +120,7 @@ public static LdClient Init(string mobileKey, User user) /// /// Creates and returns new LdClient singleton instance, then starts the workflow for /// fetching feature flags. This constructor should be used if you do not want to wait - /// for the IUpdateProcessor instance to finish initializing and receive the first response + /// for the client to finish initializing and receive the first response /// from the LaunchDarkly service. /// /// This is the creation point for LdClient, you must use this static method or the more specific @@ -119,7 +129,8 @@ public static LdClient Init(string mobileKey, User user) /// /// The singleton LdClient instance. /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static async Task InitAsync(string mobileKey, User user) { var config = Configuration.Default(mobileKey); @@ -133,7 +144,7 @@ public static async Task InitAsync(string mobileKey, User user) /// /// This constructor will wait and block on the current thread until initialization and the /// first response from the LaunchDarkly service is returned, if you would rather this happen - /// in an async fashion you can use + /// in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more basic /// to instantiate the single instance of LdClient @@ -141,7 +152,8 @@ public static async Task InitAsync(string mobileKey, User user) /// /// The singleton LdClient instance. /// The client configuration object - /// The user needed for client operations. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static LdClient Init(Configuration config, User user) { CreateInstance(config, user); @@ -166,7 +178,8 @@ public static LdClient Init(Configuration config, User user) /// /// The singleton LdClient instance. /// The client configuration object - /// The user needed for client operations. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static Task InitAsync(Configuration config, User user) { CreateInstance(config, user); @@ -184,8 +197,10 @@ public static Task InitAsync(Configuration config, User user) static void CreateInstance(Configuration configuration, User user) { - if (Instance != null) - throw new Exception("LdClient instance already exists."); + if (Instance != null) + { + throw new Exception("LdClient instance already exists."); + } Instance = new LdClient(configuration, user); Log.InfoFormat("Initialized LaunchDarkly Client {0}", @@ -318,17 +333,7 @@ JToken Variation(string featureKey, JToken defaultValue) Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); return defaultValue; } - - if (User == null || User.Key == null) - { - Log.Warn("Feature flag evaluation called with null user or null user key. Returning default"); - featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, - User, - defaultValue); - eventProcessor.SendEvent(featureRequestEvent); - return defaultValue; - } - + var flag = flagCacheManager.FlagForUser(featureKey, User); if (flag != null) { @@ -372,11 +377,6 @@ public IDictionary AllFlags() Log.Warn("AllFlags() was called before client has finished initializing. Returning null."); return null; } - if (User == null || User.Key == null) - { - Log.Warn("AllFlags() called with null user or null user key. Returning null"); - return null; - } return flagCacheManager.FlagsForUser(User) .ToDictionary(p => p.Key, p => p.Value.value); @@ -385,11 +385,6 @@ public IDictionary AllFlags() /// public void Track(string eventName, JToken data) { - if (User == null || User.Key == null) - { - Log.Warn("Track called with null user or null user key"); - } - eventProcessor.SendEvent(eventFactory.NewCustomEvent(eventName, User, data)); } @@ -433,19 +428,14 @@ public async Task IdentifyAsync(User user) { if (user == null) { - Log.Warn("Identify called with null user"); - return; + throw new ArgumentNullException("user"); } - User userWithKey = null; - if (user.Key == null) + User userWithKey = user; + if (String.IsNullOrEmpty(user.Key)) { userWithKey = UserWithUniqueKey(user); } - else - { - userWithKey = user; - } await connectionLock.WaitAsync(); try @@ -488,22 +478,14 @@ void ClearUpdateProcessor() } } - User UserWithUniqueKey(User user = null) + User UserWithUniqueKey(User user) { string uniqueId = deviceInfo.UniqueDeviceId(); - - if (user != null) - { - var updatedUser = new User(user) - { - Key = uniqueId, - Anonymous = true - }; - - return updatedUser; - } - - return new User(uniqueId); + return new User(user) + { + Key = uniqueId, + Anonymous = true + }; } void IDisposable.Dispose() diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 09990da8..69c7dacf 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -21,6 +21,19 @@ public void CanCreateClientWithConfigAndUser() Assert.NotNull(Client()); } + [Fact] + public void CannotCreateClientWithNullConfig() + { + Assert.Throws(() => LdClient.Init((Configuration)null, User.WithKey("user"))); + } + + [Fact] + public void CannotCreateClientWithNullUser() + { + Configuration config = TestUtil.ConfigWithFlagsJson(User.WithKey("dummy"), appKey, "{}"); + Assert.Throws(() => LdClient.Init(config, null)); + } + [Fact] public void IdentifyUpdatesTheUser() { @@ -30,6 +43,21 @@ public void IdentifyUpdatesTheUser() Assert.Equal(client.User, updatedUser); } + [Fact] + public void IdentifyWithNullUserThrowsException() + { + var client = Client(); + Assert.Throws(() => client.Identify(null)); + } + + [Fact] + public void IdentifyAsyncWithNullUserThrowsException() + { + var client = Client(); + Assert.ThrowsAsync(async () => await client.IdentifyAsync(null)); + // note that exceptions thrown out of an async task are always wrapped in AggregateException + } + [Fact] public void SharedClientIsTheOnlyClientAvailable() { @@ -82,7 +110,16 @@ public void UserWithNullKeyWillHaveUniqueKeySet() } [Fact] - public void IdentifyWithUserMissingKeyUsesUniqueGeneratedKey() + public void UserWithEmptyKeyWillHaveUniqueKeySet() + { + var userWithEmptyKey = User.WithKey(""); + var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, "someOtherAppKey", "{}"); + var client = TestUtil.CreateClient(config, userWithEmptyKey); + Assert.Equal(MockDeviceInfo.key, client.User.Key); + } + + [Fact] + public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() { var client = Client(); client.Identify(User.WithKey("a new user's key")); @@ -91,6 +128,15 @@ public void IdentifyWithUserMissingKeyUsesUniqueGeneratedKey() Assert.Equal(MockDeviceInfo.key, client.User.Key); } + [Fact] + public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() + { + var client = Client(); + var userWithEmptyKey = User.WithKey(""); + client.Identify(userWithEmptyKey); + Assert.Equal(MockDeviceInfo.key, client.User.Key); + } + [Fact] public void UpdatingKeylessUserWillGenerateNewUserWithSameValues() { From 8d299c7172bae06f36dd7a61fe39bf8b36ab1c4c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 11:33:40 -0700 Subject: [PATCH 016/499] don't use strong naming for LaunchDarkly.Xamarin --- .circleci/config.yml | 4 ---- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 8 ++------ src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs | 7 +------ .../LaunchDarkly.Xamarin.Tests.csproj | 10 +++------- 4 files changed, 6 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a102ad9f..93ae6397 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,11 +9,7 @@ jobs: docker: - image: microsoft/dotnet:2.0-sdk-jessie steps: - - run: - name: install packages - command: apt-get -q update && apt-get install -qy awscli - checkout - - run: aws s3 cp s3://launchdarkly-pastebin/ci/dotnet/LaunchDarkly.Xamarin.snk LaunchDarkly.Xamarin.snk - run: dotnet restore - run: dotnet build src/LaunchDarkly.Xamarin -f netstandard2.0 - run: dotnet test tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj -f netcoreapp2.0 diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 702070f3..2b6ea266 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,12 +1,9 @@ - 1.0.0-beta9 + 1.0.0-beta10 netstandard1.6;netstandard2.0;net45 - true - ..\..\LaunchDarkly.Xamarin.snk - false @@ -15,14 +12,13 @@ obj\Release\netstandard2.0 - false - + diff --git a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs index 184d6064..7d76c4d2 100644 --- a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs +++ b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs @@ -1,10 +1,5 @@ using System; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Tests,PublicKey=" + -"0024000004800000940000000602000000240000525341310004000001000100" + -"058a1dbccbc342759dc98b1eaba4467bfdea062629f212cf7c669ff26b4e2ff3" + -"c408292487bc349b8a687d73033ff14dbf861e1eea23303a5b5d13b1db034799" + -"13bd120ba372cf961d27db9f652631565f4e8aff4a79e11cfe713833157ecb5d" + -"cbc02d772967d919f8f06fbee227a664dc591932d5b05f4da1c8439702ecfdb1")] +[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Tests")] diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 903ade3f..aa036d1b 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -3,21 +3,17 @@ netcoreapp2.0 2.0.0 - true - ..\..\LaunchDarkly.Xamarin.snk - true - - + - - + + From df9e61e55fe2a36778cc4b861df61a42dd5d9e10 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 12:09:48 -0700 Subject: [PATCH 017/499] skip connectivity check in .NET Standard --- .../MobileConnectionManager.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs index 9dcfa92d..3d9cccfa 100644 --- a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs +++ b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs @@ -9,8 +9,13 @@ internal class MobileConnectionManager : IConnectionManager internal MobileConnectionManager() { - isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; - Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + UpdateConnectedStatus(); + try + { + Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + } + catch (NotImplementedInReferenceAssemblyException) + { } } bool isConnected; @@ -22,12 +27,24 @@ bool IConnectionManager.IsConnected isConnected = value; } } - - + void Connectivity_ConnectivityChanged(ConnectivityChangedEventArgs e) { - isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; + UpdateConnectedStatus(); ConnectionChanged?.Invoke(isConnected); } + + private void UpdateConnectedStatus() + { + try + { + isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; + } + catch (NotImplementedInReferenceAssemblyException) + { + // .NET Standard has no way to detect network connectivity + isConnected = true; + } + } } } From 3e4f4e12bb9ead3012d35f087159eb8b29ed7fe4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 12:11:19 -0700 Subject: [PATCH 018/499] fix name of config setter --- src/LaunchDarkly.Xamarin/Configuration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index 8a584731..3ae604df 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -239,7 +239,7 @@ public static class ConfigurationExtensions /// the configuration /// the base URI as a string /// the same Configuration instance - public static Configuration WithUri(this Configuration configuration, string uri) + public static Configuration WithBaseUri(this Configuration configuration, string uri) { if (uri != null) configuration.BaseUri = new Uri(uri); @@ -253,7 +253,7 @@ public static Configuration WithUri(this Configuration configuration, string uri /// the configuration /// the base URI /// the same Configuration instance - public static Configuration WithUri(this Configuration configuration, Uri uri) + public static Configuration WithBaseUri(this Configuration configuration, Uri uri) { if (uri != null) configuration.BaseUri = uri; From fb5621a9f840b56c5053553336d4b3c2b6765a5a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 12:53:30 -0700 Subject: [PATCH 019/499] use platform reference instead of package reference --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 2b6ea266..a38613e1 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -14,8 +14,8 @@ + - From 52bc60ae4083bbf2b2fae44923d6274fd55301d9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 12:58:36 -0700 Subject: [PATCH 020/499] skip using preferences API in .NET Standard --- .../SimpleMobileDevicePersistance.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs index 022905f6..5a0f86e1 100644 --- a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs +++ b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs @@ -7,12 +7,23 @@ internal class SimpleMobileDevicePersistance : ISimplePersistance { public void Save(string key, string value) { - Preferences.Set(key, value); + try + { + Preferences.Set(key, value); + } + catch (NotImplementedInReferenceAssemblyException) { } } public string GetValue(string key) { - return Preferences.Get(key, null); + try + { + return Preferences.Get(key, null); + } + catch (NotImplementedInReferenceAssemblyException) + { + return null; + } } } } From a9d5167179153958c1722a3624acf551b925d3a0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 13:20:11 -0700 Subject: [PATCH 021/499] beta11 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index a38613e1..6b163c85 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta10 + 1.0.0-beta11 netstandard1.6;netstandard2.0;net45 From fca4ca08c2774b9d5dac551d09b8d73fe4ee0af9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 13:24:44 -0700 Subject: [PATCH 022/499] fix test --- tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs index 03e2ac60..250deb68 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs @@ -11,7 +11,7 @@ public class ConfigurationTest public void CanOverrideConfiguration() { var config = Configuration.Default("AnyOtherSdkKey") - .WithUri("https://app.AnyOtherEndpoint.com") + .WithBaseUri("https://app.AnyOtherEndpoint.com") .WithEventQueueCapacity(99) .WithPollingInterval(TimeSpan.FromMinutes(1)); From a8ffc36f669fd1b540f58d0c1efd1ef0d4250aef Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 14:13:08 -0700 Subject: [PATCH 023/499] rm note about signing --- README.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/README.md b/README.md index b6617ad5..d2065665 100644 --- a/README.md +++ b/README.md @@ -49,26 +49,6 @@ Contributing See [Contributing](https://github.com/launchdarkly/xamarin-client/blob/master/CONTRIBUTING.md). -Signing -------- -The artifacts generated from this repo are signed by LaunchDarkly. The public key file is in this repo at `LaunchDarkly.Xamarin.pk` as well as here: - -``` -Public Key: -00240000048000009400000006020000 -00240000525341310004000001000100 -058a1dbccbc342759dc98b1eaba4467b -fdea062629f212cf7c669ff26b4e2ff3 -c408292487bc349b8a687d73033ff14d -bf861e1eea23303a5b5d13b1db034799 -13bd120ba372cf961d27db9f65263156 -5f4e8aff4a79e11cfe713833157ecb5d -cbc02d772967d919f8f06fbee227a664 -dc591932d5b05f4da1c8439702ecfdb1 - -Public Key Token: 90b24964a3dfb906f86add69004e6885 -``` - About LaunchDarkly ----------- From d32bdf609b43b6ee41fb9861a18c54bfbf189d89 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 14:56:18 -0700 Subject: [PATCH 024/499] don't set updateProcessor to null --- src/LaunchDarkly.Xamarin/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index f55c443d..8b0f4fa1 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -474,7 +474,7 @@ void ClearUpdateProcessor() if (updateProcessor != null) { updateProcessor.Dispose(); - updateProcessor = null; + updateProcessor = new NullUpdateProcessor(); } } From 23c94c1a1d3285a0b6dda896e23c62ddff4575a9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 14:58:10 -0700 Subject: [PATCH 025/499] more cleanup/simplification of test code --- .../LdClientTests.cs | 234 ++++++++++-------- .../MockComponents.cs | 178 +++++++++++++ .../MockConnectionManager.cs | 34 --- .../MockEventProcessor.cs | 19 -- .../MockFeatureFlagRequestor.cs | 25 -- .../MockFlagCacheManager.cs | 53 ---- .../MockPollingProcessor.cs | 30 --- .../StubbedConfigAndUserBuilder.cs | 58 ----- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 20 +- 9 files changed, 319 insertions(+), 332 deletions(-) create mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockConnectionManager.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockFlagCacheManager.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/MockPollingProcessor.cs delete mode 100644 tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 69c7dacf..ef74713a 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -7,55 +7,55 @@ namespace LaunchDarkly.Xamarin.Tests public class DefaultLdClientTests { static readonly string appKey = "some app key"; + static readonly User simpleUser = User.WithKey("user-key"); LdClient Client() { - User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var configuration = TestUtil.ConfigWithFlagsJson(user, appKey, "{}"); - return TestUtil.CreateClient(configuration, user); + var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + return TestUtil.CreateClient(configuration, simpleUser); } - - [Fact] - public void CanCreateClientWithConfigAndUser() - { - Assert.NotNull(Client()); - } - + [Fact] public void CannotCreateClientWithNullConfig() { - Assert.Throws(() => LdClient.Init((Configuration)null, User.WithKey("user"))); + Assert.Throws(() => LdClient.Init((Configuration)null, simpleUser)); } [Fact] public void CannotCreateClientWithNullUser() { - Configuration config = TestUtil.ConfigWithFlagsJson(User.WithKey("dummy"), appKey, "{}"); + Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); Assert.Throws(() => LdClient.Init(config, null)); } [Fact] public void IdentifyUpdatesTheUser() { - var client = Client(); - var updatedUser = User.WithKey("some new key"); - client.Identify(updatedUser); - Assert.Equal(client.User, updatedUser); + using (var client = Client()) + { + var updatedUser = User.WithKey("some new key"); + client.Identify(updatedUser); + Assert.Equal(client.User, updatedUser); + } } [Fact] public void IdentifyWithNullUserThrowsException() { - var client = Client(); - Assert.Throws(() => client.Identify(null)); + using (var client = Client()) + { + Assert.Throws(() => client.Identify(null)); + } } [Fact] public void IdentifyAsyncWithNullUserThrowsException() { - var client = Client(); - Assert.ThrowsAsync(async () => await client.IdentifyAsync(null)); - // note that exceptions thrown out of an async task are always wrapped in AggregateException + using (var client = Client()) + { + Assert.ThrowsAsync(async () => await client.IdentifyAsync(null)); + // note that exceptions thrown out of an async task are always wrapped in AggregateException + } } [Fact] @@ -63,16 +63,17 @@ public void SharedClientIsTheOnlyClientAvailable() { lock (TestUtil.ClientInstanceLock) { - User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); - var config = TestUtil.ConfigWithFlagsJson(user, appKey, "{}"); - var client = LdClient.Init(config, user); - try + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + using (var client = LdClient.Init(config, simpleUser)) { - Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); - } - finally - { - LdClient.Instance = null; + try + { + Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, simpleUser)); + } + finally + { + LdClient.Instance = null; + } } } } @@ -80,117 +81,150 @@ public void SharedClientIsTheOnlyClientAvailable() [Fact] public void ConnectionManagerShouldKnowIfOnlineOrNot() { - var client = Client(); - var connMgr = client.Config.ConnectionManager as MockConnectionManager; - connMgr.ConnectionChanged += (bool obj) => client.Online = obj; - connMgr.Connect(true); - Assert.False(client.IsOffline()); - connMgr.Connect(false); - Assert.False(client.Online); + using (var client = Client()) + { + var connMgr = client.Config.ConnectionManager as MockConnectionManager; + connMgr.ConnectionChanged += (bool obj) => client.Online = obj; + connMgr.Connect(true); + Assert.False(client.IsOffline()); + connMgr.Connect(false); + Assert.False(client.Online); + } } [Fact] public void ConnectionChangeShouldStopUpdateProcessor() { - var client = Client(); - var connMgr = client.Config.ConnectionManager as MockConnectionManager; - connMgr.ConnectionChanged += (bool obj) => client.Online = obj; - connMgr.Connect(false); - var mockUpdateProc = client.Config.MobileUpdateProcessor as MockPollingProcessor; - Assert.False(mockUpdateProc.IsRunning); + using (var client = Client()) + { + var connMgr = client.Config.ConnectionManager as MockConnectionManager; + connMgr.ConnectionChanged += (bool obj) => client.Online = obj; + connMgr.Connect(false); + var mockUpdateProc = client.Config.MobileUpdateProcessor as MockPollingProcessor; + Assert.False(mockUpdateProc.IsRunning); + } } [Fact] public void UserWithNullKeyWillHaveUniqueKeySet() { var userWithNullKey = User.WithKey(null); - var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, "someOtherAppKey", "{}"); - var client = TestUtil.CreateClient(config, userWithNullKey); - Assert.Equal(MockDeviceInfo.key, client.User.Key); + var uniqueId = "some-unique-key"; + var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, appKey, "{}") + .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + using (var client = TestUtil.CreateClient(config, userWithNullKey)) + { + Assert.Equal(uniqueId, client.User.Key); + Assert.True(client.User.Anonymous); + } } [Fact] public void UserWithEmptyKeyWillHaveUniqueKeySet() { var userWithEmptyKey = User.WithKey(""); - var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, "someOtherAppKey", "{}"); - var client = TestUtil.CreateClient(config, userWithEmptyKey); - Assert.Equal(MockDeviceInfo.key, client.User.Key); + var uniqueId = "some-unique-key"; + var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, appKey, "{}") + .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + using (var client = TestUtil.CreateClient(config, userWithEmptyKey)) + { + Assert.Equal(uniqueId, client.User.Key); + Assert.True(client.User.Anonymous); + } } [Fact] public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() { - var client = Client(); - client.Identify(User.WithKey("a new user's key")); var userWithNullKey = User.WithKey(null); - client.Identify(userWithNullKey); - Assert.Equal(MockDeviceInfo.key, client.User.Key); + var uniqueId = "some-unique-key"; + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + client.Identify(userWithNullKey); + Assert.Equal(uniqueId, client.User.Key); + Assert.True(client.User.Anonymous); + } } [Fact] public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() { - var client = Client(); var userWithEmptyKey = User.WithKey(""); - client.Identify(userWithEmptyKey); - Assert.Equal(MockDeviceInfo.key, client.User.Key); - } - - [Fact] - public void UpdatingKeylessUserWillGenerateNewUserWithSameValues() - { - var updatedUser = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn(String.Empty); - var client = Client(); - var previousUser = client.User; - client.Identify(updatedUser); - Assert.NotEqual(updatedUser, previousUser); - Assert.Equal(updatedUser.Avatar, previousUser.Avatar); - Assert.Equal(updatedUser.Country, previousUser.Country); - Assert.Equal(updatedUser.Email, previousUser.Email); - Assert.Equal(updatedUser.FirstName, previousUser.FirstName); - Assert.Equal(updatedUser.LastName, previousUser.LastName); - Assert.Equal(updatedUser.Name, previousUser.Name); - Assert.Equal(updatedUser.IpAddress, previousUser.IpAddress); - Assert.Equal(updatedUser.SecondaryKey, previousUser.SecondaryKey); - Assert.Equal(updatedUser.Custom["somePrivateAttr1"], previousUser.Custom["somePrivateAttr1"]); - Assert.Equal(updatedUser.Custom["somePrivateAttr2"], previousUser.Custom["somePrivateAttr2"]); + var uniqueId = "some-unique-key"; + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + client.Identify(userWithEmptyKey); + Assert.Equal(uniqueId, client.User.Key); + Assert.True(client.User.Anonymous); + } } [Fact] - public void UpdatingKeylessUserSetsAnonymousToTrue() - { - var updatedUser = User.WithKey(null); - var client = Client(); - var previousUser = client.User; - client.Identify(updatedUser); - Assert.True(client.User.Anonymous); + public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() + { + var user = User.WithKey("") + .AndSecondaryKey("secondary") + .AndIpAddress("10.0.0.1") + .AndCountry("US") + .AndFirstName("John") + .AndLastName("Doe") + .AndName("John Doe") + .AndAvatar("images.google.com/myAvatar") + .AndEmail("test@example.com") + .AndCustomAttribute("attr", "value"); + var uniqueId = "some-unique-key"; + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + client.Identify(user); + User newUser = client.User; + Assert.NotEqual(user.Key, newUser.Key); + Assert.Equal(user.Avatar, newUser.Avatar); + Assert.Equal(user.Country, newUser.Country); + Assert.Equal(user.Email, newUser.Email); + Assert.Equal(user.FirstName, newUser.FirstName); + Assert.Equal(user.LastName, newUser.LastName); + Assert.Equal(user.Name, newUser.Name); + Assert.Equal(user.IpAddress, newUser.IpAddress); + Assert.Equal(user.SecondaryKey, newUser.SecondaryKey); + Assert.Equal(user.Custom["attr"], newUser.Custom["attr"]); + Assert.True(newUser.Anonymous); + } } - + [Fact] public void CanRegisterListener() { - var client = Client(); - var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(); - client.RegisterFeatureFlagListener("user1-flag", listener); - listenerMgr.FlagWasUpdated("user1-flag", 7); - Assert.Equal(7, listener.FeatureFlags["user1-flag"].ToObject()); + using (var client = Client()) + { + var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; + var listener = new TestListener(); + client.RegisterFeatureFlagListener("user1-flag", listener); + listenerMgr.FlagWasUpdated("user1-flag", 7); + Assert.Equal(7, listener.FeatureFlags["user1-flag"].ToObject()); + } } [Fact] public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerManager() { - var client = Client(); - var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(); - client.RegisterFeatureFlagListener("user2-flag", listener); - listenerMgr.FlagWasUpdated("user2-flag", 7); - Assert.Equal(7, listener.FeatureFlags["user2-flag"]); - - client.UnregisterFeatureFlagListener("user2-flag", listener); - listenerMgr.FlagWasUpdated("user2-flag", 12); - Assert.NotEqual(12, listener.FeatureFlags["user2-flag"]); + using (var client = Client()) + { + var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; + var listener = new TestListener(); + client.RegisterFeatureFlagListener("user2-flag", listener); + listenerMgr.FlagWasUpdated("user2-flag", 7); + Assert.Equal(7, listener.FeatureFlags["user2-flag"]); + + client.UnregisterFeatureFlagListener("user2-flag", listener); + listenerMgr.FlagWasUpdated("user2-flag", 12); + Assert.NotEqual(12, listener.FeatureFlags["user2-flag"]); + } } } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs b/tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs new file mode 100644 index 00000000..83645b1f --- /dev/null +++ b/tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin.Tests +{ + internal class MockConnectionManager : IConnectionManager + { + public Action ConnectionChanged; + + public MockConnectionManager(bool isOnline) + { + isConnected = isOnline; + } + + bool isConnected; + public bool IsConnected + { + get + { + return isConnected; + } + + set + { + isConnected = value; + } + } + + public void Connect(bool online) + { + IsConnected = online; + ConnectionChanged?.Invoke(IsConnected); + } + } + + internal class MockDeviceInfo : IDeviceInfo + { + private readonly string key; + + public MockDeviceInfo(string key) + { + this.key = key; + } + + public string UniqueDeviceId() + { + return key; + } + } + + internal class MockEventProcessor : IEventProcessor + { + public List Events = new List(); + + public void SendEvent(Event e) + { + Events.Add(e); + } + + public void Flush() { } + + public void Dispose() { } + } + + internal class MockFeatureFlagRequestor : IFeatureFlagRequestor + { + private readonly string _jsonFlags; + + public MockFeatureFlagRequestor(string jsonFlags) + { + _jsonFlags = jsonFlags; + } + + public void Dispose() + { + + } + + public Task FeatureFlagsAsync() + { + var response = new WebResponse(200, _jsonFlags, null); + return Task.FromResult(response); + } + } + + internal class MockFlagCacheManager : IFlagCacheManager + { + private readonly IUserFlagCache _flagCache; + + public MockFlagCacheManager(IUserFlagCache flagCache) + { + _flagCache = flagCache; + } + + public void CacheFlagsFromService(IDictionary flags, User user) + { + _flagCache.CacheFlagsForUser(flags, user); + } + + public FeatureFlag FlagForUser(string flagKey, User user) + { + var flags = FlagsForUser(user); + FeatureFlag featureFlag; + if (flags.TryGetValue(flagKey, out featureFlag)) + { + return featureFlag; + } + + return null; + } + + public IDictionary FlagsForUser(User user) + { + return _flagCache.RetrieveFlags(user); + } + + public void RemoveFlagForUser(string flagKey, User user) + { + var flagsForUser = FlagsForUser(user); + flagsForUser.Remove(flagKey); + + CacheFlagsFromService(flagsForUser, user); + } + + public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) + { + var flagsForUser = FlagsForUser(user); + flagsForUser[flagKey] = featureFlag; + + CacheFlagsFromService(flagsForUser, user); + } + } + + internal class MockPersister : ISimplePersistance + { + private IDictionary map = new Dictionary(); + + public string GetValue(string key) + { + if (!map.ContainsKey(key)) + return null; + + return map[key]; + } + + public void Save(string key, string value) + { + map[key] = value; + } + } + + internal class MockPollingProcessor : IMobileUpdateProcessor + { + public bool IsRunning + { + get; + set; + } + + public void Dispose() + { + IsRunning = false; + } + + public bool Initialized() + { + return IsRunning; + } + + public Task Start() + { + IsRunning = true; + return Task.FromResult(true); + } + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockConnectionManager.cs b/tests/LaunchDarkly.Xamarin.Tests/MockConnectionManager.cs deleted file mode 100644 index cb53ed37..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/MockConnectionManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace LaunchDarkly.Xamarin.Tests -{ - internal class MockConnectionManager : IConnectionManager - { - public Action ConnectionChanged; - - public MockConnectionManager(bool isOnline) - { - isConnected = isOnline; - } - - bool isConnected; - public bool IsConnected - { - get - { - return isConnected; - } - - set - { - isConnected = value; - } - } - - public void Connect(bool online) - { - IsConnected = online; - ConnectionChanged?.Invoke(IsConnected); - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs b/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs deleted file mode 100644 index 1f0d3bc5..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/MockEventProcessor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using LaunchDarkly.Client; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class MockEventProcessor : IEventProcessor - { - public List Events = new List(); - - public void SendEvent(Event e) - { - Events.Add(e); - } - - public void Flush() { } - - public void Dispose() { } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs b/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs deleted file mode 100644 index 59b6c964..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/MockFeatureFlagRequestor.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin.Tests -{ - internal class MockFeatureFlagRequestor : IFeatureFlagRequestor - { - private readonly string _jsonFlags; - - public MockFeatureFlagRequestor(string jsonFlags) - { - _jsonFlags = jsonFlags; - } - - public void Dispose() - { - - } - - public Task FeatureFlagsAsync() - { - var response = new WebResponse(200, _jsonFlags, null); - return Task.FromResult(response); - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockFlagCacheManager.cs b/tests/LaunchDarkly.Xamarin.Tests/MockFlagCacheManager.cs deleted file mode 100644 index 936c1f46..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/MockFlagCacheManager.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using LaunchDarkly.Client; - -namespace LaunchDarkly.Xamarin.Tests -{ - internal class MockFlagCacheManager : IFlagCacheManager - { - private readonly IUserFlagCache _flagCache; - - public MockFlagCacheManager(IUserFlagCache flagCache) - { - _flagCache = flagCache; - } - - public void CacheFlagsFromService(IDictionary flags, User user) - { - _flagCache.CacheFlagsForUser(flags, user); - } - - public FeatureFlag FlagForUser(string flagKey, User user) - { - var flags = FlagsForUser(user); - FeatureFlag featureFlag; - if (flags.TryGetValue(flagKey, out featureFlag)) - { - return featureFlag; - } - - return null; - } - - public IDictionary FlagsForUser(User user) - { - return _flagCache.RetrieveFlags(user); - } - - public void RemoveFlagForUser(string flagKey, User user) - { - var flagsForUser = FlagsForUser(user); - flagsForUser.Remove(flagKey); - - CacheFlagsFromService(flagsForUser, user); - } - - public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) - { - var flagsForUser = FlagsForUser(user); - flagsForUser[flagKey] = featureFlag; - - CacheFlagsFromService(flagsForUser, user); - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockPollingProcessor.cs b/tests/LaunchDarkly.Xamarin.Tests/MockPollingProcessor.cs deleted file mode 100644 index 5bbd9105..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/MockPollingProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class MockPollingProcessor : IMobileUpdateProcessor - { - public bool IsRunning - { - get; - set; - } - - public void Dispose() - { - IsRunning = false; - } - - public bool Initialized() - { - return IsRunning; - } - - public Task Start() - { - IsRunning = true; - return Task.FromResult(true); - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs b/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs deleted file mode 100644 index 5622e9d3..00000000 --- a/tests/LaunchDarkly.Xamarin.Tests/StubbedConfigAndUserBuilder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin.Tests -{ - public static class StubbedConfigAndUserBuilder - { - public static User UserWithAllPropertiesFilledIn(string key) - { - var user = User.WithKey(key); - user.SecondaryKey = "secondaryKey"; - user.IpAddress = "10.0.0.1"; - user.Country = "US"; - user.FirstName = "John"; - user.LastName = "Doe"; - user.Name = user.FirstName + " " + user.LastName; - user.Avatar = "images.google.com/myAvatar"; - user.Email = "someEmail@google.com"; - user.Custom = new Dictionary - { - {"somePrivateAttr1", JToken.FromObject("attributeValue1")}, - {"somePrivateAttr2", JToken.FromObject("attributeValue2")}, - }; - - return user; - } - } - - public class MockPersister : ISimplePersistance - { - private IDictionary map = new Dictionary(); - - public string GetValue(string key) - { - if (!map.ContainsKey(key)) - return null; - - return map[key]; - } - - public void Save(string key, string value) - { - map[key] = value; - } - } - - public class MockDeviceInfo : IDeviceInfo - { - public const string key = "someUniqueKey"; - - public string UniqueDeviceId() - { - return key; - } - } -} diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 224c7bb2..b4e20d86 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -44,21 +44,15 @@ public static Configuration ConfigWithFlagsJson(User user, string appKey, string { stubbedFlagCache.CacheFlagsForUser(flags, user); } - - var mockOnlineConnectionManager = new MockConnectionManager(true); - var mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); - var mockPollingProcessor = new MockPollingProcessor(); - var mockPersister = new MockPersister(); - var mockDeviceInfo = new MockDeviceInfo(); - var featureFlagListener = new FeatureFlagListenerManager(); Configuration configuration = Configuration.Default(appKey) - .WithFlagCacheManager(mockFlagCacheManager) - .WithConnectionManager(mockOnlineConnectionManager) - .WithUpdateProcessor(mockPollingProcessor) - .WithPersister(mockPersister) - .WithDeviceInfo(mockDeviceInfo) - .WithFeatureFlagListenerManager(featureFlagListener); + .WithFlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) + .WithConnectionManager(new MockConnectionManager(true)) + .WithEventProcessor(new MockEventProcessor()) + .WithUpdateProcessor(new MockPollingProcessor()) + .WithPersister(new MockPersister()) + .WithDeviceInfo(new MockDeviceInfo("")) + .WithFeatureFlagListenerManager(new FeatureFlagListenerManager()); return configuration; } } From c0cd164a9250dcd0049a2d205fbaa951c20a8ee8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 23 Jul 2018 16:42:53 -0700 Subject: [PATCH 026/499] make timeout a parameter instead of a config property --- src/LaunchDarkly.Xamarin/Configuration.cs | 25 ------- src/LaunchDarkly.Xamarin/ILdMobileClient.cs | 6 +- src/LaunchDarkly.Xamarin/LdClient.cs | 65 +++++++------------ .../LdClientTests.cs | 6 +- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 5 +- 5 files changed, 32 insertions(+), 75 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index 8a584731..6c777ff5 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -60,12 +60,6 @@ public class Configuration : IMobileConfiguration /// public TimeSpan PollingInterval { get; internal set; } /// - /// How long the client constructor will block awaiting a successful connection to - /// LaunchDarkly. Setting this to 0 will not block and will cause the constructor to return - /// immediately. The default value is 5 seconds. - /// - public TimeSpan StartWaitTime { get; internal set; } - /// /// The timeout when reading data from the EventSource API. The default value is 5 minutes. /// public TimeSpan ReadTimeout { get; internal set; } @@ -157,10 +151,6 @@ public class Configuration : IMobileConfiguration /// private static readonly TimeSpan DefaultEventQueueFrequency = TimeSpan.FromSeconds(5); /// - /// Default value for . - /// - private static readonly TimeSpan DefaultStartWaitTime = TimeSpan.FromSeconds(5); - /// /// Default value for . /// private static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); @@ -205,7 +195,6 @@ public static Configuration Default(string mobileKey) EventQueueCapacity = DefaultEventQueueCapacity, EventQueueFrequency = DefaultEventQueueFrequency, PollingInterval = DefaultPollingInterval, - StartWaitTime = DefaultStartWaitTime, ReadTimeout = DefaultReadTimeout, ReconnectTime = DefaultReconnectTime, HttpClientTimeout = DefaultHttpClientTimeout, @@ -382,20 +371,6 @@ public static Configuration WithPollingInterval(this Configuration configuration return configuration; } - /// - /// Sets how long the client constructor will block awaiting a successful connection to - /// LaunchDarkly. Setting this to 0 will not block and will cause the constructor to return - /// immediately. The default value is 5 seconds. - /// - /// the configuration - /// the length of time to wait - /// the same Configuration instance - public static Configuration WithStartWaitTime(this Configuration configuration, TimeSpan startWaitTime) - { - configuration.StartWaitTime = startWaitTime; - return configuration; - } - /// /// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made. /// diff --git a/src/LaunchDarkly.Xamarin/ILdMobileClient.cs b/src/LaunchDarkly.Xamarin/ILdMobileClient.cs index faac2ecc..39e7b26d 100644 --- a/src/LaunchDarkly.Xamarin/ILdMobileClient.cs +++ b/src/LaunchDarkly.Xamarin/ILdMobileClient.cs @@ -75,8 +75,10 @@ public interface ILdMobileClient : ILdCommonClient /// /// Gets or sets the online status of the client. /// - /// The setter will block and wait on the current thread for the Update processor - /// to either be stopped or started. + /// The setter is equivalent to calling . If you are going from offline + /// to online, and you want to wait until the connection has been established, call + /// and then use await or call Wait() on + /// its return value. /// /// true if online; otherwise, false. bool Online { get; set; } diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index f55c443d..59de7ce4 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -99,8 +99,8 @@ public sealed class LdClient : ILdMobileClient /// fetching feature flags. /// /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, if you would rather this happen - /// in an async fashion you can use . + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more specific /// to instantiate the single instance of LdClient @@ -110,11 +110,14 @@ public sealed class LdClient : ILdMobileClient /// The mobile key given to you by LaunchDarkly. /// The user needed for client operations. Must not be null. /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static LdClient Init(string mobileKey, User user) + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) { var config = Configuration.Default(mobileKey); - return Init(config, user); + return Init(config, user, maxWaitTime); } /// @@ -143,8 +146,8 @@ public static async Task InitAsync(string mobileKey, User user) /// fetching Feature Flags. /// /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, if you would rather this happen - /// in an async fashion you can use . + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more basic /// to instantiate the single instance of LdClient @@ -154,13 +157,20 @@ public static async Task InitAsync(string mobileKey, User user) /// The client configuration object /// The user needed for client operations. Must not be null. /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static LdClient Init(Configuration config, User user) + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) { CreateInstance(config, user); if (Instance.Online) { - Instance.StartUpdateProcessor(); + if (!Instance.StartUpdateProcessor(maxWaitTime)) + { + Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", + maxWaitTime.TotalMilliseconds); + } } return Instance; @@ -207,10 +217,10 @@ static void CreateInstance(Configuration configuration, User user) Instance.Version); } - void StartUpdateProcessor() + bool StartUpdateProcessor(TimeSpan maxWaitTime) { var initTask = updateProcessor.Start(); - var unused = initTask.Wait(Config.StartWaitTime); + return initTask.Wait(maxWaitTime); } Task StartUpdateProcessorAsync() @@ -233,13 +243,10 @@ void SetupConnectionManager() /// public bool Online { - get - { - return online; - } + get => online; set { - SetOnlineAsync(value).Wait(); + var doNotAwaitResult = SetOnlineAsync(value); } } @@ -451,12 +458,6 @@ public async Task IdentifyAsync(User user) eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); } - void RestartUpdateProcessor() - { - ClearAndSetUpdateProcessor(); - StartUpdateProcessor(); - } - async Task RestartUpdateProcessorAsync() { ClearAndSetUpdateProcessor(); @@ -525,22 +526,6 @@ public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener l flagListenerManager.UnregisterListener(listener, flagKey); } - internal void EnterBackground() - { - // if using Streaming, processor needs to be reset - if (Config.IsStreamingEnabled) - { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - RestartUpdateProcessor(); - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); - } - else - { - PingPollingProcessor(); - } - } - internal async Task EnterBackgroundAsync() { // if using Streaming, processor needs to be reset @@ -557,12 +542,6 @@ internal async Task EnterBackgroundAsync() } } - internal void EnterForeground() - { - ResetProcessorForForeground(); - RestartUpdateProcessor(); - } - internal async Task EnterForegroundAsync() { ResetProcessorForForeground(); diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 69c7dacf..7c4ea07d 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -24,14 +24,14 @@ public void CanCreateClientWithConfigAndUser() [Fact] public void CannotCreateClientWithNullConfig() { - Assert.Throws(() => LdClient.Init((Configuration)null, User.WithKey("user"))); + Assert.Throws(() => LdClient.Init((Configuration)null, User.WithKey("user"), TimeSpan.Zero)); } [Fact] public void CannotCreateClientWithNullUser() { Configuration config = TestUtil.ConfigWithFlagsJson(User.WithKey("dummy"), appKey, "{}"); - Assert.Throws(() => LdClient.Init(config, null)); + Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); } [Fact] @@ -65,7 +65,7 @@ public void SharedClientIsTheOnlyClientAvailable() { User user = StubbedConfigAndUserBuilder.UserWithAllPropertiesFilledIn("user1Key"); var config = TestUtil.ConfigWithFlagsJson(user, appKey, "{}"); - var client = LdClient.Init(config, user); + var client = LdClient.Init(config, user, TimeSpan.Zero); try { Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, User.WithKey("otherUserKey"))); diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 224c7bb2..af8e9005 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using LaunchDarkly.Client; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -18,7 +19,7 @@ public static LdClient CreateClient(Configuration config, User user) { lock (ClientInstanceLock) { - LdClient client = LdClient.Init(config, user); + LdClient client = LdClient.Init(config, user, TimeSpan.Zero); LdClient.Instance = null; return client; } From 5305820b0f179aba2b7e52994b0b282d79883481 Mon Sep 17 00:00:00 2001 From: Andrew Shannon Brown Date: Mon, 23 Jul 2018 16:47:54 -0700 Subject: [PATCH 027/499] Remove @ashanbrown from codeowners --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 44429ee1..8b137891 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @ashanbrown + From 77816f2e1fb4ee54ce462957982d6037a275616f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 24 Jul 2018 11:38:51 -0700 Subject: [PATCH 028/499] propagate exception if polling task fails permanently --- src/LaunchDarkly.Xamarin/LdClient.cs | 27 +++++++++++++++++-- .../MobilePollingProcessor.cs | 10 ++++--- .../LdClientTests.cs | 2 +- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 1b76f165..2ee0ff77 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -220,7 +220,14 @@ static void CreateInstance(Configuration configuration, User user) bool StartUpdateProcessor(TimeSpan maxWaitTime) { var initTask = updateProcessor.Start(); - return initTask.Wait(maxWaitTime); + try + { + return initTask.Wait(maxWaitTime); + } + catch (AggregateException e) + { + throw UnwrapAggregateException(e); + } } Task StartUpdateProcessorAsync() @@ -427,7 +434,14 @@ public void Flush() /// public void Identify(User user) { - IdentifyAsync(user).Wait(); + try + { + IdentifyAsync(user).Wait(); + } + catch (AggregateException e) + { + throw UnwrapAggregateException(e); + } } /// @@ -587,5 +601,14 @@ async Task PingPollingProcessorAsync() await pollingProcessor.PingAndWait(); } } + + private Exception UnwrapAggregateException(AggregateException e) + { + if (e.InnerExceptions.Count == 1) + { + return e.InnerExceptions[0]; + } + return e; + } } } \ No newline at end of file diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs index 65f13572..b36e341c 100644 --- a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs @@ -19,8 +19,8 @@ internal class MobilePollingProcessor : IMobileUpdateProcessor private readonly TimeSpan pollingInterval; private readonly TaskCompletionSource _startTask; private readonly TaskCompletionSource _stopTask; - private static int UNINITIALIZED = 0; - private static int INITIALIZED = 1; + private const int UNINITIALIZED = 0; + private const int INITIALIZED = 1; private int _initialized = UNINITIALIZED; private volatile bool _disposed; @@ -80,7 +80,7 @@ private async Task UpdateTaskAsync() _flagCacheManager.CacheFlagsFromService(flagsDictionary, user); //We can't use bool in CompareExchange because it is not a reference type. - if (Interlocked.CompareExchange(ref _initialized, INITIALIZED, UNINITIALIZED) == 0) + if (Interlocked.CompareExchange(ref _initialized, INITIALIZED, UNINITIALIZED) == UNINITIALIZED) { _startTask.SetResult(true); Log.Info("Initialized LaunchDarkly Polling Processor."); @@ -91,6 +91,10 @@ private async Task UpdateTaskAsync() { Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(ex)); Log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); + if (_initialized == UNINITIALIZED) + { + _startTask.SetException(ex); + } ((IDisposable)this).Dispose(); } catch (Exception ex) diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index f164bcd9..e9cb9d63 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -44,7 +44,7 @@ public void IdentifyWithNullUserThrowsException() { using (var client = Client()) { - Assert.Throws(() => client.Identify(null)); + Assert.Throws(() => client.Identify(null)); } } From 05ae673a930ce3ecaa32421dacbe3bf8a09bd568 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 24 Jul 2018 13:39:16 -0700 Subject: [PATCH 029/499] validate maxWaitTime --- src/LaunchDarkly.Xamarin/LdClient.cs | 5 +++++ .../LdClientTests.cs | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 1b76f165..d32d055e 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -162,6 +162,11 @@ public static async Task InitAsync(string mobileKey, User user) /// an uninitialized state. public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) { + if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); + } + CreateInstance(config, user); if (Instance.Online) diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index f164bcd9..85ba446f 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -28,6 +28,27 @@ public void CannotCreateClientWithNullUser() Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); } + [Fact] + public void CannotCreateClientWithNegativeWaitTime() + { + Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.FromMilliseconds(-2))); + } + + [Fact] + public void CanCreateClientWithInfiniteWaitTime() + { + Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + try + { + using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } + } + finally + { + LdClient.Instance = null; + } + } + [Fact] public void IdentifyUpdatesTheUser() { From f2ac11e38f0ddafbe54fef641c2a291c56c27deb Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 24 Jul 2018 13:51:41 -0700 Subject: [PATCH 030/499] send an initial identify event when client is created --- src/LaunchDarkly.Xamarin/LdClient.cs | 1152 +++++++++-------- .../LdClientEventTests.cs | 109 +- 2 files changed, 636 insertions(+), 625 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index d32d055e..323c5b3b 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -1,596 +1,598 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - /// - /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate - /// a single LdClient for the lifetime of their application. - /// - public sealed class LdClient : ILdMobileClient - { - private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); - - /// - /// The singleton instance used by your application throughout its lifetime, can only be created once. - /// - /// Use the designated static method - /// to set this LdClient instance. - /// - /// The LdClient instance. - public static LdClient Instance { get; internal set; } - - /// - /// The Configuration instance used to setup the LdClient. - /// - /// The Configuration instance. - public Configuration Config { get; private set; } - - /// - /// The User for the LdClient operations. - /// - /// The User. - public User User { get; private set; } - - object myLockObjForConnectionChange = new object(); - object myLockObjForUserUpdate = new object(); - - IFlagCacheManager flagCacheManager; - IConnectionManager connectionManager; - IMobileUpdateProcessor updateProcessor; - IEventProcessor eventProcessor; - ISimplePersistance persister; - IDeviceInfo deviceInfo; - EventFactory eventFactory = EventFactory.Default; - IFeatureFlagListenerManager flagListenerManager; - - SemaphoreSlim connectionLock; - - // private constructor prevents initialization of this class - // without using WithConfigAnduser(config, user) - LdClient() { } - - LdClient(Configuration configuration, User user) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common.Logging; +using LaunchDarkly.Client; +using LaunchDarkly.Common; +using Newtonsoft.Json.Linq; + +namespace LaunchDarkly.Xamarin +{ + /// + /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate + /// a single LdClient for the lifetime of their application. + /// + public sealed class LdClient : ILdMobileClient + { + private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); + + /// + /// The singleton instance used by your application throughout its lifetime, can only be created once. + /// + /// Use the designated static method + /// to set this LdClient instance. + /// + /// The LdClient instance. + public static LdClient Instance { get; internal set; } + + /// + /// The Configuration instance used to setup the LdClient. + /// + /// The Configuration instance. + public Configuration Config { get; private set; } + + /// + /// The User for the LdClient operations. + /// + /// The User. + public User User { get; private set; } + + object myLockObjForConnectionChange = new object(); + object myLockObjForUserUpdate = new object(); + + IFlagCacheManager flagCacheManager; + IConnectionManager connectionManager; + IMobileUpdateProcessor updateProcessor; + IEventProcessor eventProcessor; + ISimplePersistance persister; + IDeviceInfo deviceInfo; + EventFactory eventFactory = EventFactory.Default; + IFeatureFlagListenerManager flagListenerManager; + + SemaphoreSlim connectionLock; + + // private constructor prevents initialization of this class + // without using WithConfigAnduser(config, user) + LdClient() { } + + LdClient(Configuration configuration, User user) { if (configuration == null) { throw new ArgumentNullException("configuration"); - } + } if (user == null) { throw new ArgumentNullException("user"); - } - - Config = configuration; - - connectionLock = new SemaphoreSlim(1, 1); - - persister = Factory.CreatePersister(configuration); - deviceInfo = Factory.CreateDeviceInfo(configuration); - flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); - - // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) - { - User = UserWithUniqueKey(user); - } - else - { - User = user; - } - - flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); - connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager); - eventProcessor = Factory.CreateEventProcessor(configuration); - - SetupConnectionManager(); - } - - /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. - public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) - { - var config = Configuration.Default(mobileKey); - - return Init(config, user, maxWaitTime); - } - - /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. This constructor should be used if you do not want to wait - /// for the client to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static async Task InitAsync(string mobileKey, User user) - { - var config = Configuration.Default(mobileKey); - - return await InitAsync(config, user); - } - - /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for - /// fetching Feature Flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The client configuration object - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. - public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) - { + } + + Config = configuration; + + connectionLock = new SemaphoreSlim(1, 1); + + persister = Factory.CreatePersister(configuration); + deviceInfo = Factory.CreateDeviceInfo(configuration); + flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); + + // If you pass in a user with a null or blank key, one will be assigned to them. + if (String.IsNullOrEmpty(user.Key)) + { + User = UserWithUniqueKey(user); + } + else + { + User = user; + } + + flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); + connectionManager = Factory.CreateConnectionManager(configuration); + updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager); + eventProcessor = Factory.CreateEventProcessor(configuration); + + eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(User)); + + SetupConnectionManager(); + } + + /// + /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// fetching feature flags. + /// + /// This constructor will wait and block on the current thread until initialization and the + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . + /// + /// This is the creation point for LdClient, you must use this static method or the more specific + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The mobile key given to you by LaunchDarkly. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) + { + var config = Configuration.Default(mobileKey); + + return Init(config, user, maxWaitTime); + } + + /// + /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// fetching feature flags. This constructor should be used if you do not want to wait + /// for the client to finish initializing and receive the first response + /// from the LaunchDarkly service. + /// + /// This is the creation point for LdClient, you must use this static method or the more specific + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The mobile key given to you by LaunchDarkly. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + public static async Task InitAsync(string mobileKey, User user) + { + var config = Configuration.Default(mobileKey); + + return await InitAsync(config, user); + } + + /// + /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// fetching Feature Flags. + /// + /// This constructor will wait and block on the current thread until initialization and the + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . + /// + /// This is the creation point for LdClient, you must use this static method or the more basic + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The client configuration object + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) + { if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) { throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); - } - - CreateInstance(config, user); - - if (Instance.Online) - { + } + + CreateInstance(config, user); + + if (Instance.Online) + { if (!Instance.StartUpdateProcessor(maxWaitTime)) { Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", maxWaitTime.TotalMilliseconds); - } - } - - return Instance; - } - - /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for - /// fetching Feature Flags. This constructor should be used if you do not want to wait - /// for the IUpdateProcessor instance to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The client configuration object - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static Task InitAsync(Configuration config, User user) - { - CreateInstance(config, user); - - if (Instance.Online) - { - Task t = Instance.StartUpdateProcessorAsync(); - return t.ContinueWith((result) => Instance); - } - else - { - return Task.FromResult(Instance); - } - } - - static void CreateInstance(Configuration configuration, User user) - { + } + } + + return Instance; + } + + /// + /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// fetching Feature Flags. This constructor should be used if you do not want to wait + /// for the IUpdateProcessor instance to finish initializing and receive the first response + /// from the LaunchDarkly service. + /// + /// This is the creation point for LdClient, you must use this static method or the more basic + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The client configuration object + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + public static Task InitAsync(Configuration config, User user) + { + CreateInstance(config, user); + + if (Instance.Online) + { + Task t = Instance.StartUpdateProcessorAsync(); + return t.ContinueWith((result) => Instance); + } + else + { + return Task.FromResult(Instance); + } + } + + static void CreateInstance(Configuration configuration, User user) + { if (Instance != null) - { + { throw new Exception("LdClient instance already exists."); - } - - Instance = new LdClient(configuration, user); - Log.InfoFormat("Initialized LaunchDarkly Client {0}", - Instance.Version); - } - - bool StartUpdateProcessor(TimeSpan maxWaitTime) - { - var initTask = updateProcessor.Start(); - return initTask.Wait(maxWaitTime); - } - - Task StartUpdateProcessorAsync() - { - return updateProcessor.Start(); - } - - void SetupConnectionManager() - { - if (connectionManager is MobileConnectionManager mobileConnectionManager) - { - mobileConnectionManager.ConnectionChanged += MobileConnectionManager_ConnectionChanged; - Log.InfoFormat("The mobile client connection changed online to {0}", - connectionManager.IsConnected); - } - online = connectionManager.IsConnected; - } - - bool online; - /// - public bool Online - { - get => online; - set - { - var doNotAwaitResult = SetOnlineAsync(value); - } - } - - public async Task SetOnlineAsync(bool value) - { - await connectionLock.WaitAsync(); - online = value; - try - { - if (online) - { - await RestartUpdateProcessorAsync(); - } - else - { - ClearUpdateProcessor(); - } - } - finally - { - connectionLock.Release(); - } - - return; - } - - void MobileConnectionManager_ConnectionChanged(bool isOnline) - { - Online = isOnline; - } - - /// - public bool BoolVariation(string key, bool defaultValue = false) - { - return VariationWithType(key, defaultValue, JTokenType.Boolean).Value(); - } - - /// - public string StringVariation(string key, string defaultValue) - { - var value = VariationWithType(key, defaultValue, JTokenType.String); - if (value != null) - { - return value.Value(); - } - - return null; - } - - /// - public float FloatVariation(string key, float defaultValue = 0) - { - return VariationWithType(key, defaultValue, JTokenType.Float).Value(); - } - - /// - public int IntVariation(string key, int defaultValue = 0) - { - return VariationWithType(key, defaultValue, JTokenType.Integer).Value(); - } - - /// - public JToken JsonVariation(string key, JToken defaultValue) - { - return VariationWithType(key, defaultValue, null); - } - - JToken VariationWithType(string featureKey, JToken defaultValue, JTokenType? jtokenType) - { - var returnedFlagValue = Variation(featureKey, defaultValue); - if (returnedFlagValue != null && jtokenType != null && !returnedFlagValue.Type.Equals(jtokenType)) - { - Log.ErrorFormat("Expected type: {0} but got {1} when evaluating FeatureFlag: {2}. Returning default", - jtokenType, - returnedFlagValue.Type, - featureKey); - - return defaultValue; - } - - return returnedFlagValue; - } - - JToken Variation(string featureKey, JToken defaultValue) - { - FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); - FeatureRequestEvent featureRequestEvent; - - if (!Initialized()) - { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); - return defaultValue; - } - - var flag = flagCacheManager.FlagForUser(featureKey, User); - if (flag != null) - { - featureFlagEvent = new FeatureFlagEvent(featureKey, flag); - var value = flag.value; - if (value == null || value.Type == JTokenType.Null) { - featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, - User, - defaultValue); - value = defaultValue; - } else { - featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, - User, - flag.variation, - flag.value, - defaultValue); - } - eventProcessor.SendEvent(featureRequestEvent); - return value; - } - - Log.InfoFormat("Unknown feature flag {0}; returning default value", - featureKey); - featureRequestEvent = eventFactory.NewUnknownFeatureRequestEvent(featureKey, - User, - defaultValue); - eventProcessor.SendEvent(featureRequestEvent); - return defaultValue; - } - - /// - public IDictionary AllFlags() - { - if (IsOffline()) - { - Log.Warn("AllFlags() was called when client is in offline mode. Returning null."); - return null; - } - if (!Initialized()) - { - Log.Warn("AllFlags() was called before client has finished initializing. Returning null."); - return null; - } - - return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => p.Value.value); - } - - /// - public void Track(string eventName, JToken data) - { - eventProcessor.SendEvent(eventFactory.NewCustomEvent(eventName, User, data)); - } - - /// - public void Track(string eventName) - { - Track(eventName, null); - } - - /// - public bool Initialized() - { - //bool isInited = Instance != null; - //return isInited && Online; - // TODO: This method needs to be fixed to actually check whether the update processor has initialized. - // The previous logic (above) was meaningless because this method is not static, so by definition you - // do have a client instance if we've gotten here. But that doesn't mean it is initialized. - return Online; - } - - /// - public bool IsOffline() - { - return !online; - } - - /// - public void Flush() - { - eventProcessor.Flush(); - } - - /// - public void Identify(User user) - { - IdentifyAsync(user).Wait(); - } - - /// - public async Task IdentifyAsync(User user) - { - if (user == null) - { - throw new ArgumentNullException("user"); - } - - User userWithKey = user; - if (String.IsNullOrEmpty(user.Key)) - { - userWithKey = UserWithUniqueKey(user); - } - - await connectionLock.WaitAsync(); - try - { - User = userWithKey; - await RestartUpdateProcessorAsync(); - } - finally - { - connectionLock.Release(); - } - - eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); - } - - async Task RestartUpdateProcessorAsync() - { - ClearAndSetUpdateProcessor(); - await StartUpdateProcessorAsync(); - } - - void ClearAndSetUpdateProcessor() - { - ClearUpdateProcessor(); - updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager); - } - - void ClearUpdateProcessor() - { - if (updateProcessor != null) - { - updateProcessor.Dispose(); - updateProcessor = new NullUpdateProcessor(); - } - } - - User UserWithUniqueKey(User user) - { - string uniqueId = deviceInfo.UniqueDeviceId(); + } + + Instance = new LdClient(configuration, user); + Log.InfoFormat("Initialized LaunchDarkly Client {0}", + Instance.Version); + } + + bool StartUpdateProcessor(TimeSpan maxWaitTime) + { + var initTask = updateProcessor.Start(); + return initTask.Wait(maxWaitTime); + } + + Task StartUpdateProcessorAsync() + { + return updateProcessor.Start(); + } + + void SetupConnectionManager() + { + if (connectionManager is MobileConnectionManager mobileConnectionManager) + { + mobileConnectionManager.ConnectionChanged += MobileConnectionManager_ConnectionChanged; + Log.InfoFormat("The mobile client connection changed online to {0}", + connectionManager.IsConnected); + } + online = connectionManager.IsConnected; + } + + bool online; + /// + public bool Online + { + get => online; + set + { + var doNotAwaitResult = SetOnlineAsync(value); + } + } + + public async Task SetOnlineAsync(bool value) + { + await connectionLock.WaitAsync(); + online = value; + try + { + if (online) + { + await RestartUpdateProcessorAsync(); + } + else + { + ClearUpdateProcessor(); + } + } + finally + { + connectionLock.Release(); + } + + return; + } + + void MobileConnectionManager_ConnectionChanged(bool isOnline) + { + Online = isOnline; + } + + /// + public bool BoolVariation(string key, bool defaultValue = false) + { + return VariationWithType(key, defaultValue, JTokenType.Boolean).Value(); + } + + /// + public string StringVariation(string key, string defaultValue) + { + var value = VariationWithType(key, defaultValue, JTokenType.String); + if (value != null) + { + return value.Value(); + } + + return null; + } + + /// + public float FloatVariation(string key, float defaultValue = 0) + { + return VariationWithType(key, defaultValue, JTokenType.Float).Value(); + } + + /// + public int IntVariation(string key, int defaultValue = 0) + { + return VariationWithType(key, defaultValue, JTokenType.Integer).Value(); + } + + /// + public JToken JsonVariation(string key, JToken defaultValue) + { + return VariationWithType(key, defaultValue, null); + } + + JToken VariationWithType(string featureKey, JToken defaultValue, JTokenType? jtokenType) + { + var returnedFlagValue = Variation(featureKey, defaultValue); + if (returnedFlagValue != null && jtokenType != null && !returnedFlagValue.Type.Equals(jtokenType)) + { + Log.ErrorFormat("Expected type: {0} but got {1} when evaluating FeatureFlag: {2}. Returning default", + jtokenType, + returnedFlagValue.Type, + featureKey); + + return defaultValue; + } + + return returnedFlagValue; + } + + JToken Variation(string featureKey, JToken defaultValue) + { + FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); + FeatureRequestEvent featureRequestEvent; + + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); + return defaultValue; + } + + var flag = flagCacheManager.FlagForUser(featureKey, User); + if (flag != null) + { + featureFlagEvent = new FeatureFlagEvent(featureKey, flag); + var value = flag.value; + if (value == null || value.Type == JTokenType.Null) { + featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, + User, + defaultValue); + value = defaultValue; + } else { + featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, + User, + flag.variation, + flag.value, + defaultValue); + } + eventProcessor.SendEvent(featureRequestEvent); + return value; + } + + Log.InfoFormat("Unknown feature flag {0}; returning default value", + featureKey); + featureRequestEvent = eventFactory.NewUnknownFeatureRequestEvent(featureKey, + User, + defaultValue); + eventProcessor.SendEvent(featureRequestEvent); + return defaultValue; + } + + /// + public IDictionary AllFlags() + { + if (IsOffline()) + { + Log.Warn("AllFlags() was called when client is in offline mode. Returning null."); + return null; + } + if (!Initialized()) + { + Log.Warn("AllFlags() was called before client has finished initializing. Returning null."); + return null; + } + + return flagCacheManager.FlagsForUser(User) + .ToDictionary(p => p.Key, p => p.Value.value); + } + + /// + public void Track(string eventName, JToken data) + { + eventProcessor.SendEvent(eventFactory.NewCustomEvent(eventName, User, data)); + } + + /// + public void Track(string eventName) + { + Track(eventName, null); + } + + /// + public bool Initialized() + { + //bool isInited = Instance != null; + //return isInited && Online; + // TODO: This method needs to be fixed to actually check whether the update processor has initialized. + // The previous logic (above) was meaningless because this method is not static, so by definition you + // do have a client instance if we've gotten here. But that doesn't mean it is initialized. + return Online; + } + + /// + public bool IsOffline() + { + return !online; + } + + /// + public void Flush() + { + eventProcessor.Flush(); + } + + /// + public void Identify(User user) + { + IdentifyAsync(user).Wait(); + } + + /// + public async Task IdentifyAsync(User user) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + User userWithKey = user; + if (String.IsNullOrEmpty(user.Key)) + { + userWithKey = UserWithUniqueKey(user); + } + + await connectionLock.WaitAsync(); + try + { + User = userWithKey; + await RestartUpdateProcessorAsync(); + } + finally + { + connectionLock.Release(); + } + + eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); + } + + async Task RestartUpdateProcessorAsync() + { + ClearAndSetUpdateProcessor(); + await StartUpdateProcessorAsync(); + } + + void ClearAndSetUpdateProcessor() + { + ClearUpdateProcessor(); + updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager); + } + + void ClearUpdateProcessor() + { + if (updateProcessor != null) + { + updateProcessor.Dispose(); + updateProcessor = new NullUpdateProcessor(); + } + } + + User UserWithUniqueKey(User user) + { + string uniqueId = deviceInfo.UniqueDeviceId(); return new User(user) { Key = uniqueId, Anonymous = true - }; - } - - void IDisposable.Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - void Dispose(bool disposing) - { - if (disposing) - { - Log.InfoFormat("The mobile client is being disposed"); - updateProcessor.Dispose(); - eventProcessor.Dispose(); - } - } - - /// - public Version Version - { - get - { - return MobileClientEnvironment.Instance.Version; - } - } - - /// - public void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) - { - flagListenerManager.RegisterListener(listener, flagKey); - } - - /// - public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) - { - flagListenerManager.UnregisterListener(listener, flagKey); - } - - internal async Task EnterBackgroundAsync() - { - // if using Streaming, processor needs to be reset - if (Config.IsStreamingEnabled) - { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - await RestartUpdateProcessorAsync(); - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); - } - else - { - await PingPollingProcessorAsync(); - } - } - - internal async Task EnterForegroundAsync() - { - ResetProcessorForForeground(); - await RestartUpdateProcessorAsync(); - } - - void ResetProcessorForForeground() - { - string didBackground = persister.GetValue(Constants.BACKGROUNDED_WHILE_STREAMING); - if (didBackground.Equals("true")) - { - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "false"); - ClearUpdateProcessor(); - Config.IsStreamingEnabled = true; - } - } - - internal void BackgroundTick() - { - PingPollingProcessor(); - } - - internal async Task BackgroundTickAsync() - { - await PingPollingProcessorAsync(); - } - - void PingPollingProcessor() - { - var pollingProcessor = updateProcessor as MobilePollingProcessor; - if (pollingProcessor != null) - { - var waitTask = pollingProcessor.PingAndWait(); - waitTask.Wait(); - } - } - - async Task PingPollingProcessorAsync() - { - var pollingProcessor = updateProcessor as MobilePollingProcessor; - if (pollingProcessor != null) - { - await pollingProcessor.PingAndWait(); - } - } - } + }; + } + + void IDisposable.Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposing) + { + Log.InfoFormat("The mobile client is being disposed"); + updateProcessor.Dispose(); + eventProcessor.Dispose(); + } + } + + /// + public Version Version + { + get + { + return MobileClientEnvironment.Instance.Version; + } + } + + /// + public void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) + { + flagListenerManager.RegisterListener(listener, flagKey); + } + + /// + public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) + { + flagListenerManager.UnregisterListener(listener, flagKey); + } + + internal async Task EnterBackgroundAsync() + { + // if using Streaming, processor needs to be reset + if (Config.IsStreamingEnabled) + { + ClearUpdateProcessor(); + Config.IsStreamingEnabled = false; + await RestartUpdateProcessorAsync(); + persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); + } + else + { + await PingPollingProcessorAsync(); + } + } + + internal async Task EnterForegroundAsync() + { + ResetProcessorForForeground(); + await RestartUpdateProcessorAsync(); + } + + void ResetProcessorForForeground() + { + string didBackground = persister.GetValue(Constants.BACKGROUNDED_WHILE_STREAMING); + if (didBackground.Equals("true")) + { + persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "false"); + ClearUpdateProcessor(); + Config.IsStreamingEnabled = true; + } + } + + internal void BackgroundTick() + { + PingPollingProcessor(); + } + + internal async Task BackgroundTickAsync() + { + await PingPollingProcessorAsync(); + } + + void PingPollingProcessor() + { + var pollingProcessor = updateProcessor as MobilePollingProcessor; + if (pollingProcessor != null) + { + var waitTask = pollingProcessor.PingAndWait(); + waitTask.Wait(); + } + } + + async Task PingPollingProcessorAsync() + { + var pollingProcessor = updateProcessor as MobilePollingProcessor; + if (pollingProcessor != null) + { + await pollingProcessor.PingAndWait(); + } + } + } } \ No newline at end of file diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs index 5e8c58ce..074605e4 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs @@ -23,11 +23,9 @@ public void IdentifySendsIdentifyEvent() { User user1 = User.WithKey("userkey1"); client.Identify(user1); - Assert.Collection(eventProcessor.Events, e => - { - IdentifyEvent ie = Assert.IsType(e); - Assert.Equal(user1.Key, ie.User.Key); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), // there's always an initial identify event + e => CheckIdentifyEvent(e, user1)); } } @@ -38,13 +36,14 @@ public void TrackSendsCustomEvent() { JToken data = new JValue("hi"); client.Track("eventkey", data); - Assert.Collection(eventProcessor.Events, e => - { - CustomEvent ce = Assert.IsType(e); - Assert.Equal("eventkey", ce.Key); - Assert.Equal(user.Key, ce.User.Key); - Assert.Equal(data, ce.JsonData); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + CustomEvent ce = Assert.IsType(e); + Assert.Equal("eventkey", ce.Key); + Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(data, ce.JsonData); + }); } } @@ -58,17 +57,18 @@ public void VariationSendsFeatureEventForValidFlag() { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); - Assert.Collection(eventProcessor.Events, e => - { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); - Assert.Equal(1, fe.Variation); - Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); - Assert.True(fe.TrackEvents); - Assert.Equal(2000, fe.DebugEventsUntilDate); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + Assert.True(fe.TrackEvents); + Assert.Equal(2000, fe.DebugEventsUntilDate); + }); } } @@ -82,15 +82,16 @@ public void FeatureEventUsesFlagVersionIfProvided() { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); - Assert.Collection(eventProcessor.Events, e => - { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); - Assert.Equal(1, fe.Variation); - Assert.Equal(1500, fe.Version); - Assert.Equal("b", fe.Default); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1500, fe.Version); + Assert.Equal("b", fe.Default); + }); } } @@ -103,15 +104,16 @@ public void VariationSendsFeatureEventForDefaultValue() { string result = client.StringVariation("flag", "b"); Assert.Equal("b", result); - Assert.Collection(eventProcessor.Events, e => - { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); - Assert.Null(fe.Variation); - Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + }); } } @@ -122,16 +124,23 @@ public void VariationSendsFeatureEventForUnknownFlag() { string result = client.StringVariation("flag", "b"); Assert.Equal("b", result); - Assert.Collection(eventProcessor.Events, e => - { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); - Assert.Null(fe.Variation); - Assert.Null(fe.Version); - Assert.Equal("b", fe.Default); - }); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Null(fe.Version); + Assert.Equal("b", fe.Default); + }); } } + + private void CheckIdentifyEvent(Event e, User u) + { + IdentifyEvent ie = Assert.IsType(e); + Assert.Equal(u.Key, ie.User.Key); + } } } From 3f2d0c090fd0c68cbd853fc66261be93474f274c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 24 Jul 2018 17:06:18 -0700 Subject: [PATCH 031/499] misc project file & test cleanup --- .../LaunchDarkly.Xamarin.csproj | 24 +- .../MobileStreamingProcessorTests.cs | 414 +++++++++--------- 2 files changed, 223 insertions(+), 215 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 6b163c85..2d7142ff 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -2,25 +2,31 @@ 1.0.0-beta11 - netstandard1.6;netstandard2.0;net45 + Library + LaunchDarkly.Xamarin + LaunchDarkly.Xamarin - - - false + + + netstandard1.6;netstandard2.0;net45 - - obj\Release\netstandard2.0 - - + + netstandard1.6;netstandard2.0 + - + + + + + + diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs index 83f99c41..fcb0e08e 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs @@ -1,211 +1,213 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using LaunchDarkly.Client; -using LaunchDarkly.Common; -using LaunchDarkly.EventSource; -using Newtonsoft.Json; -using Xunit; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class MobileStreamingProcessorTests - { +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LaunchDarkly.Client; +using LaunchDarkly.Common; +using LaunchDarkly.EventSource; +using Newtonsoft.Json; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class MobileStreamingProcessorTests + { private const string initialFlagsJson = "{" + "\"int-flag\":{\"value\":15,\"version\":100}," + "\"float-flag\":{\"value\":13.5,\"version\":100}," + "\"string-flag\":{\"value\":\"markw@magenic.com\",\"version\":100}" + "}"; - - User user = User.WithKey("user key"); - EventSourceMock mockEventSource; - TestEventSourceFactory eventSourceFactory; - IFlagCacheManager mockFlagCacheMgr; - - private IMobileUpdateProcessor MobileStreamingProcessorStarted() - { - mockEventSource = new EventSourceMock(); - eventSourceFactory = new TestEventSourceFactory(mockEventSource); - // stub with an empty InMemoryCache, so Stream updates can be tested - mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); - var config = Configuration.Default("someKey") - .WithConnectionManager(new MockConnectionManager(true)) - .WithIsStreamingEnabled(true) - .WithFlagCacheManager(mockFlagCacheMgr); - - var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, eventSourceFactory.Create()); - processor.Start(); - return processor; - } - - [Fact] - public void CanCreateMobileStreamingProcFromFactory() - { - var streamingProcessor = MobileStreamingProcessorStarted(); - Assert.IsType(streamingProcessor); - } - - [Fact] - public void PUTstoresFeatureFlags() - { - var streamingProcessor = MobileStreamingProcessorStarted(); - // should be empty before PUT message arrives - var flagsInCache = mockFlagCacheMgr.FlagsForUser(user); - Assert.Empty(flagsInCache); - - PUTMessageSentToProcessor(); - flagsInCache = mockFlagCacheMgr.FlagsForUser(user); - Assert.NotEmpty(flagsInCache); - int intFlagValue = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, intFlagValue); - } - - [Fact] - public void PATCHupdatesFeatureFlag() - { - // before PATCH, fill in flags - var streamingProcessor = MobileStreamingProcessorStarted(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, intFlagFromPUT); - - //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlag(), null), "patch"); - mockEventSource.RaiseMessageRcvd(eventArgs); - - //verify flag has changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(99, flagFromPatch); - } - - [Fact] - public void PATCHdoesnotUpdateFlagIfVersionIsLower() - { - // before PATCH, fill in flags - var streamingProcessor = MobileStreamingProcessorStarted(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, intFlagFromPUT); - - //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlagWithLowerVersion(), null), "patch"); - mockEventSource.RaiseMessageRcvd(eventArgs); - - //verify flag has not changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, flagFromPatch); - } - - [Fact] - public void DELETEremovesFeatureFlag() - { - // before DELETE, fill in flags, test it's there - var streamingProcessor = MobileStreamingProcessorStarted(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, intFlagFromPUT); - - // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlag(), null), "delete"); - mockEventSource.RaiseMessageRcvd(eventArgs); - - // verify flag was deleted - Assert.Null(mockFlagCacheMgr.FlagForUser("int-flag", user)); - } - - [Fact] - public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() - { - // before DELETE, fill in flags, test it's there - var streamingProcessor = MobileStreamingProcessorStarted(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); - Assert.Equal(15, intFlagFromPUT); - - // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlagWithLowerVersion(), null), "delete"); - mockEventSource.RaiseMessageRcvd(eventArgs); - - // verify flag was not deleted - Assert.NotNull(mockFlagCacheMgr.FlagForUser("int-flag", user)); - } - - string UpdatedFlag() - { - var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":999,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; - return updatedFlagAsJson; - } - - string DeleteFlag() - { - var flagToDelete = "{\"key\":\"int-flag\",\"version\":1214}"; - return flagToDelete; - } - - string UpdatedFlagWithLowerVersion() - { - var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":1,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; - return updatedFlagAsJson; - } - - string DeleteFlagWithLowerVersion() - { - var flagToDelete = "{\"key\":\"int-flag\",\"version\":1}"; - return flagToDelete; - } - - void PUTMessageSentToProcessor() - { - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(initialFlagsJson, null), "put"); - mockEventSource.RaiseMessageRcvd(eventArgs); - } - } - - class TestEventSourceFactory - { - public StreamProperties ReceivedProperties { get; private set; } - public IDictionary ReceivedHeaders { get; private set; } - IEventSource _eventSource; - - public TestEventSourceFactory(IEventSource eventSource) - { - _eventSource = eventSource; - } - - public StreamManager.EventSourceCreator Create() - { - return (StreamProperties sp, IDictionary headers) => - { - ReceivedProperties = sp; - ReceivedHeaders = headers; - return _eventSource; - }; - } - } - - class EventSourceMock : IEventSource - { - public ReadyState ReadyState => throw new NotImplementedException(); - - public event EventHandler Opened; - public event EventHandler Closed; - public event EventHandler MessageReceived; - public event EventHandler CommentReceived; - public event EventHandler Error; - - public void Close() - { - - } - - public Task StartAsync() - { - return Task.CompletedTask; - } - - public void RaiseMessageRcvd(MessageReceivedEventArgs eventArgs) - { - MessageReceived(null, eventArgs); - } - } -} + + User user = User.WithKey("user key"); + EventSourceMock mockEventSource; + TestEventSourceFactory eventSourceFactory; + IFlagCacheManager mockFlagCacheMgr; + + private IMobileUpdateProcessor MobileStreamingProcessorStarted() + { + mockEventSource = new EventSourceMock(); + eventSourceFactory = new TestEventSourceFactory(mockEventSource); + // stub with an empty InMemoryCache, so Stream updates can be tested + mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); + var config = Configuration.Default("someKey") + .WithConnectionManager(new MockConnectionManager(true)) + .WithIsStreamingEnabled(true) + .WithFlagCacheManager(mockFlagCacheMgr); + + var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, eventSourceFactory.Create()); + processor.Start(); + return processor; + } + + [Fact] + public void CanCreateMobileStreamingProcFromFactory() + { + var streamingProcessor = MobileStreamingProcessorStarted(); + Assert.IsType(streamingProcessor); + } + + [Fact] + public void PUTstoresFeatureFlags() + { + var streamingProcessor = MobileStreamingProcessorStarted(); + // should be empty before PUT message arrives + var flagsInCache = mockFlagCacheMgr.FlagsForUser(user); + Assert.Empty(flagsInCache); + + PUTMessageSentToProcessor(); + flagsInCache = mockFlagCacheMgr.FlagsForUser(user); + Assert.NotEmpty(flagsInCache); + int intFlagValue = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, intFlagValue); + } + + [Fact] + public void PATCHupdatesFeatureFlag() + { + // before PATCH, fill in flags + var streamingProcessor = MobileStreamingProcessorStarted(); + PUTMessageSentToProcessor(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, intFlagFromPUT); + + //PATCH to update 1 flag + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlag(), null), "patch"); + mockEventSource.RaiseMessageRcvd(eventArgs); + + //verify flag has changed + int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(99, flagFromPatch); + } + + [Fact] + public void PATCHdoesnotUpdateFlagIfVersionIsLower() + { + // before PATCH, fill in flags + var streamingProcessor = MobileStreamingProcessorStarted(); + PUTMessageSentToProcessor(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, intFlagFromPUT); + + //PATCH to update 1 flag + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlagWithLowerVersion(), null), "patch"); + mockEventSource.RaiseMessageRcvd(eventArgs); + + //verify flag has not changed + int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, flagFromPatch); + } + + [Fact] + public void DELETEremovesFeatureFlag() + { + // before DELETE, fill in flags, test it's there + var streamingProcessor = MobileStreamingProcessorStarted(); + PUTMessageSentToProcessor(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, intFlagFromPUT); + + // DELETE int-flag + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlag(), null), "delete"); + mockEventSource.RaiseMessageRcvd(eventArgs); + + // verify flag was deleted + Assert.Null(mockFlagCacheMgr.FlagForUser("int-flag", user)); + } + + [Fact] + public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() + { + // before DELETE, fill in flags, test it's there + var streamingProcessor = MobileStreamingProcessorStarted(); + PUTMessageSentToProcessor(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + Assert.Equal(15, intFlagFromPUT); + + // DELETE int-flag + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlagWithLowerVersion(), null), "delete"); + mockEventSource.RaiseMessageRcvd(eventArgs); + + // verify flag was not deleted + Assert.NotNull(mockFlagCacheMgr.FlagForUser("int-flag", user)); + } + + string UpdatedFlag() + { + var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":999,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; + return updatedFlagAsJson; + } + + string DeleteFlag() + { + var flagToDelete = "{\"key\":\"int-flag\",\"version\":1214}"; + return flagToDelete; + } + + string UpdatedFlagWithLowerVersion() + { + var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":1,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; + return updatedFlagAsJson; + } + + string DeleteFlagWithLowerVersion() + { + var flagToDelete = "{\"key\":\"int-flag\",\"version\":1}"; + return flagToDelete; + } + + void PUTMessageSentToProcessor() + { + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(initialFlagsJson, null), "put"); + mockEventSource.RaiseMessageRcvd(eventArgs); + } + } + + class TestEventSourceFactory + { + public StreamProperties ReceivedProperties { get; private set; } + public IDictionary ReceivedHeaders { get; private set; } + IEventSource _eventSource; + + public TestEventSourceFactory(IEventSource eventSource) + { + _eventSource = eventSource; + } + + public StreamManager.EventSourceCreator Create() + { + return (StreamProperties sp, IDictionary headers) => + { + ReceivedProperties = sp; + ReceivedHeaders = headers; + return _eventSource; + }; + } + } + + class EventSourceMock : IEventSource + { + public ReadyState ReadyState => throw new NotImplementedException(); + +#pragma warning disable 0067 // unused properties + public event EventHandler Opened; + public event EventHandler Closed; + public event EventHandler MessageReceived; + public event EventHandler CommentReceived; + public event EventHandler Error; +#pragma warning restore 0067 + + public void Close() + { + + } + + public Task StartAsync() + { + return Task.CompletedTask; + } + + public void RaiseMessageRcvd(MessageReceivedEventArgs eventArgs) + { + MessageReceived(null, eventArgs); + } + } +} From a25b8cc2bfd6d17470a7b29fa1d7ef3188e43da1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 26 Jul 2018 19:42:01 -0700 Subject: [PATCH 032/499] beta12 release (fixes System.Runtime reference problem in LD.Common) --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 4 ++-- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 2d7142ff..1d45d321 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta11 + 1.0.0-beta12 Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin @@ -18,7 +18,7 @@ - + diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index aa036d1b..0b13954b 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + From 17b758586549c062cf85999cbbd1651cdac59cb8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 27 Jul 2018 15:36:18 -0700 Subject: [PATCH 033/499] provide callback mechanism for backgrounding --- src/LaunchDarkly.Xamarin/Configuration.cs | 13 +++++ src/LaunchDarkly.Xamarin/Factory.cs | 52 ++++++------------- .../IBackgroundingState.cs | 33 ++++++++++++ src/LaunchDarkly.Xamarin/IPlatformAdapter.cs | 37 +++++++++++++ src/LaunchDarkly.Xamarin/LdClient.cs | 36 ++++++++++++- 5 files changed, 134 insertions(+), 37 deletions(-) create mode 100644 src/LaunchDarkly.Xamarin/IBackgroundingState.cs create mode 100644 src/LaunchDarkly.Xamarin/IPlatformAdapter.cs diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index 4910fe1f..a6bdda2f 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -125,6 +125,7 @@ public class Configuration : IMobileConfiguration internal ISimplePersistance Persister { get; set; } internal IDeviceInfo DeviceInfo { get; set; } internal IFeatureFlagListenerManager FeatureFlagListenerManager { get; set; } + internal IPlatformAdapter PlatformAdapter { get; set; } /// /// Default value for . @@ -651,5 +652,17 @@ public static Configuration WithBackgroundPollingInterval(this Configuration con configuration.BackgroundPollingInterval = backgroundPollingInternal; return configuration; } + + /// + /// Specifies a component that provides special functionality for the current mobile platform. + /// + /// Configuration. + /// An implementation of . + /// the same Configuration instance + public static Configuration WithPlatformAdapter(this Configuration configuration, IPlatformAdapter adapter) + { + configuration.PlatformAdapter = adapter; + return configuration; + } } } diff --git a/src/LaunchDarkly.Xamarin/Factory.cs b/src/LaunchDarkly.Xamarin/Factory.cs index 5fb139c0..fa082f67 100644 --- a/src/LaunchDarkly.Xamarin/Factory.cs +++ b/src/LaunchDarkly.Xamarin/Factory.cs @@ -14,27 +14,21 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura IFlagListenerUpdater updater, User user) { - IFlagCacheManager flagCacheManager; - if (configuration.FlagCacheManager != null) { - flagCacheManager = configuration.FlagCacheManager; + return configuration.FlagCacheManager; } else { var inMemoryCache = new UserFlagInMemoryCache(); var deviceCache = new UserFlagDeviceCache(persister); - flagCacheManager = new FlagCacheManager(inMemoryCache, deviceCache, updater, user); + return new FlagCacheManager(inMemoryCache, deviceCache, updater, user); } - - return flagCacheManager; } internal static IConnectionManager CreateConnectionManager(Configuration configuration) { - IConnectionManager connectionManager; - connectionManager = configuration.ConnectionManager ?? new MobileConnectionManager(); - return connectionManager; + return configuration.ConnectionManager ?? new MobileConnectionManager(); } internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, @@ -47,29 +41,26 @@ internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration confi return configuration.MobileUpdateProcessor; } - IMobileUpdateProcessor updateProcessor = null; if (configuration.Offline) { - Log.InfoFormat("Was configured to be offline, starting service with NullUpdateProcessor"); + Log.InfoFormat("Starting LaunchDarkly client in offline mode"); return new NullUpdateProcessor(); } if (configuration.IsStreamingEnabled) { - updateProcessor = new MobileStreamingProcessor(configuration, + return new MobileStreamingProcessor(configuration, flagCacheManager, user, source); } else { var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); - updateProcessor = new MobilePollingProcessor(featureFlagRequestor, - flagCacheManager, - user, - configuration.PollingInterval); + return new MobilePollingProcessor(featureFlagRequestor, + flagCacheManager, + user, + configuration.PollingInterval); } - - return updateProcessor; } internal static IEventProcessor CreateEventProcessor(Configuration configuration) @@ -80,7 +71,6 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration } if (configuration.Offline) { - Log.InfoFormat("Was configured to be offline, starting service with NullEventProcessor"); return new NullEventProcessor(); } @@ -90,32 +80,22 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration internal static ISimplePersistance CreatePersister(Configuration configuration) { - if (configuration.Persister != null) - { - return configuration.Persister; - } - - return new SimpleMobileDevicePersistance(); + return configuration.Persister ?? new SimpleMobileDevicePersistance(); } internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) { - if (configuration.DeviceInfo != null) - { - return configuration.DeviceInfo; - } - - return new DeviceInfo(); + return configuration.DeviceInfo ?? new DeviceInfo(); } internal static IFeatureFlagListenerManager CreateFeatureFlagListenerManager(Configuration configuration) { - if (configuration.FeatureFlagListenerManager != null) - { - return configuration.FeatureFlagListenerManager; - } + return configuration.FeatureFlagListenerManager ?? new FeatureFlagListenerManager(); + } - return new FeatureFlagListenerManager(); + internal static IPlatformAdapter CreatePlatformAdapter(Configuration configuration) + { + return configuration.PlatformAdapter ?? new NullPlatformAdapter(); } } } diff --git a/src/LaunchDarkly.Xamarin/IBackgroundingState.cs b/src/LaunchDarkly.Xamarin/IBackgroundingState.cs new file mode 100644 index 00000000..019c2fbd --- /dev/null +++ b/src/LaunchDarkly.Xamarin/IBackgroundingState.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin +{ + /// + /// An interface that is used internally by implementations of + /// to update the state of the LaunchDarkly client in background mode. Application code does not need + /// to interact with this interface. + /// + public interface IBackgroundingState + { + /// + /// Tells the LaunchDarkly client that the application is entering background mode. The client will + /// suspend the regular streaming or polling process, except when + /// is called. + /// + Task EnterBackgroundAsync(); + + /// + /// Tells the LaunchDarkly client that the application is exiting background mode. The client will + /// resume the regular streaming or polling process. + /// + Task ExitBackgroundAsync(); + + /// + /// Tells the LaunchDarkly client to initiate a request for feature flag updates while in background mode. + /// + Task BackgroundUpdateAsync(); + } +} diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs new file mode 100644 index 00000000..ad49c2c1 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LaunchDarkly.Xamarin +{ + /// + /// Interface for a component that helps LdClient interact with a specific mobile platform. + /// Currently this is necessary in order to handle features that are not part of the portable + /// Xamarin.Essentials API; in the future it may be handled automatically when you + /// create an LdClient. + /// + /// To obtain an instance of this interface, use the implementation of `PlatformComponents.CreatePlatformAdapter` + /// that is provided by the add-on library for your specific platform (e.g. LaunchDarkly.Xamarin.Android). + /// Then pass the object to + /// when you are building your client configuration. + /// + /// Application code should not call any methods of this interface directly; they are used internally + /// by LdClient. + /// + public interface IPlatformAdapter : IDisposable + { + /// + /// Tells the IPlatformAdapter to start monitoring the foreground/background state of + /// the application, and provides a callback object for it to use when the state changes. + /// + /// An implementation of IBackgroundingState provided by the client + void EnableBackgrounding(IBackgroundingState backgroundingState); + } + + internal class NullPlatformAdapter : IPlatformAdapter + { + public void EnableBackgrounding(IBackgroundingState backgroundingState) { } + + public void Dispose() { } + } +} diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 9c74e5f1..e7636eef 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -50,6 +50,7 @@ public sealed class LdClient : ILdMobileClient IDeviceInfo deviceInfo; EventFactory eventFactory = EventFactory.Default; IFeatureFlagListenerManager flagListenerManager; + IPlatformAdapter platformAdapter; SemaphoreSlim connectionLock; @@ -75,6 +76,7 @@ public sealed class LdClient : ILdMobileClient persister = Factory.CreatePersister(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); + platformAdapter = Factory.CreatePlatformAdapter(configuration); // If you pass in a user with a null or blank key, one will be assigned to them. if (String.IsNullOrEmpty(user.Key)) @@ -222,6 +224,11 @@ static void CreateInstance(Configuration configuration, User user) Instance = new LdClient(configuration, user); Log.InfoFormat("Initialized LaunchDarkly Client {0}", Instance.Version); + + if (configuration.EnableBackgroundUpdating) + { + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); + } } bool StartUpdateProcessor(TimeSpan maxWaitTime) @@ -520,7 +527,8 @@ void Dispose(bool disposing) { if (disposing) { - Log.InfoFormat("The mobile client is being disposed"); + Log.InfoFormat("Shutting down the LaunchDarkly client"); + platformAdapter.Dispose(); updateProcessor.Dispose(); eventProcessor.Dispose(); } @@ -618,4 +626,30 @@ private Exception UnwrapAggregateException(AggregateException e) return e; } } + + // Implementation of IBackgroundingState - this allows us to keep these methods out of the public LdClient API + internal class LdClientBackgroundingState : IBackgroundingState + { + private readonly LdClient _client; + + internal LdClientBackgroundingState(LdClient client) + { + _client = client; + } + + public async Task EnterBackgroundAsync() + { + await _client.EnterBackgroundAsync(); + } + + public async Task ExitBackgroundAsync() + { + await _client.EnterForegroundAsync(); + } + + public async Task BackgroundUpdateAsync() + { + await _client.BackgroundTickAsync(); + } + } } \ No newline at end of file From d7bc235f023dd0c8f97540075ddb71bda1c560c9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 27 Jul 2018 15:48:20 -0700 Subject: [PATCH 034/499] don't try to restart the update processor in background unless backgrounding is enabled --- src/LaunchDarkly.Xamarin/LdClient.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index e7636eef..6213b2c0 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -562,12 +562,18 @@ internal async Task EnterBackgroundAsync() { ClearUpdateProcessor(); Config.IsStreamingEnabled = false; - await RestartUpdateProcessorAsync(); + if (Config.EnableBackgroundUpdating) + { + await RestartUpdateProcessorAsync(); + } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } else { - await PingPollingProcessorAsync(); + if (Config.EnableBackgroundUpdating) + { + await PingPollingProcessorAsync(); + } } } From 7c023e24ea5028de522dbfeec3b0efb769a20442 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 27 Jul 2018 15:50:05 -0700 Subject: [PATCH 035/499] always initialize platform adapter --- src/LaunchDarkly.Xamarin/IPlatformAdapter.cs | 5 +++-- src/LaunchDarkly.Xamarin/LdClient.cs | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs index ad49c2c1..278d3491 100644 --- a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs +++ b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs @@ -25,12 +25,13 @@ public interface IPlatformAdapter : IDisposable /// the application, and provides a callback object for it to use when the state changes. /// /// An implementation of IBackgroundingState provided by the client - void EnableBackgrounding(IBackgroundingState backgroundingState); + /// True if the client should poll while in the background + void EnableBackgrounding(IBackgroundingState backgroundingState, bool pollWhileInBackground); } internal class NullPlatformAdapter : IPlatformAdapter { - public void EnableBackgrounding(IBackgroundingState backgroundingState) { } + public void EnableBackgrounding(IBackgroundingState backgroundingState, bool pollWhileInBackground) { } public void Dispose() { } } diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 6213b2c0..b42ef6eb 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -225,10 +225,8 @@ static void CreateInstance(Configuration configuration, User user) Log.InfoFormat("Initialized LaunchDarkly Client {0}", Instance.Version); - if (configuration.EnableBackgroundUpdating) - { - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); - } + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), + configuration.EnableBackgroundUpdating); } bool StartUpdateProcessor(TimeSpan maxWaitTime) From a7376c05c78e3867463d5a089b9a698cd1f46b25 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 27 Jul 2018 17:25:46 -0700 Subject: [PATCH 036/499] doc comment typo --- src/LaunchDarkly.Xamarin/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 9c74e5f1..8f02f24b 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -129,7 +129,7 @@ public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) /// from the LaunchDarkly service. /// /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient + /// to instantiate the single instance of LdClient /// for the lifetime of your application. /// /// The singleton LdClient instance. From 0de1c94dd6519e4d9fdc3c8e1b73324b5f537331 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 27 Jul 2018 17:26:09 -0700 Subject: [PATCH 037/499] doc comment typo --- src/LaunchDarkly.Xamarin/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 8f02f24b..cccc9157 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -152,7 +152,7 @@ public static async Task InitAsync(string mobileKey, User user) /// If you would rather this happen in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient + /// to instantiate the single instance of LdClient /// for the lifetime of your application. /// /// The singleton LdClient instance. From a97dd0d3763a714b2968a74ab981e2c922c14862 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Jul 2018 10:15:58 -0700 Subject: [PATCH 038/499] fix hang in synchronous Identify --- src/LaunchDarkly.Xamarin/LdClient.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index cccc9157..5996f6b0 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -443,7 +443,10 @@ public void Identify(User user) { try { - IdentifyAsync(user).Wait(); + // Note that we must use Task.Run here, rather than just doing IdentifyAsync(user).Wait(), + // to avoid a deadlock if we are on the main thread. See: + // https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/ + Task.Run(() => IdentifyAsync(user)).Wait(); } catch (AggregateException e) { @@ -454,6 +457,8 @@ public void Identify(User user) /// public async Task IdentifyAsync(User user) { + Log.Warn("IdentifyAsync"); + if (user == null) { throw new ArgumentNullException("user"); @@ -477,6 +482,8 @@ public async Task IdentifyAsync(User user) } eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); + + Log.Warn("IdentifyAsync ending"); } async Task RestartUpdateProcessorAsync() From d631156db35a6f15104fac318892db8c40fffd9b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Jul 2018 10:16:18 -0700 Subject: [PATCH 039/499] rm debugging --- src/LaunchDarkly.Xamarin/LdClient.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 5996f6b0..79bccf3f 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -457,8 +457,6 @@ public void Identify(User user) /// public async Task IdentifyAsync(User user) { - Log.Warn("IdentifyAsync"); - if (user == null) { throw new ArgumentNullException("user"); @@ -482,8 +480,6 @@ public async Task IdentifyAsync(User user) } eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); - - Log.Warn("IdentifyAsync ending"); } async Task RestartUpdateProcessorAsync() From b8da2e04ceb1ed7ee8574bcd67d88fc2555707f9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Jul 2018 11:20:01 -0700 Subject: [PATCH 040/499] need to pass polling interval to adapter --- src/LaunchDarkly.Xamarin/IPlatformAdapter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs index 278d3491..a50c924e 100644 --- a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs +++ b/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs @@ -25,13 +25,13 @@ public interface IPlatformAdapter : IDisposable /// the application, and provides a callback object for it to use when the state changes. /// /// An implementation of IBackgroundingState provided by the client - /// True if the client should poll while in the background - void EnableBackgrounding(IBackgroundingState backgroundingState, bool pollWhileInBackground); + /// if non-null, the interval at which polling should happen in the background + void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval); } internal class NullPlatformAdapter : IPlatformAdapter { - public void EnableBackgrounding(IBackgroundingState backgroundingState, bool pollWhileInBackground) { } + public void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval) { } public void Dispose() { } } From bb7638049f462a567dbb4664e2cc2ba5c7f1650e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Jul 2018 11:56:43 -0700 Subject: [PATCH 041/499] fix method parameter --- src/LaunchDarkly.Xamarin/LdClient.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index e943dc73..188f598a 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -225,8 +225,12 @@ static void CreateInstance(Configuration configuration, User user) Log.InfoFormat("Initialized LaunchDarkly Client {0}", Instance.Version); - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), - configuration.EnableBackgroundUpdating); + TimeSpan? bgPollInterval = null; + if (configuration.EnableBackgroundUpdating) + { + bgPollInterval = configuration.BackgroundPollingInterval; + } + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), bgPollInterval); } bool StartUpdateProcessor(TimeSpan maxWaitTime) From 3342490965d6d4708d0d4ce5e54bb4567d733b6a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Jul 2018 13:43:49 -0700 Subject: [PATCH 042/499] version 1.0.0-beta13 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 1d45d321..51d35c2d 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta12 + 1.0.0-beta13 Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin From 7a0340961d8a4008234b084a988fc05785437ead Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 31 Jul 2018 11:08:33 -0700 Subject: [PATCH 043/499] don't allow null/empty mobile key --- src/LaunchDarkly.Xamarin/Configuration.cs | 4 ++++ .../LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index a6bdda2f..dc6e578c 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -188,6 +188,10 @@ public class Configuration : IMobileConfiguration /// a Configuration instance public static Configuration Default(string mobileKey) { + if (String.IsNullOrEmpty(mobileKey)) + { + throw new ArgumentOutOfRangeException("mobileKey", "key is required"); + } var defaultConfiguration = new Configuration { BaseUri = DefaultUri, diff --git a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs index 250deb68..af1c0fc7 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs @@ -36,6 +36,18 @@ public void CanOverrideStreamConfiguration() Assert.Equal(TimeSpan.FromDays(1), config.ReconnectTime); } + [Fact] + public void MobileKeyCannotBeNull() + { + Assert.Throws(() => Configuration.Default(null)); + } + + [Fact] + public void MobileKeyCannotBeEmpty() + { + Assert.Throws(() => Configuration.Default("")); + } + [Fact] public void CannotOverrideTooSmallPollingInterval() { From f73aaa65ef4fc5a29bf909425c17c84c4f7a1708 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 31 Jul 2018 12:48:39 -0700 Subject: [PATCH 044/499] make polling intervals consistent with other mobile SDKs --- src/LaunchDarkly.Xamarin/Configuration.cs | 23 +++++++++++++++---- .../ConfigurationTest.cs | 18 +++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index dc6e578c..0435e21e 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -130,7 +130,11 @@ public class Configuration : IMobileConfiguration /// /// Default value for . /// - public static TimeSpan DefaultPollingInterval = TimeSpan.FromSeconds(30); + public static TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5); + /// + /// Minimum value for . + /// + public static TimeSpan MinimumPollingInterval = TimeSpan.FromMinutes(5); /// /// Default value for . /// @@ -174,7 +178,11 @@ public class Configuration : IMobileConfiguration /// /// The default value for . /// - private static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(3600); + private static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(60); + /// + /// The minimum value for . + /// + public static readonly TimeSpan MinimumBackgroundPollingInterval = TimeSpan.FromMinutes(15); /// /// The default value for . /// @@ -367,10 +375,10 @@ public static Configuration WithEventSamplingInterval(this Configuration configu /// the same Configuration instance public static Configuration WithPollingInterval(this Configuration configuration, TimeSpan pollingInterval) { - if (pollingInterval.CompareTo(Configuration.DefaultPollingInterval) < 0) + if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) { - Log.Warn("PollingInterval cannot be less than the default of 30 seconds."); - pollingInterval = Configuration.DefaultPollingInterval; + Log.WarnFormat("PollingInterval cannot be less than the default of {0}."); + pollingInterval = Configuration.MinimumPollingInterval; } configuration.PollingInterval = pollingInterval; return configuration; @@ -653,6 +661,11 @@ public static Configuration WithEnableBackgroundUpdating(this Configuration conf /// the same Configuration instance public static Configuration WithBackgroundPollingInterval(this Configuration configuration, TimeSpan backgroundPollingInternal) { + if (backgroundPollingInternal.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) + { + Log.WarnFormat("BackgroundPollingInterval cannot be less than the default of {0}.", Configuration.MinimumBackgroundPollingInterval); + backgroundPollingInternal = Configuration.MinimumBackgroundPollingInterval; + } configuration.BackgroundPollingInterval = backgroundPollingInternal; return configuration; } diff --git a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs index af1c0fc7..8f5b4f8d 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs @@ -13,12 +13,12 @@ public void CanOverrideConfiguration() var config = Configuration.Default("AnyOtherSdkKey") .WithBaseUri("https://app.AnyOtherEndpoint.com") .WithEventQueueCapacity(99) - .WithPollingInterval(TimeSpan.FromMinutes(1)); + .WithPollingInterval(TimeSpan.FromMinutes(45)); Assert.Equal(new Uri("https://app.AnyOtherEndpoint.com"), config.BaseUri); Assert.Equal("AnyOtherSdkKey", config.MobileKey); Assert.Equal(99, config.EventQueueCapacity); - Assert.Equal(TimeSpan.FromMinutes(1), config.PollingInterval); + Assert.Equal(TimeSpan.FromMinutes(45), config.PollingInterval); } [Fact] @@ -49,11 +49,19 @@ public void MobileKeyCannotBeEmpty() } [Fact] - public void CannotOverrideTooSmallPollingInterval() + public void CannotSetTooSmallPollingInterval() { - var config = Configuration.Default("AnyOtherSdkKey").WithPollingInterval(TimeSpan.FromSeconds(29)); + var config = Configuration.Default("AnyOtherSdkKey").WithPollingInterval(TimeSpan.FromSeconds(299)); - Assert.Equal(TimeSpan.FromSeconds(30), config.PollingInterval); + Assert.Equal(TimeSpan.FromSeconds(300), config.PollingInterval); + } + + [Fact] + public void CannotSetTooSmallBackgroundPollingInterval() + { + var config = Configuration.Default("SdkKey").WithBackgroundPollingInterval(TimeSpan.FromSeconds(899)); + + Assert.Equal(TimeSpan.FromSeconds(900), config.BackgroundPollingInterval); } [Fact] From 08e08f77009d5aef7e53904e8d2b5e7bcd83d4b2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 14 Aug 2018 11:40:16 -0700 Subject: [PATCH 045/499] bump LD.Common to 1.0.5 to get fix for reconnection delay --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 51d35c2d..68087523 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 0b13954b..400a99fc 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + From 0b66a68f02d42205366cd1193b18179211e439a2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 14 Aug 2018 11:50:08 -0700 Subject: [PATCH 046/499] 1.0.0-beta14 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 68087523..d4e9e124 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta13 + 1.0.0-beta14 Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin From 63ed5ba2c09f0d0b6b7b35c1fc6a8bc08ac4f1b4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Sep 2018 11:59:30 -0700 Subject: [PATCH 047/499] fix sample code --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2065665..609f27aa 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,15 @@ Quick setup Install-Package LaunchDarkly.Xamarin -1. Import the LaunchDarkly package: +1. Import the LaunchDarkly packages: + using LaunchDarkly.Client; using LaunchDarkly.Xamarin; 2. Initialize the LDClient with your Mobile key and user: User user = User.WithKey(username); - LdClient ldClient = LdClient.Init("YOUR_MOBILE_KEY", username); + LdClient ldClient = LdClient.Init("YOUR_MOBILE_KEY", user); Your first feature flag ----------------------- From 3c9712d541cc33e8e09624d7f828ffab1e904aaa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Sep 2018 13:15:04 -0700 Subject: [PATCH 048/499] use LaunchDarkly.Common 1.1.1 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- src/LaunchDarkly.Xamarin/LdClient.cs | 9 +++++---- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index d4e9e124..3ce8565a 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 188f598a..6e6ffa82 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -372,13 +372,13 @@ JToken Variation(string featureKey, JToken defaultValue) if (value == null || value.Type == JTokenType.Null) { featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, User, - defaultValue); + defaultValue, + EvaluationErrorKind.FLAG_NOT_FOUND); value = defaultValue; } else { featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, - flag.variation, - flag.value, + new EvaluationDetail(flag.value, flag.variation, null), defaultValue); } eventProcessor.SendEvent(featureRequestEvent); @@ -389,7 +389,8 @@ JToken Variation(string featureKey, JToken defaultValue) featureKey); featureRequestEvent = eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, - defaultValue); + defaultValue, + EvaluationErrorKind.FLAG_NOT_FOUND); eventProcessor.SendEvent(featureRequestEvent); return defaultValue; } diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 400a99fc..83335352 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + From c2661058f20815e27521f9f516fbb7d5137882c0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Sep 2018 13:19:34 -0700 Subject: [PATCH 049/499] use Xamarin.Essentials 0.10.0-preview --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- src/LaunchDarkly.Xamarin/MobileConnectionManager.cs | 2 +- .../LaunchDarkly.Xamarin.Tests.csproj | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 3ce8565a..b6a4ac24 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs index 3d9cccfa..13daea8f 100644 --- a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs +++ b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs @@ -28,7 +28,7 @@ bool IConnectionManager.IsConnected } } - void Connectivity_ConnectivityChanged(ConnectivityChangedEventArgs e) + void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e) { UpdateConnectedStatus(); ConnectionChanged?.Invoke(isConnected); diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 83335352..f93d0ab2 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -7,6 +7,7 @@ + From 65e2bdcee734e98d6d6a9a695a5326709462e52a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Sep 2018 13:36:49 -0700 Subject: [PATCH 050/499] version 1.0.0-beta15 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index b6a4ac24..89f657a8 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,7 +1,7 @@ - 1.0.0-beta14 + 1.0.0-beta15 Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin From 7faafbe80f1cf65b06b65f665ccff8bd879b51ae Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 5 Mar 2019 11:13:51 -0800 Subject: [PATCH 051/499] feat(src/): Removed Xamarin.Essentials and added necessary classes to project with proper licensing --- .../Connectivity/Connectivity.android.cs | 252 ++++++++++++++++++ .../Connectivity/Connectivity.ios.cs | 87 ++++++ .../Connectivity.ios.reachability.cs | 187 +++++++++++++ .../Connectivity/Connectivity.netstandard.cs | 41 +++ .../Connectivity/Connectivity.shared.cs | 91 +++++++ .../Connectivity/Connectivity.shared.enums.cs | 42 +++ .../LaunchDarkly.Xamarin.csproj | 54 +++- .../MobileConnectionManager.cs | 1 - .../Preferences/Preferences.android.cs | 176 ++++++++++++ .../Preferences/Preferences.ios.cs | 162 +++++++++++ .../Preferences/Preferences.netstandard.cs | 42 +++ .../Preferences/Preferences.shared.cs | 140 ++++++++++ .../SimpleMobileDevicePersistance.cs | 5 +- .../UserFlagDeviceCache.cs | 1 - .../LaunchDarkly.Xamarin.Tests.csproj | 1 - 15 files changed, 1272 insertions(+), 10 deletions(-) create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs create mode 100644 src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs create mode 100644 src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs create mode 100644 src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs create mode 100644 src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs new file mode 100644 index 00000000..29dd8742 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs @@ -0,0 +1,252 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Android.Content; +using Android.Net; +using Android.OS; +using Debug = System.Diagnostics.Debug; + +namespace LaunchDarkly.Xamarin.Connectivity +{ + public partial class Connectivity + { + static ConnectivityBroadcastReceiver conectivityReceiver; + + static void StartListeners() + { + Permissions.EnsureDeclared(PermissionType.NetworkState); + + conectivityReceiver = new ConnectivityBroadcastReceiver(OnConnectivityChanged); + + Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); + } + + static void StopListeners() + { + if (conectivityReceiver == null) + return; + try + { + Platform.AppContext.UnregisterReceiver(conectivityReceiver); + } + catch (Java.Lang.IllegalArgumentException) + { + Debug.WriteLine("Connectivity receiver already unregistered. Disposing of it."); + } + conectivityReceiver.Dispose(); + conectivityReceiver = null; + } + + static NetworkAccess IsBetterAccess(NetworkAccess currentAccess, NetworkAccess newAccess) => + newAccess > currentAccess ? newAccess : currentAccess; + + static NetworkAccess PlatformNetworkAccess + { + get + { + Permissions.EnsureDeclared(PermissionType.NetworkState); + + try + { + var currentAccess = NetworkAccess.None; + var manager = Platform.ConnectivityManager; + + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + { + foreach (var network in manager.GetAllNetworks()) + { + try + { + var capabilities = manager.GetNetworkCapabilities(network); + + if (capabilities == null) + continue; + + var info = manager.GetNetworkInfo(network); + + if (info == null || !info.IsAvailable) + continue; + + // Check to see if it has the internet capability + if (!capabilities.HasCapability(NetCapability.Internet)) + { + // Doesn't have internet, but local is possible + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Local); + continue; + } + + ProcessNetworkInfo(info); + } + catch + { + // there is a possibility, but don't worry + } + } + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var info in manager.GetAllNetworkInfo()) +#pragma warning restore CS0618 // Type or member is obsolete + { + ProcessNetworkInfo(info); + } + } + + void ProcessNetworkInfo(NetworkInfo info) + { + if (info == null || !info.IsAvailable) + return; + + if (info.IsConnected) + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Internet); + else if (info.IsConnectedOrConnecting) + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.ConstrainedInternet); + } + + return currentAccess; + } + catch (Exception e) + { + Debug.WriteLine("Unable to get connected state - do you have ACCESS_NETWORK_STATE permission? - error: {0}", e); + return NetworkAccess.Unknown; + } + } + } + + static IEnumerable PlatformConnectionProfiles + { + get + { + Permissions.EnsureDeclared(PermissionType.NetworkState); + + var manager = Platform.ConnectivityManager; + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + { + foreach (var network in manager.GetAllNetworks()) + { + NetworkInfo info = null; + try + { + info = manager.GetNetworkInfo(network); + } + catch + { + // there is a possibility, but don't worry about it + } + + var p = ProcessNetworkInfo(info); + if (p.HasValue) + yield return p.Value; + } + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var info in manager.GetAllNetworkInfo()) +#pragma warning restore CS0618 // Type or member is obsolete + { + var p = ProcessNetworkInfo(info); + if (p.HasValue) + yield return p.Value; + } + } + + ConnectionProfile? ProcessNetworkInfo(NetworkInfo info) + { + if (info == null || !info.IsAvailable || !info.IsConnectedOrConnecting) + return null; + + return GetConnectionType(info.Type, info.TypeName); + } + } + } + + internal static ConnectionProfile GetConnectionType(ConnectivityType connectivityType, string typeName) + { + switch (connectivityType) + { + case ConnectivityType.Ethernet: + return ConnectionProfile.Ethernet; + case ConnectivityType.Wifi: + return ConnectionProfile.WiFi; + case ConnectivityType.Bluetooth: + return ConnectionProfile.Bluetooth; + case ConnectivityType.Wimax: + case ConnectivityType.Mobile: + case ConnectivityType.MobileDun: + case ConnectivityType.MobileHipri: + case ConnectivityType.MobileMms: + return ConnectionProfile.Cellular; + case ConnectivityType.Dummy: + return ConnectionProfile.Unknown; + default: + if (string.IsNullOrWhiteSpace(typeName)) + return ConnectionProfile.Unknown; + + var typeNameLower = typeName.ToLowerInvariant(); + if (typeNameLower.Contains("mobile")) + return ConnectionProfile.Cellular; + + if (typeNameLower.Contains("wimax")) + return ConnectionProfile.Cellular; + + if (typeNameLower.Contains("wifi")) + return ConnectionProfile.WiFi; + + if (typeNameLower.Contains("ethernet")) + return ConnectionProfile.Ethernet; + + if (typeNameLower.Contains("bluetooth")) + return ConnectionProfile.Bluetooth; + + return ConnectionProfile.Unknown; + } + } + } + + [BroadcastReceiver(Enabled = true, Exported = false, Label = "Essentials Connectivity Broadcast Receiver")] + class ConnectivityBroadcastReceiver : BroadcastReceiver + { + Action onChanged; + + public ConnectivityBroadcastReceiver() + { + } + + public ConnectivityBroadcastReceiver(Action onChanged) => + this.onChanged = onChanged; + + public override async void OnReceive(Context context, Intent intent) + { + if (intent.Action != ConnectivityManager.ConnectivityAction) + return; + + // await 500ms to ensure that the the connection manager updates + await Task.Delay(500); + onChanged?.Invoke(); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs new file mode 100644 index 00000000..6a53fad9 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs @@ -0,0 +1,87 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; + +namespace LaunchDarkly.Xamarin.Connectivity +{ + public static partial class Connectivity + { + static ReachabilityListener listener; + + static void StartListeners() + { + listener = new ReachabilityListener(); + listener.ReachabilityChanged += OnConnectivityChanged; + } + + static void StopListeners() + { + if (listener == null) + return; + + listener.ReachabilityChanged -= OnConnectivityChanged; + listener.Dispose(); + listener = null; + } + + static NetworkAccess PlatformNetworkAccess + { + get + { + var internetStatus = Reachability.InternetConnectionStatus(); + if (internetStatus == NetworkStatus.ReachableViaCarrierDataNetwork || internetStatus == NetworkStatus.ReachableViaWiFiNetwork) + return NetworkAccess.Internet; + + var remoteHostStatus = Reachability.RemoteHostStatus(); + if (remoteHostStatus == NetworkStatus.ReachableViaCarrierDataNetwork || remoteHostStatus == NetworkStatus.ReachableViaWiFiNetwork) + return NetworkAccess.Internet; + + return NetworkAccess.None; + } + } + + static IEnumerable PlatformConnectionProfiles + { + get + { + var statuses = Reachability.GetActiveConnectionType(); + foreach (var status in statuses) + { + switch (status) + { + case NetworkStatus.ReachableViaCarrierDataNetwork: + yield return ConnectionProfile.Cellular; + break; + case NetworkStatus.ReachableViaWiFiNetwork: + yield return ConnectionProfile.WiFi; + break; + default: + yield return ConnectionProfile.Unknown; + break; + } + } + } + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs new file mode 100644 index 00000000..4963da02 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs @@ -0,0 +1,187 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using CoreFoundation; +using SystemConfiguration; + +namespace LaunchDarkly.Xamarin.Connectivity +{ + enum NetworkStatus + { + NotReachable, + ReachableViaCarrierDataNetwork, + ReachableViaWiFiNetwork + } + + static class Reachability + { + internal const string HostName = "www.microsoft.com"; + + internal static NetworkStatus RemoteHostStatus() + { + using (var remoteHostReachability = new NetworkReachability(HostName)) + { + var reachable = remoteHostReachability.TryGetFlags(out var flags); + + if (!reachable) + return NetworkStatus.NotReachable; + + if (!IsReachableWithoutRequiringConnection(flags)) + return NetworkStatus.NotReachable; + + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + return NetworkStatus.ReachableViaCarrierDataNetwork; + + return NetworkStatus.ReachableViaWiFiNetwork; + } + } + + internal static NetworkStatus InternetConnectionStatus() + { + var status = NetworkStatus.NotReachable; + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + status = NetworkStatus.ReachableViaCarrierDataNetwork; + + // If the connection is reachable and no connection is required, then assume it's WiFi + if (defaultNetworkAvailable) + { + status = NetworkStatus.ReachableViaWiFiNetwork; + } + + // If the connection is on-demand or on-traffic and no user intervention + // is required, then assume WiFi. + if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && + (flags & NetworkReachabilityFlags.InterventionRequired) == 0) + { + status = NetworkStatus.ReachableViaWiFiNetwork; + } + + return status; + } + + internal static IEnumerable GetActiveConnectionType() + { + var status = new List(); + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + { + status.Add(NetworkStatus.ReachableViaCarrierDataNetwork); + } + else if (defaultNetworkAvailable) + { + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + else if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && + (flags & NetworkReachabilityFlags.InterventionRequired) == 0) + { + // If the connection is on-demand or on-traffic and no user intervention + // is required, then assume WiFi. + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + + return status; + } + + internal static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) + { + var ip = new IPAddress(0); + using (var defaultRouteReachability = new NetworkReachability(ip)) + { + if (!defaultRouteReachability.TryGetFlags(out flags)) + return false; + + return IsReachableWithoutRequiringConnection(flags); + } + } + + internal static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags) + { + // Is it reachable with the current network configuration? + var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0; + + // Do we need a connection to reach it? + var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0; + + // Since the network stack will automatically try to get the WAN up, + // probe that + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + noConnectionRequired = true; + + return isReachable && noConnectionRequired; + } + } + + class ReachabilityListener : IDisposable + { + NetworkReachability defaultRouteReachability; + NetworkReachability remoteHostReachability; + + internal ReachabilityListener() + { + var ip = new IPAddress(0); + defaultRouteReachability = new NetworkReachability(ip); + defaultRouteReachability.SetNotification(OnChange); + defaultRouteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + + remoteHostReachability = new NetworkReachability(Reachability.HostName); + + // Need to probe before we queue, or we wont get any meaningful values + // this only happens when you create NetworkReachability from a hostname + remoteHostReachability.TryGetFlags(out var flags); + + remoteHostReachability.SetNotification(OnChange); + remoteHostReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + } + + internal event Action ReachabilityChanged; + + void IDisposable.Dispose() => Dispose(); + + internal void Dispose() + { + defaultRouteReachability?.Dispose(); + defaultRouteReachability = null; + remoteHostReachability?.Dispose(); + remoteHostReachability = null; + } + + async void OnChange(NetworkReachabilityFlags flags) + { + // Add in artifical delay so the connection status has time to change + // else it will return true no matter what. + await Task.Delay(100); + + ReachabilityChanged?.Invoke(); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs new file mode 100644 index 00000000..944ac838 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs @@ -0,0 +1,41 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System.Collections.Generic; + +namespace LaunchDarkly.Xamarin.Connectivity +{ + public static partial class Connectivity + { + static NetworkAccess PlatformNetworkAccess => + throw new System.NullReferenceException(); + + static IEnumerable PlatformConnectionProfiles => + throw new System.NullReferenceException(); + + static void StartListeners() => + throw new System.NullReferenceException(); + + static void StopListeners() => + throw new System.NullReferenceException(); + } +} diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs new file mode 100644 index 00000000..e8a23616 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs @@ -0,0 +1,91 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LaunchDarkly.Xamarin.Connectivity +{ + public static partial class Connectivity + { + static event EventHandler ConnectivityChangedInternal; + + // a cache so that events aren't fired unnecessarily + // this is mainly an issue on Android, but we can stiil do this everywhere + static NetworkAccess currentAccess; + static List currentProfiles; + + public static NetworkAccess NetworkAccess => PlatformNetworkAccess; + + public static IEnumerable ConnectionProfiles => PlatformConnectionProfiles.Distinct(); + + public static event EventHandler ConnectivityChanged + { + add + { + var wasRunning = ConnectivityChangedInternal != null; + + ConnectivityChangedInternal += value; + + if (!wasRunning && ConnectivityChangedInternal != null) + { + SetCurrent(); + StartListeners(); + } + } + + remove + { + var wasRunning = ConnectivityChangedInternal != null; + + ConnectivityChangedInternal -= value; + + if (wasRunning && ConnectivityChangedInternal == null) + StopListeners(); + } + } + + static void SetCurrent() + { + currentAccess = NetworkAccess; + currentProfiles = new List(ConnectionProfiles); + } + } + + public class ConnectivityChangedEventArgs : EventArgs + { + public ConnectivityChangedEventArgs(NetworkAccess access, IEnumerable connectionProfiles) + { + NetworkAccess = access; + ConnectionProfiles = connectionProfiles; + } + + public NetworkAccess NetworkAccess { get; } + + public IEnumerable ConnectionProfiles { get; } + + public override string ToString() => + $"{nameof(NetworkAccess)}: {NetworkAccess}, " + + $"{nameof(ConnectionProfiles)}: [{string.Join(", ", ConnectionProfiles)}]"; + } +} diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs new file mode 100644 index 00000000..e6a7c423 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs @@ -0,0 +1,42 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace LaunchDarkly.Xamarin.Connectivity +{ + public enum ConnectionProfile + { + Unknown = 0, + Bluetooth = 1, + Cellular = 2, + Ethernet = 3, + WiFi = 4 + } + + public enum NetworkAccess + { + Unknown = 0, + None = 1, + Local = 2, + ConstrainedInternet = 3, + Internet = 4 + } +} diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 89f657a8..98055e71 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -5,18 +5,18 @@ Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin + false - netstandard1.6;netstandard2.0;net45 + netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81 - netstandard1.6;netstandard2.0 + netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81 - @@ -29,4 +29,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs index 13daea8f..f9453560 100644 --- a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs +++ b/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs @@ -1,5 +1,4 @@ using System; -using Xamarin.Essentials; namespace LaunchDarkly.Xamarin { diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs new file mode 100644 index 00000000..b3616733 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs @@ -0,0 +1,176 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Globalization; +using Android.App; +using Android.Content; +using Android.Preferences; + +namespace LaunchDarkly.Xamarin.Preferences +{ + public static partial class Preferences + { + static readonly object locker = new object(); + + static bool PlatformContainsKey(string key, string sharedName) + { + lock (locker) + { + using (var sharedPreferences = GetSharedPreferences(sharedName)) + { + return sharedPreferences.Contains(key); + } + } + } + + static void PlatformRemove(string key, string sharedName) + { + lock (locker) + { + using (var sharedPreferences = GetSharedPreferences(sharedName)) + using (var editor = sharedPreferences.Edit()) + { + editor.Remove(key).Commit(); + } + } + } + + static void PlatformClear(string sharedName) + { + lock (locker) + { + using (var sharedPreferences = GetSharedPreferences(sharedName)) + using (var editor = sharedPreferences.Edit()) + { + editor.Clear().Commit(); + } + } + } + + static void PlatformSet(string key, T value, string sharedName) + { + lock (locker) + { + using (var sharedPreferences = GetSharedPreferences(sharedName)) + using (var editor = sharedPreferences.Edit()) + { + if (value == null) + { + editor.Remove(key); + } + else + { + switch (value) + { + case string s: + editor.PutString(key, s); + break; + case int i: + editor.PutInt(key, i); + break; + case bool b: + editor.PutBoolean(key, b); + break; + case long l: + editor.PutLong(key, l); + break; + case double d: + var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); + editor.PutString(key, valueString); + break; + case float f: + editor.PutFloat(key, f); + break; + } + } + editor.Apply(); + } + } + } + + static T PlatformGet(string key, T defaultValue, string sharedName) + { + lock (locker) + { + object value = null; + using (var sharedPreferences = GetSharedPreferences(sharedName)) + { + if (defaultValue == null) + { + value = sharedPreferences.GetString(key, null); + } + else + { + switch (defaultValue) + { + case int i: + value = sharedPreferences.GetInt(key, i); + break; + case bool b: + value = sharedPreferences.GetBoolean(key, b); + break; + case long l: + value = sharedPreferences.GetLong(key, l); + break; + case double d: + var savedDouble = sharedPreferences.GetString(key, null); + if (string.IsNullOrWhiteSpace(savedDouble)) + { + value = defaultValue; + } + else + { + if (!double.TryParse(savedDouble, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var outDouble)) + { + var maxString = Convert.ToString(double.MaxValue, CultureInfo.InvariantCulture); + outDouble = savedDouble.Equals(maxString) ? double.MaxValue : double.MinValue; + } + + value = outDouble; + } + break; + case float f: + value = sharedPreferences.GetFloat(key, f); + break; + case string s: + // the case when the string is not null + value = sharedPreferences.GetString(key, s); + break; + } + } + } + + return (T)value; + } + } + + static ISharedPreferences GetSharedPreferences(string sharedName) + { + var context = Application.Context; + + return string.IsNullOrWhiteSpace(sharedName) ? + PreferenceManager.GetDefaultSharedPreferences(context) : + context.GetSharedPreferences(sharedName, FileCreationMode.Private); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs new file mode 100644 index 00000000..ad508f29 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs @@ -0,0 +1,162 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Globalization; +using Foundation; + +namespace LaunchDarkly.Xamarin.Preferences +{ + public static partial class Preferences + { + static readonly object locker = new object(); + + static bool PlatformContainsKey(string key, string sharedName) + { + lock (locker) + { + return GetUserDefaults(sharedName)[key] != null; + } + } + + static void PlatformRemove(string key, string sharedName) + { + lock (locker) + { + using (var userDefaults = GetUserDefaults(sharedName)) + { + if (userDefaults[key] != null) + userDefaults.RemoveObject(key); + } + } + } + + static void PlatformClear(string sharedName) + { + lock (locker) + { + using (var userDefaults = GetUserDefaults(sharedName)) + { + var items = userDefaults.ToDictionary(); + + foreach (var item in items.Keys) + { + if (item is NSString nsString) + userDefaults.RemoveObject(nsString); + } + } + } + } + + static void PlatformSet(string key, T value, string sharedName) + { + lock (locker) + { + using (var userDefaults = GetUserDefaults(sharedName)) + { + if (value == null) + { + if (userDefaults[key] != null) + userDefaults.RemoveObject(key); + return; + } + + switch (value) + { + case string s: + userDefaults.SetString(s, key); + break; + case int i: + userDefaults.SetInt(i, key); + break; + case bool b: + userDefaults.SetBool(b, key); + break; + case long l: + var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); + userDefaults.SetString(valueString, key); + break; + case double d: + userDefaults.SetDouble(d, key); + break; + case float f: + userDefaults.SetFloat(f, key); + break; + } + } + } + } + + static T PlatformGet(string key, T defaultValue, string sharedName) + { + object value = null; + + lock (locker) + { + using (var userDefaults = GetUserDefaults(sharedName)) + { + if (userDefaults[key] == null) + return defaultValue; + + switch (defaultValue) + { + case int i: + value = (int)(nint)userDefaults.IntForKey(key); + break; + case bool b: + value = userDefaults.BoolForKey(key); + break; + case long l: + var savedLong = userDefaults.StringForKey(key); + value = Convert.ToInt64(savedLong, CultureInfo.InvariantCulture); + break; + case double d: + value = userDefaults.DoubleForKey(key); + break; + case float f: + value = userDefaults.FloatForKey(key); + break; + case string s: + // the case when the string is not null + value = userDefaults.StringForKey(key); + break; + default: + // the case when the string is null + if (typeof(T) == typeof(string)) + value = userDefaults.StringForKey(key); + break; + } + } + } + + return (T)value; + } + + static NSUserDefaults GetUserDefaults(string sharedName) + { + if (!string.IsNullOrWhiteSpace(sharedName)) + return new NSUserDefaults(sharedName, NSUserDefaultsType.SuiteName); + else + return NSUserDefaults.StandardUserDefaults; + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs new file mode 100644 index 00000000..b53f9bca --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs @@ -0,0 +1,42 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace LaunchDarkly.Xamarin.Preferences +{ + public static partial class Preferences + { + static bool PlatformContainsKey(string key, string sharedName) => + throw new System.NullReferenceException(); + + static void PlatformRemove(string key, string sharedName) => + throw new System.NullReferenceException(); + + static void PlatformClear(string sharedName) => + throw new System.NullReferenceException(); + + static void PlatformSet(string key, T value, string sharedName) => + throw new System.NullReferenceException(); + + static T PlatformGet(string key, T defaultValue, string sharedName) => + throw new System.NullReferenceException(); + } +} diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs new file mode 100644 index 00000000..6786e940 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs @@ -0,0 +1,140 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; + +namespace LaunchDarkly.Xamarin.Preferences +{ + public static partial class Preferences + { + internal static string GetPrivatePreferencesSharedName(string feature) => + $"LaunchDarkly.Xamarin.{feature}"; + + // overloads + + public static bool ContainsKey(string key) => + ContainsKey(key, null); + + public static void Remove(string key) => + Remove(key, null); + + public static void Clear() => + Clear(null); + + public static string Get(string key, string defaultValue) => + Get(key, defaultValue, null); + + public static bool Get(string key, bool defaultValue) => + Get(key, defaultValue, null); + + public static int Get(string key, int defaultValue) => + Get(key, defaultValue, null); + + public static double Get(string key, double defaultValue) => + Get(key, defaultValue, null); + + public static float Get(string key, float defaultValue) => + Get(key, defaultValue, null); + + public static long Get(string key, long defaultValue) => + Get(key, defaultValue, null); + + public static void Set(string key, string value) => + Set(key, value, null); + + public static void Set(string key, bool value) => + Set(key, value, null); + + public static void Set(string key, int value) => + Set(key, value, null); + + public static void Set(string key, double value) => + Set(key, value, null); + + public static void Set(string key, float value) => + Set(key, value, null); + + public static void Set(string key, long value) => + Set(key, value, null); + + // shared -> platform + + public static bool ContainsKey(string key, string sharedName) => + PlatformContainsKey(key, sharedName); + + public static void Remove(string key, string sharedName) => + PlatformRemove(key, sharedName); + + public static void Clear(string sharedName) => + PlatformClear(sharedName); + + public static string Get(string key, string defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static bool Get(string key, bool defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static int Get(string key, int defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static double Get(string key, double defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static float Get(string key, float defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static long Get(string key, long defaultValue, string sharedName) => + PlatformGet(key, defaultValue, sharedName); + + public static void Set(string key, string value, string sharedName) => + PlatformSet(key, value, sharedName); + + public static void Set(string key, bool value, string sharedName) => + PlatformSet(key, value, sharedName); + + public static void Set(string key, int value, string sharedName) => + PlatformSet(key, value, sharedName); + + public static void Set(string key, double value, string sharedName) => + PlatformSet(key, value, sharedName); + + public static void Set(string key, float value, string sharedName) => + PlatformSet(key, value, sharedName); + + public static void Set(string key, long value, string sharedName) => + PlatformSet(key, value, sharedName); + + // DateTime + + public static DateTime Get(string key, DateTime defaultValue) => + Get(key, defaultValue, null); + + public static void Set(string key, DateTime value) => + Set(key, value, null); + + public static DateTime Get(string key, DateTime defaultValue, string sharedName) => + DateTime.FromBinary(PlatformGet(key, defaultValue.ToBinary(), sharedName)); + + public static void Set(string key, DateTime value, string sharedName) => + PlatformSet(key, value.ToBinary(), sharedName); + } +} diff --git a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs index 5a0f86e1..821d7ce5 100644 --- a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs +++ b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs @@ -1,7 +1,4 @@ -using System; -using Xamarin.Essentials; - -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Xamarin { internal class SimpleMobileDevicePersistance : ISimplePersistance { diff --git a/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs b/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs index aa20773c..b3fc8ff3 100644 --- a/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs @@ -4,7 +4,6 @@ using LaunchDarkly.Client; using LaunchDarkly.Common; using Newtonsoft.Json; -using Xamarin.Essentials; namespace LaunchDarkly.Xamarin { diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index f93d0ab2..83335352 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -7,7 +7,6 @@ - From 3d015eb1f0968a242e424592f2d4aaf8578fce4d Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 5 Mar 2019 11:24:07 -0800 Subject: [PATCH 052/499] feat(README, LdClient.cs): Implemented Initialized(), removed python twisted from README --- README.md | 1 - src/LaunchDarkly.Xamarin/LdClient.cs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 609f27aa..23535490 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ About LaunchDarkly * [JavaScript](http://docs.launchdarkly.com/docs/js-sdk-reference "LaunchDarkly JavaScript SDK") * [PHP](http://docs.launchdarkly.com/docs/php-sdk-reference "LaunchDarkly PHP SDK") * [Python](http://docs.launchdarkly.com/docs/python-sdk-reference "LaunchDarkly Python SDK") - * [Python Twisted](http://docs.launchdarkly.com/docs/python-twisted-sdk-reference "LaunchDarkly Python Twisted SDK") * [Go](http://docs.launchdarkly.com/docs/go-sdk-reference "LaunchDarkly Go SDK") * [Node.JS](http://docs.launchdarkly.com/docs/node-sdk-reference "LaunchDarkly Node SDK") * [.NET](http://docs.launchdarkly.com/docs/dotnet-sdk-reference "LaunchDarkly .Net SDK") diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 6e6ffa82..03bb1588 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -428,12 +428,7 @@ public void Track(string eventName) /// public bool Initialized() { - //bool isInited = Instance != null; - //return isInited && Online; - // TODO: This method needs to be fixed to actually check whether the update processor has initialized. - // The previous logic (above) was meaningless because this method is not static, so by definition you - // do have a client instance if we've gotten here. But that doesn't mean it is initialized. - return Online; + return Online && updateProcessor.Initialized(); } /// From 95d4e1567ef13564ba5f14f970ebf58067fbc8a7 Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 5 Mar 2019 14:30:06 -0800 Subject: [PATCH 053/499] feat(src/): Added BackgroundAdapter/, changed conditional compilation a bunch, fixed a lot of build errors that weren't appearing before --- .../BackgroundAdapter.android.cs | 89 +++++++++++++++++++ .../BackgroundAdapter.ios.cs | 51 +++++++++++ .../BackgroundAdapter.netstandard.cs | 18 ++++ ...ientRunMode.cs => ClientRunMode.shared.cs} | 0 ...nfiguration.cs => Configuration.shared.cs} | 0 .../Connectivity/Connectivity.netstandard.cs | 10 ++- .../{Constants.cs => Constants.shared.cs} | 0 .../{DeviceInfo.cs => DeviceInfo.shared.cs} | 0 .../{Extensions.cs => Extensions.shared.cs} | 0 .../{Factory.cs => Factory.shared.cs} | 0 .../{FeatureFlag.cs => FeatureFlag.shared.cs} | 0 ...s => FeatureFlagListenerManager.shared.cs} | 0 ...stor.cs => FeatureFlagRequestor.shared.cs} | 0 ...eManager.cs => FlagCacheManager.shared.cs} | 0 ...State.cs => IBackgroundingState.shared.cs} | 0 ...anager.cs => IConnectionManager.shared.cs} | 0 .../{IDeviceInfo.cs => IDeviceInfo.shared.cs} | 0 ...ener.cs => IFeatureFlagListener.shared.cs} | 0 ... => IFeatureFlagListenerManager.shared.cs} | 0 ...Manager.cs => IFlagCacheManager.shared.cs} | 0 ...ileClient.cs => ILdMobileClient.shared.cs} | 0 ...tion.cs => IMobileConfiguration.shared.cs} | 0 ...or.cs => IMobileUpdateProcessor.shared.cs} | 0 ...mAdapter.cs => IPlatformAdapter.shared.cs} | 0 ...stance.cs => ISimplePersistance.shared.cs} | 0 ...rFlagCache.cs => IUserFlagCache.shared.cs} | 0 .../{LdClient.cs => LdClient.shared.cs} | 2 + ...r.cs => MobileConnectionManager.shared.cs} | 10 +-- ...or.cs => MobilePollingProcessor.shared.cs} | 0 ... => MobileSideClientEnvironment.shared.cs} | 0 ....cs => MobileStreamingProcessor.shared.cs} | 0 .../Preferences/Preferences.netstandard.cs | 12 +-- ...cs => SimpleInMemoryPersistance.shared.cs} | 0 ...> SimpleMobileDevicePersistance.shared.cs} | 12 +-- ...Cache.cs => UserFlagDeviceCache.shared.cs} | 0 ...che.cs => UserFlagInMemoryCache.shared.cs} | 0 36 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs create mode 100644 src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs rename src/LaunchDarkly.Xamarin/{ClientRunMode.cs => ClientRunMode.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{Configuration.cs => Configuration.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{Constants.cs => Constants.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{DeviceInfo.cs => DeviceInfo.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{Extensions.cs => Extensions.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{Factory.cs => Factory.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{FeatureFlag.cs => FeatureFlag.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{FeatureFlagListenerManager.cs => FeatureFlagListenerManager.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{FeatureFlagRequestor.cs => FeatureFlagRequestor.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{FlagCacheManager.cs => FlagCacheManager.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IBackgroundingState.cs => IBackgroundingState.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IConnectionManager.cs => IConnectionManager.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IDeviceInfo.cs => IDeviceInfo.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IFeatureFlagListener.cs => IFeatureFlagListener.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IFeatureFlagListenerManager.cs => IFeatureFlagListenerManager.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IFlagCacheManager.cs => IFlagCacheManager.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{ILdMobileClient.cs => ILdMobileClient.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IMobileConfiguration.cs => IMobileConfiguration.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IMobileUpdateProcessor.cs => IMobileUpdateProcessor.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IPlatformAdapter.cs => IPlatformAdapter.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{ISimplePersistance.cs => ISimplePersistance.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{IUserFlagCache.cs => IUserFlagCache.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{LdClient.cs => LdClient.shared.cs} (96%) rename src/LaunchDarkly.Xamarin/{MobileConnectionManager.cs => MobileConnectionManager.shared.cs} (70%) rename src/LaunchDarkly.Xamarin/{MobilePollingProcessor.cs => MobilePollingProcessor.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{MobileSideClientEnvironment.cs => MobileSideClientEnvironment.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{MobileStreamingProcessor.cs => MobileStreamingProcessor.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{SimpleInMemoryPersistance.cs => SimpleInMemoryPersistance.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{SimpleMobileDevicePersistance.cs => SimpleMobileDevicePersistance.shared.cs} (54%) rename src/LaunchDarkly.Xamarin/{UserFlagDeviceCache.cs => UserFlagDeviceCache.shared.cs} (100%) rename src/LaunchDarkly.Xamarin/{UserFlagInMemoryCache.cs => UserFlagInMemoryCache.shared.cs} (100%) diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs new file mode 100644 index 00000000..586168c0 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs @@ -0,0 +1,89 @@ +using System; +using LaunchDarkly.Xamarin; +using Android.App; +using Android.OS; + +namespace LaunchDarkly.Xamarin.BackgroundAdapter +{ + public class BackgroundAdapter : IPlatformAdapter + { + private static ActivityLifecycleCallbacks _callbacks; + private Application application; + + public void EnableBackgrounding(IBackgroundingState backgroundingState) + { + if (_callbacks == null) + { + _callbacks = new ActivityLifecycleCallbacks(backgroundingState); + application = (Application)Application.Context; + application.RegisterActivityLifecycleCallbacks(_callbacks); + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void _Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + application = null; + _callbacks = null; + + disposedValue = true; + } + } + + public void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + private class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks + { + private IBackgroundingState _backgroundingState; + + public ActivityLifecycleCallbacks(IBackgroundingState backgroundingState) + { + _backgroundingState = backgroundingState; + } + + public void OnActivityCreated(Activity activity, Bundle savedInstanceState) + { + } + + public void OnActivityDestroyed(Activity activity) + { + } + + public void OnActivityPaused(Activity activity) + { + _backgroundingState.EnterBackgroundAsync(); + } + + public void OnActivityResumed(Activity activity) + { + _backgroundingState.ExitBackgroundAsync(); + } + + public void OnActivitySaveInstanceState(Activity activity, Bundle outState) + { + } + + public void OnActivityStarted(Activity activity) + { + } + + public void OnActivityStopped(Activity activity) + { + } + } + } +} diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs new file mode 100644 index 00000000..47190d6c --- /dev/null +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -0,0 +1,51 @@ +using System; +using LaunchDarkly.Xamarin; +using UIKit; + +namespace LaunchDarkly.Xamarin.BackgroundAdapter +{ + public class BackgroundAdapter : UIApplicationDelegate, IPlatformAdapter + { + private IBackgroundingState _backgroundingState; + + public void EnableBackgrounding(IBackgroundingState backgroundingState) + { + _backgroundingState = backgroundingState; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected void _Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + _backgroundingState = null; + + disposedValue = true; + } + } + + public new void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + public override void WillEnterForeground(UIApplication application) + { + _backgroundingState.ExitBackgroundAsync(); + } + + public override void DidEnterBackground(UIApplication application) + { + _backgroundingState.EnterBackgroundAsync(); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs new file mode 100644 index 00000000..403d6eae --- /dev/null +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs @@ -0,0 +1,18 @@ +using System; +using LaunchDarkly.Xamarin; + +namespace LaunchDarkly.Xamarin.BackgroundAdapter +{ + public class BackgroundAdapter : IPlatformAdapter + { + public void Dispose() + { + throw new NotImplementedException(); + } + + public void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/ClientRunMode.cs b/src/LaunchDarkly.Xamarin/ClientRunMode.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ClientRunMode.cs rename to src/LaunchDarkly.Xamarin/ClientRunMode.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Configuration.cs rename to src/LaunchDarkly.Xamarin/Configuration.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs index 944ac838..38af6cec 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs @@ -20,6 +20,8 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System; + using System.Collections.Generic; namespace LaunchDarkly.Xamarin.Connectivity @@ -27,15 +29,15 @@ namespace LaunchDarkly.Xamarin.Connectivity public static partial class Connectivity { static NetworkAccess PlatformNetworkAccess => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static IEnumerable PlatformConnectionProfiles => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static void StartListeners() => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static void StopListeners() => - throw new System.NullReferenceException(); + throw new NotImplementedException(); } } diff --git a/src/LaunchDarkly.Xamarin/Constants.cs b/src/LaunchDarkly.Xamarin/Constants.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Constants.cs rename to src/LaunchDarkly.Xamarin/Constants.shared.cs diff --git a/src/LaunchDarkly.Xamarin/DeviceInfo.cs b/src/LaunchDarkly.Xamarin/DeviceInfo.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/DeviceInfo.cs rename to src/LaunchDarkly.Xamarin/DeviceInfo.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Extensions.cs b/src/LaunchDarkly.Xamarin/Extensions.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Extensions.cs rename to src/LaunchDarkly.Xamarin/Extensions.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Factory.cs b/src/LaunchDarkly.Xamarin/Factory.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Factory.cs rename to src/LaunchDarkly.Xamarin/Factory.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlag.cs b/src/LaunchDarkly.Xamarin/FeatureFlag.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlag.cs rename to src/LaunchDarkly.Xamarin/FeatureFlag.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.cs b/src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.cs rename to src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs b/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs rename to src/LaunchDarkly.Xamarin/FeatureFlagRequestor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FlagCacheManager.cs b/src/LaunchDarkly.Xamarin/FlagCacheManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FlagCacheManager.cs rename to src/LaunchDarkly.Xamarin/FlagCacheManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IBackgroundingState.cs b/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IBackgroundingState.cs rename to src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IConnectionManager.cs b/src/LaunchDarkly.Xamarin/IConnectionManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IConnectionManager.cs rename to src/LaunchDarkly.Xamarin/IConnectionManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IDeviceInfo.cs b/src/LaunchDarkly.Xamarin/IDeviceInfo.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IDeviceInfo.cs rename to src/LaunchDarkly.Xamarin/IDeviceInfo.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFeatureFlagListener.cs b/src/LaunchDarkly.Xamarin/IFeatureFlagListener.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFeatureFlagListener.cs rename to src/LaunchDarkly.Xamarin/IFeatureFlagListener.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.cs b/src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.cs rename to src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFlagCacheManager.cs b/src/LaunchDarkly.Xamarin/IFlagCacheManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFlagCacheManager.cs rename to src/LaunchDarkly.Xamarin/IFlagCacheManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/ILdMobileClient.cs b/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ILdMobileClient.cs rename to src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IMobileConfiguration.cs b/src/LaunchDarkly.Xamarin/IMobileConfiguration.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IMobileConfiguration.cs rename to src/LaunchDarkly.Xamarin/IMobileConfiguration.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.cs b/src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.cs rename to src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.cs b/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IPlatformAdapter.cs rename to src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs diff --git a/src/LaunchDarkly.Xamarin/ISimplePersistance.cs b/src/LaunchDarkly.Xamarin/ISimplePersistance.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ISimplePersistance.cs rename to src/LaunchDarkly.Xamarin/ISimplePersistance.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IUserFlagCache.cs b/src/LaunchDarkly.Xamarin/IUserFlagCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IUserFlagCache.cs rename to src/LaunchDarkly.Xamarin/IUserFlagCache.shared.cs diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs similarity index 96% rename from src/LaunchDarkly.Xamarin/LdClient.cs rename to src/LaunchDarkly.Xamarin/LdClient.shared.cs index 03bb1588..aba76010 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -69,6 +69,8 @@ public sealed class LdClient : ILdMobileClient throw new ArgumentNullException("user"); } + configuration.PlatformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); + Config = configuration; connectionLock = new SemaphoreSlim(1, 1); diff --git a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs b/src/LaunchDarkly.Xamarin/MobileConnectionManager.shared.cs similarity index 70% rename from src/LaunchDarkly.Xamarin/MobileConnectionManager.cs rename to src/LaunchDarkly.Xamarin/MobileConnectionManager.shared.cs index f9453560..c3c835c2 100644 --- a/src/LaunchDarkly.Xamarin/MobileConnectionManager.cs +++ b/src/LaunchDarkly.Xamarin/MobileConnectionManager.shared.cs @@ -11,9 +11,9 @@ internal MobileConnectionManager() UpdateConnectedStatus(); try { - Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + LaunchDarkly.Xamarin.Connectivity.Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } - catch (NotImplementedInReferenceAssemblyException) + catch (NotImplementedException) { } } @@ -27,7 +27,7 @@ bool IConnectionManager.IsConnected } } - void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e) + void Connectivity_ConnectivityChanged(object sender, Connectivity.ConnectivityChangedEventArgs e) { UpdateConnectedStatus(); ConnectionChanged?.Invoke(isConnected); @@ -37,9 +37,9 @@ private void UpdateConnectedStatus() { try { - isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; + isConnected = LaunchDarkly.Xamarin.Connectivity.Connectivity.NetworkAccess == LaunchDarkly.Xamarin.Connectivity.NetworkAccess.Internet; } - catch (NotImplementedInReferenceAssemblyException) + catch (NotImplementedException) { // .NET Standard has no way to detect network connectivity isConnected = true; diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobilePollingProcessor.cs rename to src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.cs b/src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.cs rename to src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs b/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs rename to src/LaunchDarkly.Xamarin/MobileStreamingProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs index b53f9bca..02694894 100644 --- a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs @@ -20,23 +20,25 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System; + namespace LaunchDarkly.Xamarin.Preferences { public static partial class Preferences { static bool PlatformContainsKey(string key, string sharedName) => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static void PlatformRemove(string key, string sharedName) => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static void PlatformClear(string sharedName) => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static void PlatformSet(string key, T value, string sharedName) => - throw new System.NullReferenceException(); + throw new NotImplementedException(); static T PlatformGet(string key, T defaultValue, string sharedName) => - throw new System.NullReferenceException(); + throw new NotImplementedException(); } } diff --git a/src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.cs b/src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.cs rename to src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.shared.cs diff --git a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.shared.cs similarity index 54% rename from src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs rename to src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.shared.cs index 821d7ce5..5809d1b3 100644 --- a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.cs +++ b/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.shared.cs @@ -1,4 +1,6 @@ -namespace LaunchDarkly.Xamarin +using System; + +namespace LaunchDarkly.Xamarin { internal class SimpleMobileDevicePersistance : ISimplePersistance { @@ -6,18 +8,18 @@ public void Save(string key, string value) { try { - Preferences.Set(key, value); + LaunchDarkly.Xamarin.Preferences.Preferences.Set(key, value); } - catch (NotImplementedInReferenceAssemblyException) { } + catch (NotImplementedException) { } } public string GetValue(string key) { try { - return Preferences.Get(key, null); + return LaunchDarkly.Xamarin.Preferences.Preferences.Get(key, null); } - catch (NotImplementedInReferenceAssemblyException) + catch (NotImplementedException) { return null; } diff --git a/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs b/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserFlagDeviceCache.cs rename to src/LaunchDarkly.Xamarin/UserFlagDeviceCache.shared.cs diff --git a/src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.cs b/src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.cs rename to src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.shared.cs From 791531cf267e6a8d90a6d34caa500e80867e0f77 Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 5 Mar 2019 16:52:44 -0800 Subject: [PATCH 054/499] fix(src/): AssemblyInfo wasn't compiling, removed unnecessary public key --- LaunchDarkly.Xamarin.pk | Bin 160 -> 0 bytes .../LaunchDarkly.Xamarin.csproj | 5 +---- .../{AssemblyInfo.cs => AssemblyInfo.shared.cs} | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 LaunchDarkly.Xamarin.pk rename src/LaunchDarkly.Xamarin/Properties/{AssemblyInfo.cs => AssemblyInfo.shared.cs} (99%) diff --git a/LaunchDarkly.Xamarin.pk b/LaunchDarkly.Xamarin.pk deleted file mode 100644 index d8290e41aed16ba3e3752701be6d9a3c063507f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa500966iXFVm!$NhP$%`JV zq(*!F>INn$@)FN{W}otFPA~Js2q`3oyfmANXnk`7Kk-e!h8`a3BQQE!T@$g}14o$? zy%Gzfa?h3>C)=N8CNWlDPKy6ZdEp%XaX2#-e#>3Uz%6$vXW1F}@Nd52C#GcFSs60b OuwPA~$U~O`?ESGf_CA>a diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 98055e71..b1463d06 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -25,11 +25,8 @@ - - - - + diff --git a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs similarity index 99% rename from src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs rename to src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs index 7d76c4d2..fcb57d2a 100644 --- a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.cs +++ b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs @@ -2,4 +2,3 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Tests")] - From 18ea6f8efdcf9a41941584a9e4d5789a83a52e11 Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 5 Mar 2019 17:09:55 -0800 Subject: [PATCH 055/499] fix(LdClient.shared.cs): Fixed unit tests by catching NotImplementedException thrown by BackgroundAdapter.netstandard.cs --- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index aba76010..272e1f4a 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -232,7 +232,14 @@ static void CreateInstance(Configuration configuration, User user) { bgPollInterval = configuration.BackgroundPollingInterval; } - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), bgPollInterval); + try + { + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), bgPollInterval); + } + catch + { + Log.Info("Foreground/Background is only available on iOS and Android"); + } } bool StartUpdateProcessor(TimeSpan maxWaitTime) @@ -531,7 +538,14 @@ void Dispose(bool disposing) if (disposing) { Log.InfoFormat("Shutting down the LaunchDarkly client"); - platformAdapter.Dispose(); + try + { + platformAdapter.Dispose(); + } + catch + { + Log.Info("Foreground/Background is only available on iOS and Android"); + } updateProcessor.Dispose(); eventProcessor.Dispose(); } From 40fa90921a5ac9da0b3aca4be202045d7929950f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 12 Mar 2019 19:29:37 -0700 Subject: [PATCH 056/499] implement evaluation reasons --- src/LaunchDarkly.Xamarin/Configuration.cs | 26 +++- src/LaunchDarkly.Xamarin/FeatureFlag.cs | 31 +++- .../FeatureFlagRequestor.cs | 36 ++--- src/LaunchDarkly.Xamarin/ILdMobileClient.cs | 50 +++++++ .../IMobileConfiguration.cs | 10 ++ .../LaunchDarkly.Xamarin.csproj | 2 +- src/LaunchDarkly.Xamarin/LdClient.cs | 140 ++++++++++-------- .../MobileStreamingProcessor.cs | 35 ++--- src/LaunchDarkly.Xamarin/ValueType.cs | 81 ++++++++++ .../FeatureFlagTests.cs | 4 +- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- .../LdClientEvaluationTests.cs | 89 +++++++++++ .../LdClientEventTests.cs | 84 +++++++++++ .../MobileStreamingProcessorTests.cs | 78 ++++++++-- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 12 +- 15 files changed, 556 insertions(+), 124 deletions(-) create mode 100644 src/LaunchDarkly.Xamarin/ValueType.cs diff --git a/src/LaunchDarkly.Xamarin/Configuration.cs b/src/LaunchDarkly.Xamarin/Configuration.cs index 0435e21e..126a6123 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.cs @@ -108,7 +108,15 @@ public class Configuration : IMobileConfiguration /// True if full user details should be included in every analytics event. The default is false (events will /// only include the user key, except for one "index" event that provides the full details for the user). /// - public bool InlineUsersInEvents { get; internal set; } + public bool InlineUsersInEvents { get; internal set; } + /// + /// True if LaunchDarkly should provide additional information about how flag values were + /// calculated. The additional information will then be available through the client's "detail" + /// methods such as . Since this + /// increases the size of network requests, such information is not sent unless you set this option + /// to true. + /// + public bool EvaluationReasons { get; internal set; } /// public TimeSpan BackgroundPollingInterval { get; internal set; } /// @@ -581,6 +589,22 @@ public static Configuration WithUseReport(this Configuration configuration, bool return configuration; } + /// + /// Set to true if LaunchDarkly should provide additional information about how flag values were + /// calculated. The additional information will then be available through the client's "detail" + /// methods such as . Since this + /// increases the size of network requests, such information is not sent unless you set this option + /// to true. + /// + /// Configuration. + /// True if evaluation reasons are desired. + /// the same Configuration instance + public static Configuration WithEvaluationReasons(this Configuration configuration, bool evaluationReasons) + { + configuration.EvaluationReasons = evaluationReasons; + return configuration; + } + /// /// Sets the IMobileUpdateProcessor instance, used internally for stubbing mock instances. /// diff --git a/src/LaunchDarkly.Xamarin/FeatureFlag.cs b/src/LaunchDarkly.Xamarin/FeatureFlag.cs index bd37ecbf..b6b67d5c 100644 --- a/src/LaunchDarkly.Xamarin/FeatureFlag.cs +++ b/src/LaunchDarkly.Xamarin/FeatureFlag.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json.Linq; +using LaunchDarkly.Client; using LaunchDarkly.Common; namespace LaunchDarkly.Xamarin @@ -10,8 +11,10 @@ public class FeatureFlag : IEquatable public int version; public int? flagVersion; public bool trackEvents; + public bool trackReason; public int? variation; public long? debugEventsUntilDate; + public EvaluationReason reason; public bool Equals(FeatureFlag otherFlag) { @@ -20,22 +23,26 @@ public bool Equals(FeatureFlag otherFlag) && flagVersion == otherFlag.flagVersion && trackEvents == otherFlag.trackEvents && variation == otherFlag.variation - && debugEventsUntilDate == otherFlag.debugEventsUntilDate; + && debugEventsUntilDate == otherFlag.debugEventsUntilDate + && reason == otherFlag.reason; } } - internal class FeatureFlagEvent : IFlagEventProperties + /// + /// The IFlagEventProperties abstraction is used by LaunchDarkly.Common to communicate properties + /// that affect event generation. We can't just have FeatureFlag itself implement that interface, + /// because it doesn't actually contain its own flag key. + /// + internal struct FeatureFlagEvent : IFlagEventProperties { - private FeatureFlag _featureFlag; - private string _key; + private readonly FeatureFlag _featureFlag; + private readonly string _key; public static FeatureFlagEvent Default(string key) { return new FeatureFlagEvent(key, new FeatureFlag()); } - - private FeatureFlagEvent() { } - + public FeatureFlagEvent(string key, FeatureFlag featureFlag) { _featureFlag = featureFlag; @@ -43,8 +50,16 @@ public FeatureFlagEvent(string key, FeatureFlag featureFlag) } public string Key => _key; - public int Version => _featureFlag.flagVersion ?? _featureFlag.version; + public int EventVersion => _featureFlag.flagVersion ?? _featureFlag.version; public bool TrackEvents => _featureFlag.trackEvents; public long? DebugEventsUntilDate => _featureFlag.debugEventsUntilDate; + + public bool IsExperiment(EvaluationReason reason) + { + // EventFactory passes the reason parameter to this method because the server-side SDK needs to + // look at the reason; but in this client-side SDK, we don't look at that parameter, because + // LD has already done the relevant calculation for us and sent us the result in trackReason. + return _featureFlag.trackReason; + } } } diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs b/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs index 1e8b5982..14c609a4 100644 --- a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs @@ -35,9 +35,11 @@ internal interface IFeatureFlagRequestor : IDisposable internal class FeatureFlagRequestor : IFeatureFlagRequestor { private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagRequestor)); + private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); + private readonly IMobileConfiguration _configuration; private readonly User _currentUser; - private volatile HttpClient _httpClient; + private readonly HttpClient _httpClient; private volatile EntityTagHeaderValue _etag; internal FeatureFlagRequestor(IMobileConfiguration configuration, User user) @@ -49,36 +51,34 @@ internal FeatureFlagRequestor(IMobileConfiguration configuration, User user) public async Task FeatureFlagsAsync() { - HttpRequestMessage requestMessage; - if (_configuration.UseReport) - { - requestMessage = ReportRequestMessage(); - } - else - { - requestMessage = GetRequestMessage(); - } - + var requestMessage = _configuration.UseReport ? ReportRequestMessage() : GetRequestMessage(); return await MakeRequest(requestMessage); } private HttpRequestMessage GetRequestMessage() { - var encodedUser = _currentUser.AsJson().Base64Encode(); - var requestUrlPath = _configuration.BaseUri + Constants.FLAG_REQUEST_PATH_GET + encodedUser; - var request = new HttpRequestMessage(HttpMethod.Get, requestUrlPath); - return request; + var path = Constants.FLAG_REQUEST_PATH_GET + _currentUser.AsJson().Base64Encode(); + return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } private HttpRequestMessage ReportRequestMessage() { - var requestUrlPath = _configuration.BaseUri + Constants.FLAG_REQUEST_PATH_REPORT; - var request = new HttpRequestMessage(new HttpMethod("REPORT"), requestUrlPath); + var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(Constants.FLAG_REQUEST_PATH_REPORT)); request.Content = new StringContent(_currentUser.AsJson(), Encoding.UTF8, Constants.APPLICATION_JSON); - return request; } + private Uri MakeRequestUriWithPath(string path) + { + var uri = new UriBuilder(_configuration.BaseUri); + uri.Path = path; + if (_configuration.EvaluationReasons) + { + uri.Query = "withReasons=true"; + } + return uri.Uri; + } + private async Task MakeRequest(HttpRequestMessage request) { var cts = new CancellationTokenSource(_configuration.HttpClientTimeout); diff --git a/src/LaunchDarkly.Xamarin/ILdMobileClient.cs b/src/LaunchDarkly.Xamarin/ILdMobileClient.cs index 39e7b26d..9f13e1f1 100644 --- a/src/LaunchDarkly.Xamarin/ILdMobileClient.cs +++ b/src/LaunchDarkly.Xamarin/ILdMobileClient.cs @@ -23,6 +23,16 @@ public interface ILdMobileClient : ILdCommonClient /// disabled in the LaunchDarkly control panel bool BoolVariation(string key, bool defaultValue = false); + /// + /// Returns the boolean value of a feature flag for a given flag key, in an object that also + /// describes the way the value was determined. The Reason property in the result will + /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false); + /// /// Returns the string value of a feature flag for a given flag key. /// @@ -32,6 +42,16 @@ public interface ILdMobileClient : ILdCommonClient /// disabled in the LaunchDarkly control panel string StringVariation(string key, string defaultValue); + /// + /// Returns the string value of a feature flag for a given flag key, in an object that also + /// describes the way the value was determined. The Reason property in the result will + /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail StringVariationDetail(string key, string defaultValue); + /// /// Returns the float value of a feature flag for a given flag key. /// @@ -41,6 +61,16 @@ public interface ILdMobileClient : ILdCommonClient /// disabled in the LaunchDarkly control panel float FloatVariation(string key, float defaultValue = 0); + /// + /// Returns the float value of a feature flag for a given flag key, in an object that also + /// describes the way the value was determined. The Reason property in the result will + /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0); + /// /// Returns the integer value of a feature flag for a given flag key. /// @@ -50,6 +80,16 @@ public interface ILdMobileClient : ILdCommonClient /// disabled in the LaunchDarkly control panel int IntVariation(string key, int defaultValue = 0); + /// + /// Returns the integer value of a feature flag for a given flag key, in an object that also + /// describes the way the value was determined. The Reason property in the result will + /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail IntVariationDetail(string key, int defaultValue = 0); + /// /// Returns the JToken value of a feature flag for a given flag key. /// @@ -59,6 +99,16 @@ public interface ILdMobileClient : ILdCommonClient /// disabled in the LaunchDarkly control panel JToken JsonVariation(string key, JToken defaultValue); + /// + /// Returns the JToken value of a feature flag for a given flag key, in an object that also + /// describes the way the value was determined. The Reason property in the result will + /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail JsonVariationDetail(string key, JToken defaultValue); + /// /// Tracks that current user performed an event for the given JToken value and given event name. /// diff --git a/src/LaunchDarkly.Xamarin/IMobileConfiguration.cs b/src/LaunchDarkly.Xamarin/IMobileConfiguration.cs index 50341113..8a98fde0 100644 --- a/src/LaunchDarkly.Xamarin/IMobileConfiguration.cs +++ b/src/LaunchDarkly.Xamarin/IMobileConfiguration.cs @@ -41,5 +41,15 @@ public interface IMobileConfiguration : IBaseConfiguration /// /// true if use report; otherwise, false. bool UseReport { get; } + + /// + /// True if LaunchDarkly should provide additional information about how flag values were + /// calculated. The additional information will then be available through the client's "detail" + /// methods such as . Since this + /// increases the size of network requests, such information is not sent unless you set this option + /// to true. + /// + /// true if evaluation reasons are desired. + bool EvaluationReasons { get; } } } diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 89f657a8..6881df12 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/LdClient.cs b/src/LaunchDarkly.Xamarin/LdClient.cs index 6e6ffa82..cbbb1fa3 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.cs @@ -48,7 +48,8 @@ public sealed class LdClient : ILdMobileClient IEventProcessor eventProcessor; ISimplePersistance persister; IDeviceInfo deviceInfo; - EventFactory eventFactory = EventFactory.Default; + readonly EventFactory eventFactoryDefault = EventFactory.Default; + readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; IFeatureFlagListenerManager flagListenerManager; IPlatformAdapter platformAdapter; @@ -93,7 +94,7 @@ public sealed class LdClient : ILdMobileClient updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager); eventProcessor = Factory.CreateEventProcessor(configuration); - eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(User)); + eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); SetupConnectionManager(); } @@ -304,95 +305,112 @@ void MobileConnectionManager_ConnectionChanged(bool isOnline) /// public bool BoolVariation(string key, bool defaultValue = false) { - return VariationWithType(key, defaultValue, JTokenType.Boolean).Value(); + return VariationInternal(key, defaultValue, ValueType.Bool, eventFactoryDefault).Value; + } + + /// + public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) + { + return VariationInternal(key, defaultValue, ValueType.Bool, eventFactoryWithReasons); } /// public string StringVariation(string key, string defaultValue) { - var value = VariationWithType(key, defaultValue, JTokenType.String); - if (value != null) - { - return value.Value(); - } - - return null; + return VariationInternal(key, defaultValue, ValueType.String, eventFactoryDefault).Value; + } + + /// + public EvaluationDetail StringVariationDetail(string key, string defaultValue) + { + return VariationInternal(key, defaultValue, ValueType.String, eventFactoryWithReasons); } /// public float FloatVariation(string key, float defaultValue = 0) { - return VariationWithType(key, defaultValue, JTokenType.Float).Value(); + return VariationInternal(key, defaultValue, ValueType.Float, eventFactoryDefault).Value; + } + + /// + public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueType.Float, eventFactoryWithReasons); } /// public int IntVariation(string key, int defaultValue = 0) { - return VariationWithType(key, defaultValue, JTokenType.Integer).Value(); + return VariationInternal(key, defaultValue, ValueType.Int, eventFactoryDefault).Value; + } + + /// + public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueType.Int, eventFactoryWithReasons); } /// public JToken JsonVariation(string key, JToken defaultValue) { - return VariationWithType(key, defaultValue, null); - } - - JToken VariationWithType(string featureKey, JToken defaultValue, JTokenType? jtokenType) + return VariationInternal(key, defaultValue, ValueType.Json, eventFactoryDefault).Value; + } + + /// + public EvaluationDetail JsonVariationDetail(string key, JToken defaultValue) { - var returnedFlagValue = Variation(featureKey, defaultValue); - if (returnedFlagValue != null && jtokenType != null && !returnedFlagValue.Type.Equals(jtokenType)) - { - Log.ErrorFormat("Expected type: {0} but got {1} when evaluating FeatureFlag: {2}. Returning default", - jtokenType, - returnedFlagValue.Type, - featureKey); - - return defaultValue; - } - - return returnedFlagValue; + return VariationInternal(key, defaultValue, ValueType.Json, eventFactoryWithReasons); } - JToken Variation(string featureKey, JToken defaultValue) + EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) { FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); - FeatureRequestEvent featureRequestEvent; + JToken defaultJson = desiredType.ValueToJson(defaultValue); + + EvaluationDetail errorResult(EvaluationErrorKind kind) => + new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); if (!Initialized()) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); - return defaultValue; - } - + return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); + } + var flag = flagCacheManager.FlagForUser(featureKey, User); - if (flag != null) - { - featureFlagEvent = new FeatureFlagEvent(featureKey, flag); - var value = flag.value; - if (value == null || value.Type == JTokenType.Null) { - featureRequestEvent = eventFactory.NewDefaultFeatureRequestEvent(featureFlagEvent, - User, - defaultValue, - EvaluationErrorKind.FLAG_NOT_FOUND); - value = defaultValue; - } else { - featureRequestEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, - User, - new EvaluationDetail(flag.value, flag.variation, null), - defaultValue); - } - eventProcessor.SendEvent(featureRequestEvent); - return value; + if (flag == null) + { + Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); + eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + EvaluationErrorKind.FLAG_NOT_FOUND)); + return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); } - Log.InfoFormat("Unknown feature flag {0}; returning default value", - featureKey); - featureRequestEvent = eventFactory.NewUnknownFeatureRequestEvent(featureKey, - User, - defaultValue, - EvaluationErrorKind.FLAG_NOT_FOUND); - eventProcessor.SendEvent(featureRequestEvent); - return defaultValue; + featureFlagEvent = new FeatureFlagEvent(featureKey, flag); + EvaluationDetail result; + JToken valueJson; + if (flag.value == null || flag.value.Type == JTokenType.Null) + { + valueJson = defaultJson; + result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); + } + else + { + try + { + valueJson = flag.value; + var value = desiredType.ValueFromJson(flag.value); + result = new EvaluationDetail(value, flag.variation, flag.reason); + } + catch (Exception) + { + valueJson = defaultJson; + result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + } + } + var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, + new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); + eventProcessor.SendEvent(featureEvent); + return result; } /// @@ -416,7 +434,7 @@ public IDictionary AllFlags() /// public void Track(string eventName, JToken data) { - eventProcessor.SendEvent(eventFactory.NewCustomEvent(eventName, User, data)); + eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data)); } /// @@ -489,7 +507,7 @@ public async Task IdentifyAsync(User user) connectionLock.Release(); } - eventProcessor.SendEvent(eventFactory.NewIdentifyEvent(userWithKey)); + eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(userWithKey)); } async Task RestartUpdateProcessorAsync() diff --git a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs b/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs index 1cd8c9b9..47a66423 100644 --- a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.cs @@ -14,6 +14,7 @@ namespace LaunchDarkly.Xamarin internal class MobileStreamingProcessor : IMobileUpdateProcessor, IStreamProcessor { private static readonly ILog Log = LogManager.GetLogger(typeof(MobileStreamingProcessor)); + private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); private readonly IMobileConfiguration _configuration; private readonly IFlagCacheManager _cacheManager; @@ -29,15 +30,7 @@ internal MobileStreamingProcessor(IMobileConfiguration configuration, this._cacheManager = cacheManager; this._user = user; - StreamProperties streamProperties; - if (_configuration.UseReport) - { - streamProperties = MakeStreamPropertiesForREPORT(); - } - else - { - streamProperties = MakeStreamPropertiesForGET(); - } + var streamProperties = _configuration.UseReport ? MakeStreamPropertiesForReport() : MakeStreamPropertiesForGet(); _streamManager = new StreamManager(this, streamProperties, @@ -60,20 +53,28 @@ Task IMobileUpdateProcessor.Start() #endregion - private StreamProperties MakeStreamPropertiesForGET() + private StreamProperties MakeStreamPropertiesForGet() { var userEncoded = _user.AsJson().Base64Encode(); - Uri uri = new Uri(_configuration.StreamUri, Constants.STREAM_REQUEST_PATH + userEncoded); - return new StreamProperties(uri, HttpMethod.Get, null); + var path = Constants.STREAM_REQUEST_PATH + userEncoded; + return new StreamProperties(MakeRequestUriWithPath(path), HttpMethod.Get, null); } - private StreamProperties MakeStreamPropertiesForREPORT() + private StreamProperties MakeStreamPropertiesForReport() { - var userEncoded = _user.AsJson(); - Uri uri = new Uri(_configuration.StreamUri, Constants.STREAM_REQUEST_PATH); var content = new StringContent(_user.AsJson(), Encoding.UTF8, Constants.APPLICATION_JSON); - var method = new HttpMethod("REPORT"); - return new StreamProperties(uri, method, content); + return new StreamProperties(MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), ReportMethod, content); + } + + private Uri MakeRequestUriWithPath(string path) + { + var uri = new UriBuilder(_configuration.StreamUri); + uri.Path = path; + if (_configuration.EvaluationReasons) + { + uri.Query = "withReasons=true"; + } + return uri.Uri; } #region IStreamProcessor diff --git a/src/LaunchDarkly.Xamarin/ValueType.cs b/src/LaunchDarkly.Xamarin/ValueType.cs new file mode 100644 index 00000000..3874f81e --- /dev/null +++ b/src/LaunchDarkly.Xamarin/ValueType.cs @@ -0,0 +1,81 @@ +using System; +using Newtonsoft.Json.Linq; + +namespace LaunchDarkly.Xamarin +{ + internal class ValueType + { + public Func ValueFromJson { get; internal set; } + public Func ValueToJson { get; internal set; } + } + + internal class ValueType + { + private static ArgumentException BadTypeException() + { + return new ArgumentException("unexpected data type"); + } + + public static ValueType Bool = new ValueType + { + ValueFromJson = json => + { + if (json.Type != JTokenType.Boolean) + { + throw BadTypeException(); + } + return json.Value(); + }, + ValueToJson = value => new JValue(value) + }; + + public static ValueType Int = new ValueType + { + ValueFromJson = json => + { + if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) + { + throw BadTypeException(); + } + return json.Value(); + }, + ValueToJson = value => new JValue(value) + }; + + public static ValueType Float = new ValueType + { + ValueFromJson = json => + { + if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) + { + throw BadTypeException(); + } + return json.Value(); + }, + ValueToJson = value => new JValue(value) + }; + + public static ValueType String = new ValueType + { + ValueFromJson = json => + { + if (json == null || json.Type == JTokenType.Null) + { + return null; // strings are always nullable + } + if (json.Type != JTokenType.String) + { + throw BadTypeException(); + } + return json.Value(); + }, + ValueToJson = value => value == null ? null : new JValue(value) + }; + + public static ValueType Json = new ValueType + { + ValueFromJson = json => json, + ValueToJson = value => value + }; + } +} diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs index bd990c54..5b804a88 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs @@ -14,7 +14,7 @@ public void ReturnsFlagVersionAsVersion() flag.flagVersion = 123; flag.version = 456; var flagEvent = new FeatureFlagEvent("my-flag", flag); - Assert.Equal(123, flagEvent.Version); + Assert.Equal(123, flagEvent.EventVersion); } [Fact] @@ -23,7 +23,7 @@ public void FallsBackToVersionAsVersion() var flag = new FeatureFlag(); flag.version = 456; var flagEvent = new FeatureFlagEvent("my-flag", flag); - Assert.Equal(456, flagEvent.Version); + Assert.Equal(456, flagEvent.EventVersion); } } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index f93d0ab2..2045f2a2 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs index 78d9fe9a..84bc3bb0 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs @@ -36,6 +36,17 @@ public void BoolVariationReturnsDefaultForUnknownFlag() Assert.False(client.BoolVariation(nonexistentFlagKey)); } + [Fact] + public void BoolVariationDetailReturnsValue() + { + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true), 1, reason); + var client = ClientWithFlagsJson(flagsJson); + + var expected = new EvaluationDetail(true, 1, reason); + Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); + } + [Fact] public void IntVariationReturnsValue() { @@ -45,6 +56,15 @@ public void IntVariationReturnsValue() Assert.Equal(3, client.IntVariation("flag-key", 0)); } + [Fact] + public void IntVariationCoercesFloatValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3.0f)); + var client = ClientWithFlagsJson(flagsJson); + + Assert.Equal(3, client.IntVariation("flag-key", 0)); + } + [Fact] public void IntVariationReturnsDefaultForUnknownFlag() { @@ -52,6 +72,17 @@ public void IntVariationReturnsDefaultForUnknownFlag() Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); } + [Fact] + public void IntVariationDetailReturnsValue() + { + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3), 1, reason); + var client = ClientWithFlagsJson(flagsJson); + + var expected = new EvaluationDetail(3, 1, reason); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); + } + [Fact] public void FloatVariationReturnsValue() { @@ -61,6 +92,15 @@ public void FloatVariationReturnsValue() Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); } + [Fact] + public void FloatVariationCoercesIntValue() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2)); + var client = ClientWithFlagsJson(flagsJson); + + Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); + } + [Fact] public void FloatVariationReturnsDefaultForUnknownFlag() { @@ -68,6 +108,17 @@ public void FloatVariationReturnsDefaultForUnknownFlag() Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); } + [Fact] + public void FloatVariationDetailReturnsValue() + { + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f), 1, reason); + var client = ClientWithFlagsJson(flagsJson); + + var expected = new EvaluationDetail(2.5f, 1, reason); + Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); + } + [Fact] public void StringVariationReturnsValue() { @@ -84,6 +135,17 @@ public void StringVariationReturnsDefaultForUnknownFlag() Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); } + [Fact] + public void StringVariationDetailReturnsValue() + { + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value"), 1, reason); + var client = ClientWithFlagsJson(flagsJson); + + var expected = new EvaluationDetail("string value", 1, reason); + Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); + } + [Fact] public void JsonVariationReturnsValue() { @@ -102,6 +164,22 @@ public void JsonVariationReturnsDefaultForUnknownFlag() Assert.Null(client.JsonVariation(nonexistentFlagKey, null)); } + [Fact] + public void JsonVariationDetailReturnsValue() + { + var jsonValue = new JObject { { "thing", new JValue("stuff") } }; + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); + var client = ClientWithFlagsJson(flagsJson); + + var expected = new EvaluationDetail(jsonValue, 1, reason); + var result = client.JsonVariationDetail("flag-key", new JValue(3)); + // Note, JToken.Equals() doesn't work, so we need to test each property separately + Assert.True(JToken.DeepEquals(expected.Value, result.Value)); + Assert.Equal(expected.VariationIndex, result.VariationIndex); + Assert.Equal(expected.Reason, result.Reason); + } + [Fact] public void AllFlagsReturnsAllFlagValues() { @@ -124,6 +202,17 @@ public void DefaultValueReturnedIfValueTypeIsDifferent() Assert.Equal(3, client.IntVariation("flag-key", 3)); } + [Fact] + public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() + { + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); + var client = TestUtil.CreateClient(config, user); + + var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); + } + [Fact] public void DefaultValueReturnedIfFlagValueIsNull() { diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs index 074605e4..387467a1 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs @@ -68,6 +68,7 @@ public void VariationSendsFeatureEventForValidFlag() Assert.Equal("b", fe.Default); Assert.True(fe.TrackEvents); Assert.Equal(2000, fe.DebugEventsUntilDate); + Assert.Null(fe.Reason); }); } } @@ -113,6 +114,7 @@ public void VariationSendsFeatureEventForDefaultValue() Assert.Null(fe.Variation); Assert.Equal(1000, fe.Version); Assert.Equal("b", fe.Default); + Assert.Null(fe.Reason); }); } } @@ -133,6 +135,88 @@ public void VariationSendsFeatureEventForUnknownFlag() Assert.Null(fe.Variation); Assert.Null(fe.Version); Assert.Equal("b", fe.Default); + Assert.Null(fe.Reason); + }); + } + } + + [Fact] + public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() + { + string flagsJson = @"{""flag"":{ + ""value"":""a"",""variation"":1,""version"":1000, + ""trackReason"":true, ""reason"":{""kind"":""OFF""} + }}"; + using (LdClient client = MakeClient(user, flagsJson)) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("a", result); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + Assert.True(fe.TrackEvents); + Assert.Null(fe.DebugEventsUntilDate); + Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); + }); + } + } + + [Fact] + public void VariationDetailSendsFeatureEventWithReasonForValidFlag() + { + string flagsJson = @"{""flag"":{ + ""value"":""a"",""variation"":1,""version"":1000, + ""trackEvents"":true, ""debugEventsUntilDate"":2000, + ""reason"":{""kind"":""OFF""} + }}"; + using (LdClient client = MakeClient(user, flagsJson)) + { + EvaluationDetail result = client.StringVariationDetail("flag", "b"); + Assert.Equal("a", result.Value); + Assert.Equal(EvaluationReason.Off.Instance, result.Reason); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("a", fe.Value); + Assert.Equal(1, fe.Variation); + Assert.Equal(1000, fe.Version); + Assert.Equal("b", fe.Default); + Assert.True(fe.TrackEvents); + Assert.Equal(2000, fe.DebugEventsUntilDate); + Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); + }); + } + } + + [Fact] + public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() + { + using (LdClient client = MakeClient(user, "{}")) + { + EvaluationDetail result = client.StringVariationDetail("flag", "b"); + var expectedReason = new EvaluationReason.Error(EvaluationErrorKind.FLAG_NOT_FOUND); + Assert.Equal("b", result.Value); + Assert.Equal(expectedReason, result.Reason); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Null(fe.Version); + Assert.Equal("b", fe.Default); + Assert.False(fe.TrackEvents); + Assert.Null(fe.DebugEventsUntilDate); + Assert.Equal(expectedReason, fe.Reason); }); } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs index fcb0e08e..7969e83c 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Net.Http; +using System.Text; using System.Threading.Tasks; using LaunchDarkly.Client; using LaunchDarkly.Common; @@ -17,32 +19,82 @@ public class MobileStreamingProcessorTests "\"string-flag\":{\"value\":\"markw@magenic.com\",\"version\":100}" + "}"; - User user = User.WithKey("user key"); - EventSourceMock mockEventSource; - TestEventSourceFactory eventSourceFactory; - IFlagCacheManager mockFlagCacheMgr; + private readonly User user = User.WithKey("me"); + private const string encodedUser = "eyJrZXkiOiJtZSIsImN1c3RvbSI6e319"; - private IMobileUpdateProcessor MobileStreamingProcessorStarted() + private EventSourceMock mockEventSource; + private TestEventSourceFactory eventSourceFactory; + private IFlagCacheManager mockFlagCacheMgr; + private Configuration config; + + public MobileStreamingProcessorTests() { mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); - // stub with an empty InMemoryCache, so Stream updates can be tested mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); - var config = Configuration.Default("someKey") - .WithConnectionManager(new MockConnectionManager(true)) - .WithIsStreamingEnabled(true) - .WithFlagCacheManager(mockFlagCacheMgr); - + config = Configuration.Default("someKey") + .WithConnectionManager(new MockConnectionManager(true)) + .WithIsStreamingEnabled(true) + .WithFlagCacheManager(mockFlagCacheMgr); + + } + + private IMobileUpdateProcessor MobileStreamingProcessorStarted() + { var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, eventSourceFactory.Create()); processor.Start(); return processor; } [Fact] - public void CanCreateMobileStreamingProcFromFactory() + public void StreamUriInGetModeHasUser() + { + config.WithUseReport(false); + var streamingProcessor = MobileStreamingProcessorStarted(); + var props = eventSourceFactory.ReceivedProperties; + Assert.Equal(HttpMethod.Get, props.Method); + Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser), props.StreamUri); + } + + [Fact] + public void StreamUriInGetModeHasReasonsParameterIfConfigured() + { + config.WithUseReport(false); + config.WithEvaluationReasons(true); + var streamingProcessor = MobileStreamingProcessorStarted(); + var props = eventSourceFactory.ReceivedProperties; + Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser + "?withReasons=true"), props.StreamUri); + } + + [Fact] + public void StreamUriInReportModeHasNoUser() + { + config.WithUseReport(true); + var streamingProcessor = MobileStreamingProcessorStarted(); + var props = eventSourceFactory.ReceivedProperties; + Assert.Equal(new HttpMethod("REPORT"), props.Method); + Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH), props.StreamUri); + } + + [Fact] + public void StreamUriInReportModeHasReasonsParameterIfConfigured() + { + config.WithUseReport(true); + config.WithEvaluationReasons(true); + var streamingProcessor = MobileStreamingProcessorStarted(); + var props = eventSourceFactory.ReceivedProperties; + Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + "?withReasons=true"), props.StreamUri); + } + + [Fact] + public async void StreamRequestBodyInReportModeHasUser() { + config.WithUseReport(true); var streamingProcessor = MobileStreamingProcessorStarted(); - Assert.IsType(streamingProcessor); + var props = eventSourceFactory.ReceivedProperties; + var body = Assert.IsType(props.RequestBody); + var s = await body.ReadAsStringAsync(); + Assert.Equal(user.AsJson(), s); } [Fact] diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 8653b37a..57c8b013 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -25,9 +25,17 @@ public static LdClient CreateClient(Configuration config, User user) } } - public static string JsonFlagsWithSingleFlag(string flagKey, JToken value) + public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) { - JObject fo = new JObject { { "value", value } }; + JObject fo = new JObject { { "value", value } }; + if (variation != null) + { + fo["variation"] = new JValue(variation.Value); + } + if (reason != null) + { + fo["reason"] = JToken.FromObject(reason); + } JObject o = new JObject { { flagKey, fo } }; return JsonConvert.SerializeObject(o); } From a1f08ca2574098d74f81160cc5e457b41d899554 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 13 Mar 2019 13:54:33 -0700 Subject: [PATCH 057/499] always dispose of CancellationTokenSource --- .../FeatureFlagRequestor.cs | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs b/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs index 14c609a4..10174600 100644 --- a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.cs @@ -81,48 +81,45 @@ private Uri MakeRequestUriWithPath(string path) private async Task MakeRequest(HttpRequestMessage request) { - var cts = new CancellationTokenSource(_configuration.HttpClientTimeout); - - if (_etag != null) - { - request.Headers.IfNoneMatch.Add(_etag); - } + using (var cts = new CancellationTokenSource(_configuration.HttpClientTimeout)) + { + if (_etag != null) + { + request.Headers.IfNoneMatch.Add(_etag); + } - try - { - Log.DebugFormat("Getting flags with uri: {0}", request.RequestUri.AbsoluteUri); - using (var response = await _httpClient.SendAsync(request, cts.Token).ConfigureAwait(false)) + try { - if (response.StatusCode == HttpStatusCode.NotModified) - { - Log.Debug("Get all flags returned 304: not modified"); - return new WebResponse(304, null, "Get all flags returned 304: not modified"); - } - _etag = response.Headers.ETag; - //We ensure the status code after checking for 304, because 304 isn't considered success - if (!response.IsSuccessStatusCode) + Log.DebugFormat("Getting flags with uri: {0}", request.RequestUri.AbsoluteUri); + using (var response = await _httpClient.SendAsync(request, cts.Token).ConfigureAwait(false)) { - throw new UnsuccessfulResponseException((int)response.StatusCode); + if (response.StatusCode == HttpStatusCode.NotModified) + { + Log.Debug("Get all flags returned 304: not modified"); + return new WebResponse(304, null, "Get all flags returned 304: not modified"); + } + _etag = response.Headers.ETag; + //We ensure the status code after checking for 304, because 304 isn't considered success + if (!response.IsSuccessStatusCode) + { + throw new UnsuccessfulResponseException((int)response.StatusCode); + } + + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return new WebResponse(200, content, null); } - - var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - return new WebResponse(200, content, null); } - } - catch (TaskCanceledException tce) - { - if (tce.CancellationToken == cts.Token) + catch (TaskCanceledException tce) { - //Indicates the task was cancelled by something other than a request timeout - throw; - } - //Otherwise this was a request timeout. - throw new TimeoutException("Get item with URL: " + request.RequestUri + - " timed out after : " + _configuration.HttpClientTimeout); - } - catch (Exception) - { - throw; + if (tce.CancellationToken == cts.Token) + { + //Indicates the task was cancelled by something other than a request timeout + throw; + } + //Otherwise this was a request timeout. + throw new TimeoutException("Get item with URL: " + request.RequestUri + + " timed out after : " + _configuration.HttpClientTimeout); + } } } From a23efce5c57bac5061358a57ba94c1fa7cae7132 Mon Sep 17 00:00:00 2001 From: torchhound Date: Mon, 18 Mar 2019 13:05:23 -0700 Subject: [PATCH 058/499] feat(./): removed Xamarin.Essentials, build succeeds for all platforms, added platform specific unit tests --- .../Assets/AboutAssets.txt | 19 ++ .../LDAndroidTests.cs | 23 ++ .../LaunchDarkly.Xamarin.Android.Tests.csproj | 98 +++++++++ .../MainActivity.cs | 23 ++ .../Properties/AndroidManifest.xml | 5 + .../Properties/AssemblyInfo.cs | 27 +++ .../Resources/AboutResources.txt | 44 ++++ .../Resources/Resource.designer.cs | 175 +++++++++++++++ .../Resources/mipmap-hdpi/Icon.png | Bin 0 -> 2201 bytes .../Resources/mipmap-mdpi/Icon.png | Bin 0 -> 1410 bytes .../Resources/mipmap-xhdpi/Icon.png | Bin 0 -> 3237 bytes .../Resources/mipmap-xxhdpi/Icon.png | Bin 0 -> 5414 bytes .../Resources/mipmap-xxxhdpi/Icon.png | Bin 0 -> 7825 bytes .../designtime/build.props | 24 +++ .../packages.config | 57 +++++ .../Entitlements.plist | 6 + LaunchDarkly.Xamarin.iOS.Tests/Info.plist | 36 ++++ LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs | 25 +++ .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 128 +++++++++++ .../LaunchScreen.storyboard | 27 +++ LaunchDarkly.Xamarin.iOS.Tests/Main.cs | 20 ++ .../UnitTestAppDelegate.cs | 45 ++++ .../packages.config | 56 +++++ LaunchDarkly.Xamarin.sln | 48 +++++ .../BackgroundAdapter.netstandard.cs | 2 +- .../Connectivity/Connectivity.android.cs | 18 +- .../Connectivity/Connectivity.netstandard.cs | 1 - .../Connectivity/Connectivity.shared.cs | 15 ++ .../IPlatformAdapter.shared.cs | 5 +- .../LaunchDarkly.Xamarin.csproj | 28 +-- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 2 +- .../MainThread/MainThread.android.cs | 51 +++++ .../MainThread/MainThread.ios.cs | 38 ++++ .../MainThread/MainThread.netstandard.cs | 35 +++ .../MainThread/MainThread.shared.cs | 100 +++++++++ .../Permissions/Permissions.android.cs | 201 ++++++++++++++++++ .../Permissions/Permissions.ios.cs | 124 +++++++++++ .../Permissions/Permissions.netstandard.cs | 39 ++++ .../Permissions/Permissions.shared.cs | 46 ++++ .../Permissions/Permissions.shared.enums.cs | 53 +++++ .../Platform/Platform.android.cs | 177 +++++++++++++++ .../Platform/Platform.ios.cs | 104 +++++++++ .../Platform/Platform.shared.cs | 30 +++ .../Properties/AssemblyInfo.shared.cs | 2 + 44 files changed, 1925 insertions(+), 32 deletions(-) create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt create mode 100644 LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs create mode 100644 LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj create mode 100644 LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-hdpi/Icon.png create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-mdpi/Icon.png create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xhdpi/Icon.png create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxhdpi/Icon.png create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png create mode 100644 LaunchDarkly.Xamarin.Android.Tests/designtime/build.props create mode 100644 LaunchDarkly.Xamarin.Android.Tests/packages.config create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/Info.plist create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/Main.cs create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs create mode 100644 LaunchDarkly.Xamarin.iOS.Tests/packages.config create mode 100644 src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs create mode 100644 src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs create mode 100644 src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs create mode 100644 src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs create mode 100644 src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs create mode 100644 src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs create mode 100644 src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs create mode 100644 src/LaunchDarkly.Xamarin/Platform/Platform.android.cs create mode 100644 src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs diff --git a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt new file mode 100644 index 00000000..a9b0638e --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs new file mode 100644 index 00000000..0904b94b --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs @@ -0,0 +1,23 @@ +using System; +using NUnit.Framework; + +namespace LaunchDarkly.Xamarin.Android.Tests +{ + [TestFixture] + public class LDAndroidTests + { + [SetUp] + public void Setup() { } + + + [TearDown] + public void Tear() { } + + [Test] + public void Pass() + { + Console.WriteLine("test1"); + Assert.True(true); + } + } +} diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj new file mode 100644 index 00000000..47653e52 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -0,0 +1,98 @@ + + + + Debug + AnyCPU + {0B18C336-C770-42C1-B77A-E4A49F789677} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + LaunchDarkly.Xamarin.Android.Tests + LaunchDarkly.Xamarin.Android.Tests + v9.0 + MonoAndroid90 + True + Resources\Resource.designer.cs + Resource + Resources + Assets + Properties\AndroidManifest.xml + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + None + + arm64-v8a;armeabi;armeabi-v7a;x86 + + + true + pdbonly + true + bin\Release + prompt + 4 + true + false + + + + + + + + + + + + ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll + + + ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + + + + + ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll + + + ..\packages\LaunchDarkly.EventSource.3.2.3\lib\netstandard1.4\LaunchDarkly.EventSource.dll + + + ..\packages\LaunchDarkly.Common.1.2.3\lib\netstandard2.0\LaunchDarkly.Common.dll + + + ..\packages\Plugin.CurrentActivity.2.1.0.4\lib\monoandroid44\Plugin.CurrentActivity.dll + + + + ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\monoandroid71\Plugin.DeviceInfo.dll + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs b/LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs new file mode 100644 index 00000000..22d1b416 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs @@ -0,0 +1,23 @@ +using System.Reflection; + +using Android.App; +using Android.OS; +using Xamarin.Android.NUnitLite; + +namespace LaunchDarkly.Xamarin.Android.Tests +{ + [Activity(Label = "LaunchDarkly.Xamarin.Android.Tests", MainLauncher = true)] + public class MainActivity : TestSuiteActivity + { + protected override void OnCreate(Bundle bundle) + { + // tests can be inside the main assembly + AddTest(Assembly.GetExecutingAssembly()); + // or in any reference assemblies + // AddTest (typeof (Your.Library.TestClass).Assembly); + + // Once you called base.OnCreate(), you cannot add more assemblies. + base.OnCreate(bundle); + } + } +} diff --git a/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml b/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml new file mode 100644 index 00000000..5aedade7 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs b/LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..80ab1048 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.App; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("LaunchDarkly.Xamarin.Android.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("${AuthorCopyright}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt b/LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt new file mode 100644 index 00000000..10f52d46 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs b/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs new file mode 100644 index 00000000..b7d502eb --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs @@ -0,0 +1,175 @@ +#pragma warning disable 1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.Xamarin.Android.Tests.Resource", IsApplication=true)] + +namespace LaunchDarkly.Xamarin.Android.Tests +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + public partial class Resource + { + + static Resource() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + public static void UpdateIdValues() + { + global::Xamarin.Android.NUnitLite.Resource.Id.OptionHostName = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionHostName; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionPort = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionPort; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionRemoteServer = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionRemoteServer; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionsButton = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionsButton; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultFullName = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultFullName; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultMessage = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultMessage; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultResultState = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultResultState; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultRunSingleMethodTest = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultRunSingleMethodTest; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultStackTrace = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultStackTrace; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsFailed = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsFailed; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsId = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsId; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsIgnored = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsIgnored; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsInconclusive = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsInconclusive; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsMessage = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsMessage; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsPassed = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsPassed; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsResult = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsResult; + global::Xamarin.Android.NUnitLite.Resource.Id.RunTestsButton = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.RunTestsButton; + global::Xamarin.Android.NUnitLite.Resource.Id.TestSuiteListView = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.TestSuiteListView; + global::Xamarin.Android.NUnitLite.Resource.Layout.options = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.options; + global::Xamarin.Android.NUnitLite.Resource.Layout.results = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.results; + global::Xamarin.Android.NUnitLite.Resource.Layout.test_result = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.test_result; + global::Xamarin.Android.NUnitLite.Resource.Layout.test_suite = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.test_suite; + } + + public partial class Attribute + { + + static Attribute() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Attribute() + { + } + } + + public partial class Id + { + + // aapt resource value: 0x7f040001 + public const int OptionHostName = 2130968577; + + // aapt resource value: 0x7f040002 + public const int OptionPort = 2130968578; + + // aapt resource value: 0x7f040000 + public const int OptionRemoteServer = 2130968576; + + // aapt resource value: 0x7f040010 + public const int OptionsButton = 2130968592; + + // aapt resource value: 0x7f04000b + public const int ResultFullName = 2130968587; + + // aapt resource value: 0x7f04000d + public const int ResultMessage = 2130968589; + + // aapt resource value: 0x7f04000c + public const int ResultResultState = 2130968588; + + // aapt resource value: 0x7f04000a + public const int ResultRunSingleMethodTest = 2130968586; + + // aapt resource value: 0x7f04000e + public const int ResultStackTrace = 2130968590; + + // aapt resource value: 0x7f040006 + public const int ResultsFailed = 2130968582; + + // aapt resource value: 0x7f040003 + public const int ResultsId = 2130968579; + + // aapt resource value: 0x7f040007 + public const int ResultsIgnored = 2130968583; + + // aapt resource value: 0x7f040008 + public const int ResultsInconclusive = 2130968584; + + // aapt resource value: 0x7f040009 + public const int ResultsMessage = 2130968585; + + // aapt resource value: 0x7f040005 + public const int ResultsPassed = 2130968581; + + // aapt resource value: 0x7f040004 + public const int ResultsResult = 2130968580; + + // aapt resource value: 0x7f04000f + public const int RunTestsButton = 2130968591; + + // aapt resource value: 0x7f040011 + public const int TestSuiteListView = 2130968593; + + static Id() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Id() + { + } + } + + public partial class Layout + { + + // aapt resource value: 0x7f030000 + public const int options = 2130903040; + + // aapt resource value: 0x7f030001 + public const int results = 2130903041; + + // aapt resource value: 0x7f030002 + public const int test_result = 2130903042; + + // aapt resource value: 0x7f030003 + public const int test_suite = 2130903043; + + static Layout() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Layout() + { + } + } + + public partial class Mipmap + { + + // aapt resource value: 0x7f020000 + public const int Icon = 2130837504; + + static Mipmap() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Mipmap() + { + } + } + } +} +#pragma warning restore 1591 diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-hdpi/Icon.png b/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-hdpi/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c804644c5e47daffed8e8573b5da99d0939bc0 GIT binary patch literal 2201 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 literal 0 HcmV?d00001 diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xhdpi/Icon.png b/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xhdpi/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b7e2e57aa90f8687bdfa19d82ff6bda71535b8d5 GIT binary patch literal 3237 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 Xx9ez7Rf!D(00000NkvXXu0mjfmS|-l literal 0 HcmV?d00001 diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxhdpi/Icon.png b/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxhdpi/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d20a38d1782921964638e873ffc98d148ebda6a GIT binary patch literal 5414 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 c0$sN|hZ~f>0q(p1ua5js-$bwSo@3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist b/LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist new file mode 100644 index 00000000..9ae59937 --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Info.plist b/LaunchDarkly.Xamarin.iOS.Tests/Info.plist new file mode 100644 index 00000000..1cbfd464 --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + LaunchDarkly.Xamarin.iOS.Tests + CFBundleIdentifier + com.launchdarkly.LaunchDarkly-Xamarin-iOS-Tests + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 12.1 + UIDeviceFamily + + 1 + 2 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UILaunchStoryboardName + LaunchScreen + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs new file mode 100644 index 00000000..c70de6b8 --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs @@ -0,0 +1,25 @@ +using System; +using NUnit.Framework; +//using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + [TestFixture] + public class LDiOSTests + { + [SetUp] + public void Setup() { + //LdClient client = LdClient.Init(config, user, TimeSpan.Zero); + } + + [TearDown] + public void Tear() { } + + [Test] + public void Pass() + { + Console.WriteLine("test1"); + Assert.True(true); + } + } +} diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj new file mode 100644 index 00000000..5ad98579 --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -0,0 +1,128 @@ + + + + Debug + iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + LaunchDarkly.Xamarin.iOS.Tests + LaunchDarkly.Xamarin.iOS.Tests + Resources + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG; + prompt + 4 + iPhone Developer + true + true + true + 46596 + None + x86_64 + NSUrlSessionHandler + false + + + + pdbonly + true + bin\iPhone\Release + + prompt + 4 + iPhone Developer + true + true + Entitlements.plist + SdkOnly + ARM64 + NSUrlSessionHandler + + + + pdbonly + true + bin\iPhoneSimulator\Release + + prompt + 4 + iPhone Developer + true + None + x86_64 + NSUrlSessionHandler + + + + true + full + false + bin\iPhone\Debug + DEBUG; + prompt + 4 + iPhone Developer + true + true + true + true + true + Entitlements.plist + 42164 + SdkOnly + ARM64 + NSUrlSessionHandler + + + + + ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\Xamarin.iOS + + + + + + + + + ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll + + + ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + + + + + ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll + + + ..\packages\LaunchDarkly.EventSource.3.2.3\lib\netstandard1.4\LaunchDarkly.EventSource.dll + + + ..\packages\LaunchDarkly.Common.1.2.3\lib\netstandard2.0\LaunchDarkly.Common.dll + + + ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard b/LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard new file mode 100644 index 00000000..5d2e905a --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Main.cs b/LaunchDarkly.Xamarin.iOS.Tests/Main.cs new file mode 100644 index 00000000..c040831c --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/Main.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +using Foundation; +using UIKit; + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + public class Application + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "UnitTestAppDelegate" + // you can specify it here. + UIApplication.Main(args, null, "UnitTestAppDelegate"); + } + } +} diff --git a/LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs b/LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs new file mode 100644 index 00000000..d5e666aa --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +using Foundation; +using UIKit; +using MonoTouch.NUnit.UI; + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + // The UIApplicationDelegate for the application. This class is responsible for launching the + // User Interface of the application, as well as listening (and optionally responding) to + // application events from iOS. + [Register("UnitTestAppDelegate")] + public partial class UnitTestAppDelegate : UIApplicationDelegate + { + // class-level declarations + UIWindow window; + TouchRunner runner; + + // + // This method is invoked when the application has loaded and is ready to run. In this + // method you should instantiate the window, load the UI into it and then make the window + // visible. + // + // You have 17 seconds to return from this method, or iOS will terminate your application. + // + public override bool FinishedLaunching(UIApplication app, NSDictionary options) + { + // create a new window instance based on the screen size + window = new UIWindow(UIScreen.MainScreen.Bounds); + runner = new TouchRunner(window); + + // register every tests included in the main application/assembly + runner.Add(System.Reflection.Assembly.GetExecutingAssembly()); + + window.RootViewController = new UINavigationController(runner.GetViewController()); + + // make the window visible + window.MakeKeyAndVisible(); + + return true; + } + } +} diff --git a/LaunchDarkly.Xamarin.iOS.Tests/packages.config b/LaunchDarkly.Xamarin.iOS.Tests/packages.config new file mode 100644 index 00000000..d0029198 --- /dev/null +++ b/LaunchDarkly.Xamarin.iOS.Tests/packages.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.sln b/LaunchDarkly.Xamarin.sln index 386a7301..82f88ff0 100644 --- a/LaunchDarkly.Xamarin.sln +++ b/LaunchDarkly.Xamarin.sln @@ -6,20 +6,68 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.Tests", "tests\LaunchDarkly.Xamarin.Tests\LaunchDarkly.Xamarin.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.iOS.Tests", "LaunchDarkly.Xamarin.iOS.Tests\LaunchDarkly.Xamarin.iOS.Tests.csproj", "{066AA0F9-449A-48F5-9492-D698F0EFD923}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.Android.Tests", "LaunchDarkly.Xamarin.Android.Tests\LaunchDarkly.Xamarin.Android.Tests.csproj", "{0B18C336-C770-42C1-B77A-E4A49F789677}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7717A2B2-9905-40A7-989F-790139D69543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7717A2B2-9905-40A7-989F-790139D69543}.Debug|Any CPU.Build.0 = Debug|Any CPU {7717A2B2-9905-40A7-989F-790139D69543}.Release|Any CPU.ActiveCfg = Release|Any CPU {7717A2B2-9905-40A7-989F-790139D69543}.Release|Any CPU.Build.0 = Release|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Release|iPhone.ActiveCfg = Release|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Release|iPhone.Build.0 = Release|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7717A2B2-9905-40A7-989F-790139D69543}.Debug|iPhone.Build.0 = Debug|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|Any CPU.Build.0 = Debug|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|Any CPU.ActiveCfg = Release|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|Any CPU.Build.0 = Release|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhone.ActiveCfg = Release|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhone.Build.0 = Release|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.Build.0 = Debug|Any CPU + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|Any CPU.ActiveCfg = Release|iPhone + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|Any CPU.Build.0 = Release|iPhone + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhone.ActiveCfg = Release|iPhone + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhone.Build.0 = Release|iPhone + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhone.ActiveCfg = Debug|iPhone + {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhone.Build.0 = Debug|iPhone + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|Any CPU.Build.0 = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhone.ActiveCfg = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhone.Build.0 = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs index 403d6eae..f5f66010 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs @@ -10,7 +10,7 @@ public void Dispose() throw new NotImplementedException(); } - public void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval) + public void EnableBackgrounding(IBackgroundingState backgroundingState) { throw new NotImplementedException(); } diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs index 29dd8742..94c6fd47 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs @@ -36,11 +36,11 @@ public partial class Connectivity static void StartListeners() { - Permissions.EnsureDeclared(PermissionType.NetworkState); + Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); conectivityReceiver = new ConnectivityBroadcastReceiver(OnConnectivityChanged); - Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); + Platform.Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); } static void StopListeners() @@ -49,7 +49,7 @@ static void StopListeners() return; try { - Platform.AppContext.UnregisterReceiver(conectivityReceiver); + Platform.Platform.AppContext.UnregisterReceiver(conectivityReceiver); } catch (Java.Lang.IllegalArgumentException) { @@ -66,14 +66,14 @@ static NetworkAccess PlatformNetworkAccess { get { - Permissions.EnsureDeclared(PermissionType.NetworkState); + Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); try { var currentAccess = NetworkAccess.None; - var manager = Platform.ConnectivityManager; + var manager = Platform.Platform.ConnectivityManager; - if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + if (Platform.Platform.HasApiLevel(BuildVersionCodes.Lollipop)) { foreach (var network in manager.GetAllNetworks()) { @@ -140,10 +140,10 @@ static IEnumerable PlatformConnectionProfiles { get { - Permissions.EnsureDeclared(PermissionType.NetworkState); + Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); - var manager = Platform.ConnectivityManager; - if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + var manager = Platform.Platform.ConnectivityManager; + if (Platform.Platform.HasApiLevel(BuildVersionCodes.Lollipop)) { foreach (var network in manager.GetAllNetworks()) { diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs index 38af6cec..22c73f8f 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs @@ -21,7 +21,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; - using System.Collections.Generic; namespace LaunchDarkly.Xamarin.Connectivity diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs index e8a23616..00e4ec58 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs @@ -70,6 +70,21 @@ static void SetCurrent() currentAccess = NetworkAccess; currentProfiles = new List(ConnectionProfiles); } + + static void OnConnectivityChanged(NetworkAccess access, IEnumerable profiles) + => OnConnectivityChanged(new ConnectivityChangedEventArgs(access, profiles)); + + static void OnConnectivityChanged() + => OnConnectivityChanged(NetworkAccess, ConnectionProfiles); + + static void OnConnectivityChanged(ConnectivityChangedEventArgs e) + { + if (currentAccess != e.NetworkAccess || !currentProfiles.SequenceEqual(e.ConnectionProfiles)) + { + SetCurrent(); + MainThread.MainThread.BeginInvokeOnMainThread(() => ConnectivityChangedInternal?.Invoke(null, e)); + } + } } public class ConnectivityChangedEventArgs : EventArgs diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs b/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs index a50c924e..ad49c2c1 100644 --- a/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs +++ b/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs @@ -25,13 +25,12 @@ public interface IPlatformAdapter : IDisposable /// the application, and provides a callback object for it to use when the state changes. /// /// An implementation of IBackgroundingState provided by the client - /// if non-null, the interval at which polling should happen in the background - void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval); + void EnableBackgrounding(IBackgroundingState backgroundingState); } internal class NullPlatformAdapter : IPlatformAdapter { - public void EnableBackgrounding(IBackgroundingState backgroundingState, TimeSpan? backgroundPollInterval) { } + public void EnableBackgrounding(IBackgroundingState backgroundingState) { } public void Dispose() { } } diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index b1463d06..4b02a400 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -1,26 +1,19 @@ + + netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; 1.0.0-beta15 Library LaunchDarkly.Xamarin LaunchDarkly.Xamarin false + bin\$(Configuration)\$(Framework) + true + latest - - netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81 - - - netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81 - - - - - - - - @@ -28,6 +21,10 @@ + + + + @@ -70,8 +67,5 @@ - - - - + diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 272e1f4a..1af85dc4 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -234,7 +234,7 @@ static void CreateInstance(Configuration configuration, User user) } try { - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance), bgPollInterval); + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); } catch { diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs new file mode 100644 index 00000000..338cf637 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs @@ -0,0 +1,51 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using Android.OS; + +namespace LaunchDarkly.Xamarin.MainThread +{ + public static partial class MainThread + { + static Handler handler; + + static bool PlatformIsMainThread + { + get + { + if (Platform.Platform.HasApiLevel(BuildVersionCodes.M)) + return Looper.MainLooper.IsCurrentThread; + + return Looper.MyLooper() == Looper.MainLooper; + } + } + + static void PlatformBeginInvokeOnMainThread(Action action) + { + if (handler?.Looper != Looper.MainLooper) + handler = new Handler(Looper.MainLooper); + + handler.Post(action); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs new file mode 100644 index 00000000..9ac0fb94 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs @@ -0,0 +1,38 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using Foundation; + +namespace LaunchDarkly.Xamarin.MainThread +{ + public static partial class MainThread + { + static bool PlatformIsMainThread => + NSThread.Current.IsMainThread; + + static void PlatformBeginInvokeOnMainThread(Action action) + { + NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs new file mode 100644 index 00000000..0ef92edf --- /dev/null +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs @@ -0,0 +1,35 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; + +namespace LaunchDarkly.Xamarin.MainThread +{ + public static partial class MainThread + { + static void PlatformBeginInvokeOnMainThread(Action action) => + throw new NotImplementedException(); + + static bool PlatformIsMainThread => + throw new NotImplementedException(); + } +} diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs new file mode 100644 index 00000000..3b72d83c --- /dev/null +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs @@ -0,0 +1,100 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin.MainThread +{ + public static partial class MainThread + { + public static bool IsMainThread => + PlatformIsMainThread; + + public static void BeginInvokeOnMainThread(Action action) + { + if (IsMainThread) + { + action(); + } + else + { + PlatformBeginInvokeOnMainThread(action); + } + } + + internal static Task InvokeOnMainThread(Action action) + { + if (IsMainThread) + { + action(); +#if NETSTANDARD1_0 + return Task.FromResult(true); +#else + return Task.CompletedTask; +#endif + } + + var tcs = new TaskCompletionSource(); + + BeginInvokeOnMainThread(() => + { + try + { + action(); + tcs.TrySetResult(true); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + }); + + return tcs.Task; + } + + internal static Task InvokeOnMainThread(Func action) + { + if (IsMainThread) + { + return Task.FromResult(action()); + } + + var tcs = new TaskCompletionSource(); + + BeginInvokeOnMainThread(() => + { + try + { + var result = action(); + tcs.TrySetResult(result); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + }); + + return tcs.Task; + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs new file mode 100644 index 00000000..5e5fe6c8 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs @@ -0,0 +1,201 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Android; +using Android.Content.PM; +using Android.OS; +using Android.Support.V4.App; +using Android.Support.V4.Content; + +namespace LaunchDarkly.Xamarin.Permissions +{ + internal static partial class Permissions + { + static readonly object locker = new object(); + static int requestCode = 0; + + static Dictionary tcs)> requests = + new Dictionary)>(); + + static void PlatformEnsureDeclared(PermissionType permission) + { + var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: false); + + // No actual android permissions required here, just return + if (androidPermissions == null || !androidPermissions.Any()) + return; + + var context = Platform.Platform.AppContext; + + foreach (var ap in androidPermissions) + { + var packageInfo = context.PackageManager.GetPackageInfo(context.PackageName, PackageInfoFlags.Permissions); + var requestedPermissions = packageInfo?.RequestedPermissions; + + // If the manifest is missing any of the permissions we need, throw + if (!requestedPermissions?.Any(r => r.Equals(ap, StringComparison.OrdinalIgnoreCase)) ?? false) + throw new UnauthorizedAccessException($"You need to declare the permission: `{ap}` in your AndroidManifest.xml"); + } + } + + static Task PlatformCheckStatusAsync(PermissionType permission) + { + EnsureDeclared(permission); + + // If there are no android permissions for the given permission type + // just return granted since we have none to ask for + var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true); + + if (androidPermissions == null || !androidPermissions.Any()) + return Task.FromResult(PermissionStatus.Granted); + + var context = Platform.Platform.AppContext; + var targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= BuildVersionCodes.M; + + foreach (var ap in androidPermissions) + { + if (targetsMOrHigher) + { + if (ContextCompat.CheckSelfPermission(context, ap) != Permission.Granted) + return Task.FromResult(PermissionStatus.Denied); + } + else + { + if (PermissionChecker.CheckSelfPermission(context, ap) != PermissionChecker.PermissionGranted) + return Task.FromResult(PermissionStatus.Denied); + } + } + + return Task.FromResult(PermissionStatus.Granted); + } + + static async Task PlatformRequestAsync(PermissionType permission) + { + // Check status before requesting first + if (await PlatformCheckStatusAsync(permission) == PermissionStatus.Granted) + return PermissionStatus.Granted; + + TaskCompletionSource tcs; + var doRequest = true; + + lock (locker) + { + if (requests.ContainsKey(permission)) + { + tcs = requests[permission].tcs; + doRequest = false; + } + else + { + tcs = new TaskCompletionSource(); + + // Get new request code and wrap it around for next use if it's going to reach max + if (++requestCode >= int.MaxValue) + requestCode = 1; + + requests.Add(permission, (requestCode, tcs)); + } + } + + if (!doRequest) + return await tcs.Task; + + if (!MainThread.MainThread.IsMainThread) + throw new System.UnauthorizedAccessException("Permission request must be invoked on main thread."); + + var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true).ToArray(); + + ActivityCompat.RequestPermissions(Platform.Platform.GetCurrentActivity(true), androidPermissions, requestCode); + + return await tcs.Task; + } + + internal static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) + { + lock (locker) + { + // Check our pending requests for one with a matching request code + foreach (var kvp in requests) + { + if (kvp.Value.requestCode == requestCode) + { + var tcs = kvp.Value.tcs; + + // Look for any denied requests, and deny the whole request if so + // Remember, each PermissionType is tied to 1 or more android permissions + // so if any android permissions denied the whole PermissionType is considered denied + if (grantResults.Any(g => g == Permission.Denied)) + tcs.TrySetResult(PermissionStatus.Denied); + else + tcs.TrySetResult(PermissionStatus.Granted); + break; + } + } + } + } + } + + static class PermissionTypeExtensions + { + internal static IEnumerable ToAndroidPermissions(this PermissionType permissionType, bool onlyRuntimePermissions) + { + var permissions = new List<(string permission, bool runtimePermission)>(); + + switch (permissionType) + { + case PermissionType.Battery: + permissions.Add((Manifest.Permission.BatteryStats, false)); + break; + case PermissionType.Camera: + permissions.Add((Manifest.Permission.Camera, true)); + break; + case PermissionType.Flashlight: + permissions.Add((Manifest.Permission.Camera, true)); + permissions.Add((Manifest.Permission.Flashlight, false)); + break; + case PermissionType.LocationWhenInUse: + permissions.Add((Manifest.Permission.AccessFineLocation, true)); + permissions.Add((Manifest.Permission.AccessCoarseLocation, true)); + break; + case PermissionType.NetworkState: + permissions.Add((Manifest.Permission.AccessNetworkState, false)); + break; + case PermissionType.Vibrate: + permissions.Add((Manifest.Permission.Vibrate, true)); + break; + } + + if (onlyRuntimePermissions) + { + return permissions + .Where(p => p.runtimePermission) + .Select(p => p.permission); + } + + return permissions.Select(p => p.permission); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs new file mode 100644 index 00000000..1d2807b1 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs @@ -0,0 +1,124 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System.Threading.Tasks; +using System; +using CoreLocation; +using Foundation; + +namespace LaunchDarkly.Xamarin.Permissions +{ + internal static partial class Permissions + { + static void PlatformEnsureDeclared(PermissionType permission) + { + var info = NSBundle.MainBundle.InfoDictionary; + + if (permission == PermissionType.LocationWhenInUse) + { + if (!info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) + throw new UnauthorizedAccessException("You must set `NSLocationWhenInUseUsageDescription` in your Info.plist file to enable Authorization Requests for Location updates."); + } + } + + static Task PlatformCheckStatusAsync(PermissionType permission) + { + EnsureDeclared(permission); + + switch (permission) + { + case PermissionType.LocationWhenInUse: + return Task.FromResult(GetLocationStatus()); + } + + return Task.FromResult(PermissionStatus.Granted); + } + + static async Task PlatformRequestAsync(PermissionType permission) + { + // Check status before requesting first and only request if Unknown + var status = await PlatformCheckStatusAsync(permission); + if (status != PermissionStatus.Unknown) + return status; + + EnsureDeclared(permission); + + switch (permission) + { + case PermissionType.LocationWhenInUse: + + if (!MainThread.MainThread.IsMainThread) + throw new UnauthorizedAccessException("Permission request must be invoked on main thread."); + + return await RequestLocationAsync(); + default: + return PermissionStatus.Granted; + } + } + + static PermissionStatus GetLocationStatus() + { + if (!CLLocationManager.LocationServicesEnabled) + return PermissionStatus.Disabled; + + var status = CLLocationManager.Status; + + switch (status) + { + case CLAuthorizationStatus.AuthorizedAlways: + case CLAuthorizationStatus.AuthorizedWhenInUse: + return PermissionStatus.Granted; + case CLAuthorizationStatus.Denied: + return PermissionStatus.Denied; + case CLAuthorizationStatus.Restricted: + return PermissionStatus.Restricted; + default: + return PermissionStatus.Unknown; + } + } + + static CLLocationManager locationManager; + + static Task RequestLocationAsync() + { + locationManager = new CLLocationManager(); + + var tcs = new TaskCompletionSource(locationManager); + + locationManager.AuthorizationChanged += LocationAuthCallback; + locationManager.RequestWhenInUseAuthorization(); + + return tcs.Task; + + void LocationAuthCallback(object sender, CLAuthorizationChangedEventArgs e) + { + if (e.Status == CLAuthorizationStatus.NotDetermined) + return; + + locationManager.AuthorizationChanged -= LocationAuthCallback; + tcs.TrySetResult(GetLocationStatus()); + locationManager.Dispose(); + locationManager = null; + } + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs new file mode 100644 index 00000000..2495b379 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs @@ -0,0 +1,39 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin.Permissions +{ + internal static partial class Permissions + { + static void PlatformEnsureDeclared(PermissionType permission) => + throw new NotImplementedException(); + + static Task PlatformCheckStatusAsync(PermissionType permission) => + throw new NotImplementedException(); + + static Task PlatformRequestAsync(PermissionType permission) => + throw new NotImplementedException(); + } +} diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs new file mode 100644 index 00000000..f7d28c98 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs @@ -0,0 +1,46 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin.Permissions +{ + internal static partial class Permissions + { + internal static void EnsureDeclared(PermissionType permission) => + PlatformEnsureDeclared(permission); + + internal static Task CheckStatusAsync(PermissionType permission) => + PlatformCheckStatusAsync(permission); + + internal static Task RequestAsync(PermissionType permission) => + PlatformRequestAsync(permission); + + internal static async Task RequireAsync(PermissionType permission) + { + if (await RequestAsync(permission) != PermissionStatus.Granted) + throw new System.UnauthorizedAccessException($"{permission} was not granted."); + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs new file mode 100644 index 00000000..5e4366b3 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs @@ -0,0 +1,53 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace LaunchDarkly.Xamarin.Permissions +{ + enum PermissionStatus + { + // Denied by user + Denied, + + // Feature is disabled on device + Disabled, + + // Granted by user + Granted, + + // Restricted (only iOS) + Restricted, + + // Permission is in an unknown state + Unknown + } + + enum PermissionType + { + Unknown, + Battery, + Camera, + Flashlight, + LocationWhenInUse, + NetworkState, + Vibrate, + } +} diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs new file mode 100644 index 00000000..bf019408 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs @@ -0,0 +1,177 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Linq; +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.Hardware; +using Android.Hardware.Camera2; +using Android.Locations; +using Android.Net; +using Android.Net.Wifi; +using Android.OS; + +namespace LaunchDarkly.Xamarin.Platform +{ + public static partial class Platform + { + static ActivityLifecycleContextListener lifecycleListener; + + internal static Context AppContext => + Application.Context; + + internal static Activity GetCurrentActivity(bool throwOnNull) + { + var activity = lifecycleListener?.Activity; + if (throwOnNull && activity == null) + throw new NullReferenceException("The current Activity can not be detected. Ensure that you have called Init in your Activity or Application class."); + + return activity; + } + + public static void Init(Application application) + { + lifecycleListener = new ActivityLifecycleContextListener(); + application.RegisterActivityLifecycleCallbacks(lifecycleListener); + } + + public static void Init(Activity activity, Bundle bundle) + { + Init(activity.Application); + lifecycleListener.Activity = activity; + } + + public static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) => + Permissions.Permissions.OnRequestPermissionsResult(requestCode, permissions, grantResults); + + internal static bool HasSystemFeature(string systemFeature) + { + var packageManager = AppContext.PackageManager; + foreach (var feature in packageManager.GetSystemAvailableFeatures()) + { + if (feature.Name.Equals(systemFeature, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + + internal static bool IsIntentSupported(Intent intent) + { + var manager = AppContext.PackageManager; + var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly); + return activities.Any(); + } + + internal static bool HasApiLevel(BuildVersionCodes versionCode) => + (int)Build.VERSION.SdkInt >= (int)versionCode; + + internal static CameraManager CameraManager => + AppContext.GetSystemService(Context.CameraService) as CameraManager; + + internal static ConnectivityManager ConnectivityManager => + AppContext.GetSystemService(Context.ConnectivityService) as ConnectivityManager; + + internal static Vibrator Vibrator => + AppContext.GetSystemService(Context.VibratorService) as Vibrator; + + internal static WifiManager WifiManager => + AppContext.GetSystemService(Context.WifiService) as WifiManager; + + internal static SensorManager SensorManager => + AppContext.GetSystemService(Context.SensorService) as SensorManager; + + internal static ClipboardManager ClipboardManager => + AppContext.GetSystemService(Context.ClipboardService) as ClipboardManager; + + internal static LocationManager LocationManager => + AppContext.GetSystemService(Context.LocationService) as LocationManager; + + internal static PowerManager PowerManager => + AppContext.GetSystemService(Context.PowerService) as PowerManager; + + internal static Java.Util.Locale GetLocale() + { + var resources = AppContext.Resources; + var config = resources.Configuration; + if (HasApiLevel(BuildVersionCodes.N)) + return config.Locales.Get(0); + + return config.Locale; + } + + internal static void SetLocale(Java.Util.Locale locale) + { + Java.Util.Locale.Default = locale; + var resources = AppContext.Resources; + var config = resources.Configuration; + if (HasApiLevel(BuildVersionCodes.N)) + config.SetLocale(locale); + else + config.Locale = locale; + +#pragma warning disable CS0618 // Type or member is obsolete + resources.UpdateConfiguration(config, resources.DisplayMetrics); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks + { + WeakReference currentActivity = new WeakReference(null); + + internal Context Context => + Activity ?? Application.Context; + + internal Activity Activity + { + get => currentActivity.TryGetTarget(out var a) ? a : null; + set => currentActivity.SetTarget(value); + } + + void Application.IActivityLifecycleCallbacks.OnActivityCreated(Activity activity, Bundle savedInstanceState) => + Activity = activity; + + void Application.IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity) + { + } + + void Application.IActivityLifecycleCallbacks.OnActivityPaused(Activity activity) => + Activity = activity; + + void Application.IActivityLifecycleCallbacks.OnActivityResumed(Activity activity) => + Activity = activity; + + void Application.IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState) + { + } + + void Application.IActivityLifecycleCallbacks.OnActivityStarted(Activity activity) + { + } + + void Application.IActivityLifecycleCallbacks.OnActivityStopped(Activity activity) + { + } + } +} diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs new file mode 100644 index 00000000..d337326d --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs @@ -0,0 +1,104 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using CoreMotion; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace LaunchDarkly.Xamarin.Platform +{ + public static partial class Platform + { + [DllImport(ObjCRuntime.Constants.SystemLibrary, EntryPoint = "sysctlbyname")] + internal static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); + + internal static string GetSystemLibraryProperty(string property) + { + var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); + SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); + + var propertyLength = Marshal.ReadInt32(lengthPtr); + + if (propertyLength == 0) + { + Marshal.FreeHGlobal(lengthPtr); + throw new InvalidOperationException("Unable to read length of property."); + } + + var valuePtr = Marshal.AllocHGlobal(propertyLength); + SysctlByName(property, valuePtr, lengthPtr, IntPtr.Zero, 0); + + var returnValue = Marshal.PtrToStringAnsi(valuePtr); + + Marshal.FreeHGlobal(lengthPtr); + Marshal.FreeHGlobal(valuePtr); + + return returnValue; + } + + internal static bool HasOSVersion(int major, int minor) => + UIDevice.CurrentDevice.CheckSystemVersion(major, minor); + + internal static UIViewController GetCurrentViewController(bool throwIfNull = true) + { + UIViewController viewController = null; + + var window = UIApplication.SharedApplication.KeyWindow; + + if (window.WindowLevel == UIWindowLevel.Normal) + viewController = window.RootViewController; + + if (viewController == null) + { + window = UIApplication.SharedApplication + .Windows + .OrderByDescending(w => w.WindowLevel) + .FirstOrDefault(w => w.RootViewController != null && w.WindowLevel == UIWindowLevel.Normal); + + if (window == null) + throw new InvalidOperationException("Could not find current view controller."); + else + viewController = window.RootViewController; + } + + while (viewController.PresentedViewController != null) + viewController = viewController.PresentedViewController; + + if (throwIfNull && viewController == null) + throw new InvalidOperationException("Could not find current view controller."); + + return viewController; + } + + static CMMotionManager motionManager; + + internal static CMMotionManager MotionManager => + motionManager ?? (motionManager = new CMMotionManager()); + + internal static NSOperationQueue GetCurrentQueue() => + NSOperationQueue.CurrentQueue ?? new NSOperationQueue(); + } +} diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs new file mode 100644 index 00000000..0120f880 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs @@ -0,0 +1,30 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace LaunchDarkly.Xamarin.Platform +{ +#if !NETSTANDARD + public static partial class Platform + { + } +#endif +} diff --git a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs index fcb57d2a..25bb9808 100644 --- a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs +++ b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs @@ -2,3 +2,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.iOS.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Android.Tests")] From 2b5faf0728003d0dd43fa98471cfc85e623f1849 Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 19 Mar 2019 15:36:20 -0700 Subject: [PATCH 059/499] feat(MobilePollingProcessor.shared.cs, LdClient.shared.cs, BackgroundAdapter.android.cs, Configuration.shared.cs): Android lifecycle callbacks and background polling work --- .../LDAndroidTests.cs | 24 ++++++++++++++----- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 4 +++- LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs | 21 +++++++++++----- .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 2 +- .../BackgroundAdapter.android.cs | 4 ++++ .../BackgroundAdapter.ios.cs | 5 ++++ .../Configuration.shared.cs | 14 +---------- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 14 +++++++++-- .../MobilePollingProcessor.shared.cs | 9 +++++-- 9 files changed, 66 insertions(+), 31 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs index 0904b94b..07069142 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs +++ b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs @@ -6,18 +6,30 @@ namespace LaunchDarkly.Xamarin.Android.Tests [TestFixture] public class LDAndroidTests { - [SetUp] - public void Setup() { } + private ILdMobileClient client; + [SetUp] + public void Setup() + { + var user = LaunchDarkly.Client.User.WithKey("test-user"); + client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, TimeSpan.Zero); + } [TearDown] - public void Tear() { } + public void Tear() { LdClient.Instance = null; } + + [Test] + public void BooleanFeatureFlag() + { + Console.WriteLine("Test Boolean Variation"); + Assert.True(client.BoolVariation("boolean-feature-flag")); + } [Test] - public void Pass() + public void IntFeatureFlag() { - Console.WriteLine("test1"); - Assert.True(true); + Console.WriteLine("Test Integer Variation"); + Assert.True(client.IntVariation("int-feature-flag") == 2); } } } diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index 47653e52..f0c65929 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -41,7 +41,9 @@ - + + ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\monoandroid81\LaunchDarkly.Xamarin.dll + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs index c70de6b8..d02cc5f2 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs +++ b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs @@ -1,25 +1,34 @@ using System; using NUnit.Framework; -//using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin.iOS.Tests { [TestFixture] public class LDiOSTests { + private ILdMobileClient client; + [SetUp] public void Setup() { - //LdClient client = LdClient.Init(config, user, TimeSpan.Zero); + var user = LaunchDarkly.Client.User.WithKey("test-user"); + client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, TimeSpan.Zero); } [TearDown] - public void Tear() { } + public void Tear() { LdClient.Instance = null;} + + [Test] + public void BooleanFeatureFlag() + { + Console.WriteLine("Test Boolean Variation"); + Assert.True(client.BoolVariation("boolean-feature-flag")); + } [Test] - public void Pass() + public void IntFeatureFlag() { - Console.WriteLine("test1"); - Assert.True(true); + Console.WriteLine("Test Integer Variation"); + Assert.True(client.IntVariation("int-feature-flag") == 1); } } } diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj index 5ad98579..8d99a7f8 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -82,7 +82,7 @@ - ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\Xamarin.iOS + ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\xamarin.ios10\LaunchDarkly.Xamarin.dll diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs index 586168c0..b2914758 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Xamarin; using Android.App; using Android.OS; +using Android.Util; namespace LaunchDarkly.Xamarin.BackgroundAdapter { @@ -14,6 +15,7 @@ public void EnableBackgrounding(IBackgroundingState backgroundingState) { if (_callbacks == null) { + Log.Info("Xamarin", "Enable Backgrounding"); _callbacks = new ActivityLifecycleCallbacks(backgroundingState); application = (Application)Application.Context; application.RegisterActivityLifecycleCallbacks(_callbacks); @@ -65,11 +67,13 @@ public void OnActivityDestroyed(Activity activity) public void OnActivityPaused(Activity activity) { + Log.Info("Xamarin", "Entering Background"); _backgroundingState.EnterBackgroundAsync(); } public void OnActivityResumed(Activity activity) { + Log.Info("Xamarin", "Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs index 47190d6c..b34aadc4 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -1,15 +1,18 @@ using System; using LaunchDarkly.Xamarin; using UIKit; +using Common.Logging; namespace LaunchDarkly.Xamarin.BackgroundAdapter { public class BackgroundAdapter : UIApplicationDelegate, IPlatformAdapter { private IBackgroundingState _backgroundingState; + private static readonly ILog Log = LogManager.GetLogger(typeof(BackgroundAdapter)); public void EnableBackgrounding(IBackgroundingState backgroundingState) { + Log.Debug("Enable Backgrounding"); _backgroundingState = backgroundingState; } @@ -40,11 +43,13 @@ protected void _Dispose(bool disposing) public override void WillEnterForeground(UIApplication application) { + Log.Debug("Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } public override void DidEnterBackground(UIApplication application) { + Log.Debug("Entering Background"); _backgroundingState.EnterBackgroundAsync(); } } diff --git a/src/LaunchDarkly.Xamarin/Configuration.shared.cs b/src/LaunchDarkly.Xamarin/Configuration.shared.cs index 0435e21e..b2979d51 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.shared.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.shared.cs @@ -220,7 +220,7 @@ public static Configuration Default(string mobileKey) UserKeysCapacity = DefaultUserKeysCapacity, UserKeysFlushInterval = DefaultUserKeysFlushInterval, InlineUsersInEvents = false, - EnableBackgroundUpdating = false, + EnableBackgroundUpdating = true, UseReport = true }; @@ -593,18 +593,6 @@ internal static Configuration WithUpdateProcessor(this Configuration configurati return configuration; } - /// - /// Sets the Events URI. - /// - /// Configuration. - /// Events URI. - /// the same Configuration instance - public static Configuration WithEventsURI(this Configuration configuration, Uri eventsUri) - { - configuration.EventsUri = eventsUri; - return configuration; - } - /// /// Sets the ISimplePersistance instance, used internally for stubbing mock instances. /// diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 1af85dc4..32758a4a 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -71,6 +71,8 @@ public sealed class LdClient : ILdMobileClient configuration.PlatformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); + Log.Debug("After Platform Adapter"); + Config = configuration; connectionLock = new SemaphoreSlim(1, 1); @@ -577,10 +579,12 @@ internal async Task EnterBackgroundAsync() // if using Streaming, processor needs to be reset if (Config.IsStreamingEnabled) { + Console.WriteLine("StreamingEnabled"); ClearUpdateProcessor(); Config.IsStreamingEnabled = false; if (Config.EnableBackgroundUpdating) { + Console.WriteLine("BackgroundEnabled"); await RestartUpdateProcessorAsync(); } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); @@ -589,6 +593,7 @@ internal async Task EnterBackgroundAsync() { if (Config.EnableBackgroundUpdating) { + Console.WriteLine("BackgroundEnabled"); await PingPollingProcessorAsync(); } } @@ -626,7 +631,8 @@ void PingPollingProcessor() var pollingProcessor = updateProcessor as MobilePollingProcessor; if (pollingProcessor != null) { - var waitTask = pollingProcessor.PingAndWait(); + Console.WriteLine("PingPollingProcessor"); + var waitTask = pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); waitTask.Wait(); } } @@ -636,7 +642,8 @@ async Task PingPollingProcessorAsync() var pollingProcessor = updateProcessor as MobilePollingProcessor; if (pollingProcessor != null) { - await pollingProcessor.PingAndWait(); + Console.WriteLine("PingPollingProcessorAsync"); + await pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); } } @@ -662,16 +669,19 @@ internal LdClientBackgroundingState(LdClient client) public async Task EnterBackgroundAsync() { + Console.WriteLine("EnterBackgroundAsyncc"); await _client.EnterBackgroundAsync(); } public async Task ExitBackgroundAsync() { + Console.WriteLine("ExitBackgroundAsync"); await _client.EnterForegroundAsync(); } public async Task BackgroundUpdateAsync() { + Console.WriteLine("BackgroundUpdateAsync"); await _client.BackgroundTickAsync(); } } diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs index b36e341c..2a0b9e19 100644 --- a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs +++ b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs @@ -54,9 +54,14 @@ bool IMobileUpdateProcessor.Initialized() return _initialized == INITIALIZED; } - public async Task PingAndWait() + public async Task PingAndWait(TimeSpan backgroundPollingInterval) { - await UpdateTaskAsync(); + while (!_disposed) + { + Console.WriteLine("PingAndWait"); + await UpdateTaskAsync(); + await Task.Delay(backgroundPollingInterval); + } } private async Task UpdateTaskLoopAsync() From d468886d6df58504e1482466c02bdec90e793298 Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 19 Mar 2019 16:30:49 -0700 Subject: [PATCH 060/499] feat(BackgroundAdapter.ios.cs): iOS foreground and background polling works but some errors do appear in the log, further investigation required --- .../BackgroundAdapter/BackgroundAdapter.android.cs | 7 +++---- .../BackgroundAdapter/BackgroundAdapter.ios.cs | 12 +++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs index b2914758..4780e8dc 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs @@ -2,7 +2,6 @@ using LaunchDarkly.Xamarin; using Android.App; using Android.OS; -using Android.Util; namespace LaunchDarkly.Xamarin.BackgroundAdapter { @@ -15,7 +14,7 @@ public void EnableBackgrounding(IBackgroundingState backgroundingState) { if (_callbacks == null) { - Log.Info("Xamarin", "Enable Backgrounding"); + Console.WriteLine("Enable Backgrounding"); _callbacks = new ActivityLifecycleCallbacks(backgroundingState); application = (Application)Application.Context; application.RegisterActivityLifecycleCallbacks(_callbacks); @@ -67,13 +66,13 @@ public void OnActivityDestroyed(Activity activity) public void OnActivityPaused(Activity activity) { - Log.Info("Xamarin", "Entering Background"); + Console.WriteLine("Entering Background"); _backgroundingState.EnterBackgroundAsync(); } public void OnActivityResumed(Activity activity) { - Log.Info("Xamarin", "Entering Foreground"); + Console.WriteLine("Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs index b34aadc4..da07da28 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -2,17 +2,22 @@ using LaunchDarkly.Xamarin; using UIKit; using Common.Logging; +using Foundation; namespace LaunchDarkly.Xamarin.BackgroundAdapter { - public class BackgroundAdapter : UIApplicationDelegate, IPlatformAdapter + public class BackgroundAdapter : IPlatformAdapter { private IBackgroundingState _backgroundingState; + private NSObject _foregroundHandle; + private NSObject _backgroundHandle; private static readonly ILog Log = LogManager.GetLogger(typeof(BackgroundAdapter)); public void EnableBackgrounding(IBackgroundingState backgroundingState) { Log.Debug("Enable Backgrounding"); + _foregroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, HandleWillEnterForeground); + _backgroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, HandleWillEnterBackground); _backgroundingState = backgroundingState; } @@ -29,6 +34,7 @@ protected void _Dispose(bool disposing) } _backgroundingState = null; + _foregroundHandle = null; disposedValue = true; } @@ -41,13 +47,13 @@ protected void _Dispose(bool disposing) } #endregion - public override void WillEnterForeground(UIApplication application) + private void HandleWillEnterForeground(NSNotification notification) { Log.Debug("Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } - public override void DidEnterBackground(UIApplication application) + private void HandleWillEnterBackground(NSNotification notification) { Log.Debug("Entering Background"); _backgroundingState.EnterBackgroundAsync(); From b26a8ce149b9df8bc742973bd4601b7655bcca9d Mon Sep 17 00:00:00 2001 From: torchhound Date: Tue, 19 Mar 2019 17:14:52 -0700 Subject: [PATCH 061/499] feat(src/): changed xamarin essentials code to internal --- .../BackgroundAdapter/BackgroundAdapter.android.cs | 2 +- .../BackgroundAdapter/BackgroundAdapter.ios.cs | 2 +- .../BackgroundAdapter/BackgroundAdapter.netstandard.cs | 2 +- src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs | 2 +- src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs | 2 +- .../Connectivity/Connectivity.ios.reachability.cs | 2 +- .../Connectivity/Connectivity.netstandard.cs | 2 +- src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs | 4 ++-- .../Connectivity/Connectivity.shared.enums.cs | 4 ++-- src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs | 2 +- src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs | 2 +- src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs | 2 +- src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs | 2 +- src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs | 2 +- .../Permissions/Permissions.shared.enums.cs | 4 ++-- src/LaunchDarkly.Xamarin/Platform/Platform.android.cs | 4 ++-- src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs | 2 +- src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs | 2 +- src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs | 2 +- src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs | 2 +- .../Preferences/Preferences.netstandard.cs | 2 +- src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs | 2 +- 22 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs index 4780e8dc..786690cf 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Xamarin.BackgroundAdapter { - public class BackgroundAdapter : IPlatformAdapter + internal class BackgroundAdapter : IPlatformAdapter { private static ActivityLifecycleCallbacks _callbacks; private Application application; diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs index da07da28..a4b15d37 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -6,7 +6,7 @@ namespace LaunchDarkly.Xamarin.BackgroundAdapter { - public class BackgroundAdapter : IPlatformAdapter + internal class BackgroundAdapter : IPlatformAdapter { private IBackgroundingState _backgroundingState; private NSObject _foregroundHandle; diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs index f5f66010..b7e2038b 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin.BackgroundAdapter { - public class BackgroundAdapter : IPlatformAdapter + internal class BackgroundAdapter : IPlatformAdapter { public void Dispose() { diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs index 94c6fd47..d91290db 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs @@ -30,7 +30,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Connectivity { - public partial class Connectivity + internal partial class Connectivity { static ConnectivityBroadcastReceiver conectivityReceiver; diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs index 6a53fad9..162f2d18 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs @@ -25,7 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Connectivity { - public static partial class Connectivity + internal static partial class Connectivity { static ReachabilityListener listener; diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs index 4963da02..0c8f5018 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs @@ -36,7 +36,7 @@ enum NetworkStatus ReachableViaWiFiNetwork } - static class Reachability + internal static class Reachability { internal const string HostName = "www.microsoft.com"; diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs index 22c73f8f..738f7738 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs @@ -25,7 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Connectivity { - public static partial class Connectivity + internal static partial class Connectivity { static NetworkAccess PlatformNetworkAccess => throw new NotImplementedException(); diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs index 00e4ec58..3282deeb 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs @@ -26,7 +26,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Connectivity { - public static partial class Connectivity + internal static partial class Connectivity { static event EventHandler ConnectivityChangedInternal; @@ -87,7 +87,7 @@ static void OnConnectivityChanged(ConnectivityChangedEventArgs e) } } - public class ConnectivityChangedEventArgs : EventArgs + internal class ConnectivityChangedEventArgs : EventArgs { public ConnectivityChangedEventArgs(NetworkAccess access, IEnumerable connectionProfiles) { diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs index e6a7c423..88e37766 100644 --- a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs +++ b/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Connectivity { - public enum ConnectionProfile + internal enum ConnectionProfile { Unknown = 0, Bluetooth = 1, @@ -31,7 +31,7 @@ public enum ConnectionProfile WiFi = 4 } - public enum NetworkAccess + internal enum NetworkAccess { Unknown = 0, None = 1, diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs index 338cf637..1162805d 100644 --- a/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs @@ -25,7 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.MainThread { - public static partial class MainThread + internal static partial class MainThread { static Handler handler; diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs index 9ac0fb94..3ae74c81 100644 --- a/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs @@ -25,7 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.MainThread { - public static partial class MainThread + internal static partial class MainThread { static bool PlatformIsMainThread => NSThread.Current.IsMainThread; diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs index 0ef92edf..1a80cc56 100644 --- a/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs @@ -24,7 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.MainThread { - public static partial class MainThread + internal static partial class MainThread { static void PlatformBeginInvokeOnMainThread(Action action) => throw new NotImplementedException(); diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs b/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs index 3b72d83c..021164e2 100644 --- a/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs +++ b/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs @@ -25,7 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.MainThread { - public static partial class MainThread + internal static partial class MainThread { public static bool IsMainThread => PlatformIsMainThread; diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs index 5e5fe6c8..17499e08 100644 --- a/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs @@ -158,7 +158,7 @@ internal static void OnRequestPermissionsResult(int requestCode, string[] permis } } - static class PermissionTypeExtensions + internal static class PermissionTypeExtensions { internal static IEnumerable ToAndroidPermissions(this PermissionType permissionType, bool onlyRuntimePermissions) { diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs index 5e4366b3..ce24091a 100644 --- a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs +++ b/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Permissions { - enum PermissionStatus + internal enum PermissionStatus { // Denied by user Denied, @@ -40,7 +40,7 @@ enum PermissionStatus Unknown } - enum PermissionType + internal enum PermissionType { Unknown, Battery, diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs index bf019408..435768ac 100644 --- a/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs @@ -34,7 +34,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Platform { - public static partial class Platform + internal static partial class Platform { static ActivityLifecycleContextListener lifecycleListener; @@ -136,7 +136,7 @@ internal static void SetLocale(Java.Util.Locale locale) } } - class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks + internal class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks { WeakReference currentActivity = new WeakReference(null); diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs index d337326d..b416338f 100644 --- a/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs @@ -30,7 +30,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Platform { - public static partial class Platform + internal static partial class Platform { [DllImport(ObjCRuntime.Constants.SystemLibrary, EntryPoint = "sysctlbyname")] internal static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs b/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs index 0120f880..59395a07 100644 --- a/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs +++ b/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs @@ -23,7 +23,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Platform { #if !NETSTANDARD - public static partial class Platform + internal static partial class Platform { } #endif diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs index b3616733..381029fb 100644 --- a/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs @@ -28,7 +28,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { - public static partial class Preferences + internal static partial class Preferences { static readonly object locker = new object(); diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs index ad508f29..9497cd09 100644 --- a/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs @@ -26,7 +26,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { - public static partial class Preferences + internal static partial class Preferences { static readonly object locker = new object(); diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs index 02694894..6aa16790 100644 --- a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs @@ -24,7 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { - public static partial class Preferences + internal static partial class Preferences { static bool PlatformContainsKey(string key, string sharedName) => throw new NotImplementedException(); diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs b/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs index 6786e940..62997c12 100644 --- a/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs +++ b/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs @@ -24,7 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { - public static partial class Preferences + internal static partial class Preferences { internal static string GetPrivatePreferencesSharedName(string feature) => $"LaunchDarkly.Xamarin.{feature}"; From 158e71004084c8407846c9964ea2832f95919619 Mon Sep 17 00:00:00 2001 From: torchhound Date: Thu, 21 Mar 2019 11:08:00 -0700 Subject: [PATCH 062/499] fix(LDAndroidTests.cs, LDiOSTests.cs, AndroidManifest.xml, packages.config): platform specific unit tests build and pass --- .../LDAndroidTests.cs | 3 +- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 116 +++ .../Properties/AndroidManifest.xml | 1 + .../Resources/Resource.designer.cs | 803 +++++++++++++++++- .../packages.config | 29 + LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs | 5 +- .../BackgroundAdapter.ios.cs | 2 +- 7 files changed, 909 insertions(+), 50 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs index 07069142..502cabd8 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs +++ b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs @@ -12,7 +12,8 @@ public class LDAndroidTests public void Setup() { var user = LaunchDarkly.Client.User.WithKey("test-user"); - client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, TimeSpan.Zero); + var timeSpan = TimeSpan.FromSeconds(10); + client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, timeSpan); } [TearDown] diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index f0c65929..4f15dec9 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -74,6 +74,93 @@ ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\monoandroid71\Plugin.DeviceInfo.dll + + ..\packages\Xamarin.Android.Support.Annotations.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Annotations.dll + + + ..\packages\Xamarin.Android.Arch.Core.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Common.dll + + + ..\packages\Xamarin.Android.Arch.Core.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Runtime.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.Core.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.Core.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Runtime.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.ViewModel.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.ViewModel.dll + + + ..\packages\Xamarin.Android.Support.Collections.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Collections.dll + + + ..\packages\Xamarin.Android.Support.CursorAdapter.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CursorAdapter.dll + + + ..\packages\Xamarin.Android.Support.DocumentFile.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DocumentFile.dll + + + ..\packages\Xamarin.Android.Support.Interpolator.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Interpolator.dll + + + ..\packages\Xamarin.Android.Support.LocalBroadcastManager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.LocalBroadcastManager.dll + + + ..\packages\Xamarin.Android.Support.Print.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Print.dll + + + ..\packages\Xamarin.Android.Support.VersionedParcelable.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.VersionedParcelable.dll + + + ..\packages\Xamarin.Android.Support.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Compat.dll + + + ..\packages\Xamarin.Android.Support.AsyncLayoutInflater.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.AsyncLayoutInflater.dll + + + ..\packages\Xamarin.Android.Support.CustomView.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CustomView.dll + + + ..\packages\Xamarin.Android.Support.CoordinaterLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CoordinaterLayout.dll + + + ..\packages\Xamarin.Android.Support.DrawerLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DrawerLayout.dll + + + ..\packages\Xamarin.Android.Support.Loader.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Loader.dll + + + ..\packages\Xamarin.Android.Support.Core.Utils.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.Utils.dll + + + ..\packages\Xamarin.Android.Support.Media.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Media.Compat.dll + + + ..\packages\Xamarin.Android.Support.SlidingPaneLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SlidingPaneLayout.dll + + + ..\packages\Xamarin.Android.Support.SwipeRefreshLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SwipeRefreshLayout.dll + + + ..\packages\Xamarin.Android.Support.ViewPager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.ViewPager.dll + + + ..\packages\Xamarin.Android.Support.Core.UI.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.UI.dll + + + ..\packages\Xamarin.Android.Support.Fragment.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Fragment.dll + + + ..\packages\Xamarin.Android.Support.v4.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v4.dll + @@ -97,4 +184,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml b/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml index 5aedade7..1456678d 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml +++ b/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs b/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs index b7d502eb..8144be89 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs +++ b/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs @@ -53,6 +53,69 @@ public static void UpdateIdValues() public partial class Attribute { + // aapt resource value: 0x7f010009 + public const int alpha = 2130771977; + + // aapt resource value: 0x7f010000 + public const int coordinatorLayoutStyle = 2130771968; + + // aapt resource value: 0x7f010011 + public const int font = 2130771985; + + // aapt resource value: 0x7f01000a + public const int fontProviderAuthority = 2130771978; + + // aapt resource value: 0x7f01000d + public const int fontProviderCerts = 2130771981; + + // aapt resource value: 0x7f01000e + public const int fontProviderFetchStrategy = 2130771982; + + // aapt resource value: 0x7f01000f + public const int fontProviderFetchTimeout = 2130771983; + + // aapt resource value: 0x7f01000b + public const int fontProviderPackage = 2130771979; + + // aapt resource value: 0x7f01000c + public const int fontProviderQuery = 2130771980; + + // aapt resource value: 0x7f010010 + public const int fontStyle = 2130771984; + + // aapt resource value: 0x7f010013 + public const int fontVariationSettings = 2130771987; + + // aapt resource value: 0x7f010012 + public const int fontWeight = 2130771986; + + // aapt resource value: 0x7f010001 + public const int keylines = 2130771969; + + // aapt resource value: 0x7f010004 + public const int layout_anchor = 2130771972; + + // aapt resource value: 0x7f010006 + public const int layout_anchorGravity = 2130771974; + + // aapt resource value: 0x7f010003 + public const int layout_behavior = 2130771971; + + // aapt resource value: 0x7f010008 + public const int layout_dodgeInsetEdges = 2130771976; + + // aapt resource value: 0x7f010007 + public const int layout_insetEdge = 2130771975; + + // aapt resource value: 0x7f010005 + public const int layout_keyline = 2130771973; + + // aapt resource value: 0x7f010002 + public const int statusBarBackground = 2130771970; + + // aapt resource value: 0x7f010014 + public const int ttcIndex = 2130771988; + static Attribute() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); @@ -63,62 +126,383 @@ private Attribute() } } + public partial class Color + { + + // aapt resource value: 0x7f060003 + public const int notification_action_color_filter = 2131099651; + + // aapt resource value: 0x7f060004 + public const int notification_icon_bg_color = 2131099652; + + // aapt resource value: 0x7f060000 + public const int notification_material_background_media_default_color = 2131099648; + + // aapt resource value: 0x7f060001 + public const int primary_text_default_material_dark = 2131099649; + + // aapt resource value: 0x7f060005 + public const int ripple_material_light = 2131099653; + + // aapt resource value: 0x7f060002 + public const int secondary_text_default_material_dark = 2131099650; + + // aapt resource value: 0x7f060006 + public const int secondary_text_default_material_light = 2131099654; + + static Color() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Color() + { + } + } + + public partial class Dimension + { + + // aapt resource value: 0x7f070008 + public const int compat_button_inset_horizontal_material = 2131165192; + + // aapt resource value: 0x7f070009 + public const int compat_button_inset_vertical_material = 2131165193; + + // aapt resource value: 0x7f07000a + public const int compat_button_padding_horizontal_material = 2131165194; + + // aapt resource value: 0x7f07000b + public const int compat_button_padding_vertical_material = 2131165195; + + // aapt resource value: 0x7f07000c + public const int compat_control_corner_material = 2131165196; + + // aapt resource value: 0x7f07000d + public const int compat_notification_large_icon_max_height = 2131165197; + + // aapt resource value: 0x7f07000e + public const int compat_notification_large_icon_max_width = 2131165198; + + // aapt resource value: 0x7f07000f + public const int notification_action_icon_size = 2131165199; + + // aapt resource value: 0x7f070010 + public const int notification_action_text_size = 2131165200; + + // aapt resource value: 0x7f070011 + public const int notification_big_circle_margin = 2131165201; + + // aapt resource value: 0x7f070005 + public const int notification_content_margin_start = 2131165189; + + // aapt resource value: 0x7f070012 + public const int notification_large_icon_height = 2131165202; + + // aapt resource value: 0x7f070013 + public const int notification_large_icon_width = 2131165203; + + // aapt resource value: 0x7f070006 + public const int notification_main_column_padding_top = 2131165190; + + // aapt resource value: 0x7f070007 + public const int notification_media_narrow_margin = 2131165191; + + // aapt resource value: 0x7f070014 + public const int notification_right_icon_size = 2131165204; + + // aapt resource value: 0x7f070004 + public const int notification_right_side_padding_top = 2131165188; + + // aapt resource value: 0x7f070015 + public const int notification_small_icon_background_padding = 2131165205; + + // aapt resource value: 0x7f070016 + public const int notification_small_icon_size_as_large = 2131165206; + + // aapt resource value: 0x7f070017 + public const int notification_subtext_size = 2131165207; + + // aapt resource value: 0x7f070018 + public const int notification_top_pad = 2131165208; + + // aapt resource value: 0x7f070019 + public const int notification_top_pad_large_text = 2131165209; + + // aapt resource value: 0x7f070000 + public const int subtitle_corner_radius = 2131165184; + + // aapt resource value: 0x7f070001 + public const int subtitle_outline_width = 2131165185; + + // aapt resource value: 0x7f070002 + public const int subtitle_shadow_offset = 2131165186; + + // aapt resource value: 0x7f070003 + public const int subtitle_shadow_radius = 2131165187; + + static Dimension() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Dimension() + { + } + } + + public partial class Drawable + { + + // aapt resource value: 0x7f020000 + public const int notification_action_background = 2130837504; + + // aapt resource value: 0x7f020001 + public const int notification_bg = 2130837505; + + // aapt resource value: 0x7f020002 + public const int notification_bg_low = 2130837506; + + // aapt resource value: 0x7f020003 + public const int notification_bg_low_normal = 2130837507; + + // aapt resource value: 0x7f020004 + public const int notification_bg_low_pressed = 2130837508; + + // aapt resource value: 0x7f020005 + public const int notification_bg_normal = 2130837509; + + // aapt resource value: 0x7f020006 + public const int notification_bg_normal_pressed = 2130837510; + + // aapt resource value: 0x7f020007 + public const int notification_icon_background = 2130837511; + + // aapt resource value: 0x7f02000a + public const int notification_template_icon_bg = 2130837514; + + // aapt resource value: 0x7f02000b + public const int notification_template_icon_low_bg = 2130837515; + + // aapt resource value: 0x7f020008 + public const int notification_tile_bg = 2130837512; + + // aapt resource value: 0x7f020009 + public const int notify_panel_notification_icon_bg = 2130837513; + + static Drawable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Drawable() + { + } + } + public partial class Id { - // aapt resource value: 0x7f040001 - public const int OptionHostName = 2130968577; + // aapt resource value: 0x7f0a0032 + public const int OptionHostName = 2131361842; - // aapt resource value: 0x7f040002 - public const int OptionPort = 2130968578; + // aapt resource value: 0x7f0a0033 + public const int OptionPort = 2131361843; - // aapt resource value: 0x7f040000 - public const int OptionRemoteServer = 2130968576; + // aapt resource value: 0x7f0a0031 + public const int OptionRemoteServer = 2131361841; - // aapt resource value: 0x7f040010 - public const int OptionsButton = 2130968592; + // aapt resource value: 0x7f0a0041 + public const int OptionsButton = 2131361857; - // aapt resource value: 0x7f04000b - public const int ResultFullName = 2130968587; + // aapt resource value: 0x7f0a003c + public const int ResultFullName = 2131361852; - // aapt resource value: 0x7f04000d - public const int ResultMessage = 2130968589; + // aapt resource value: 0x7f0a003e + public const int ResultMessage = 2131361854; - // aapt resource value: 0x7f04000c - public const int ResultResultState = 2130968588; + // aapt resource value: 0x7f0a003d + public const int ResultResultState = 2131361853; - // aapt resource value: 0x7f04000a - public const int ResultRunSingleMethodTest = 2130968586; + // aapt resource value: 0x7f0a003b + public const int ResultRunSingleMethodTest = 2131361851; - // aapt resource value: 0x7f04000e - public const int ResultStackTrace = 2130968590; + // aapt resource value: 0x7f0a003f + public const int ResultStackTrace = 2131361855; - // aapt resource value: 0x7f040006 - public const int ResultsFailed = 2130968582; + // aapt resource value: 0x7f0a0037 + public const int ResultsFailed = 2131361847; - // aapt resource value: 0x7f040003 - public const int ResultsId = 2130968579; + // aapt resource value: 0x7f0a0034 + public const int ResultsId = 2131361844; - // aapt resource value: 0x7f040007 - public const int ResultsIgnored = 2130968583; + // aapt resource value: 0x7f0a0038 + public const int ResultsIgnored = 2131361848; - // aapt resource value: 0x7f040008 - public const int ResultsInconclusive = 2130968584; + // aapt resource value: 0x7f0a0039 + public const int ResultsInconclusive = 2131361849; - // aapt resource value: 0x7f040009 - public const int ResultsMessage = 2130968585; + // aapt resource value: 0x7f0a003a + public const int ResultsMessage = 2131361850; - // aapt resource value: 0x7f040005 - public const int ResultsPassed = 2130968581; + // aapt resource value: 0x7f0a0036 + public const int ResultsPassed = 2131361846; - // aapt resource value: 0x7f040004 - public const int ResultsResult = 2130968580; + // aapt resource value: 0x7f0a0035 + public const int ResultsResult = 2131361845; - // aapt resource value: 0x7f04000f - public const int RunTestsButton = 2130968591; + // aapt resource value: 0x7f0a0040 + public const int RunTestsButton = 2131361856; - // aapt resource value: 0x7f040011 - public const int TestSuiteListView = 2130968593; + // aapt resource value: 0x7f0a0042 + public const int TestSuiteListView = 2131361858; + + // aapt resource value: 0x7f0a0020 + public const int action0 = 2131361824; + + // aapt resource value: 0x7f0a001d + public const int action_container = 2131361821; + + // aapt resource value: 0x7f0a0024 + public const int action_divider = 2131361828; + + // aapt resource value: 0x7f0a001e + public const int action_image = 2131361822; + + // aapt resource value: 0x7f0a001f + public const int action_text = 2131361823; + + // aapt resource value: 0x7f0a002e + public const int actions = 2131361838; + + // aapt resource value: 0x7f0a0017 + public const int all = 2131361815; + + // aapt resource value: 0x7f0a0018 + public const int async = 2131361816; + + // aapt resource value: 0x7f0a0019 + public const int blocking = 2131361817; + + // aapt resource value: 0x7f0a0008 + public const int bottom = 2131361800; + + // aapt resource value: 0x7f0a0021 + public const int cancel_action = 2131361825; + + // aapt resource value: 0x7f0a0009 + public const int center = 2131361801; + + // aapt resource value: 0x7f0a000a + public const int center_horizontal = 2131361802; + + // aapt resource value: 0x7f0a000b + public const int center_vertical = 2131361803; + + // aapt resource value: 0x7f0a0029 + public const int chronometer = 2131361833; + + // aapt resource value: 0x7f0a000c + public const int clip_horizontal = 2131361804; + + // aapt resource value: 0x7f0a000d + public const int clip_vertical = 2131361805; + + // aapt resource value: 0x7f0a000e + public const int end = 2131361806; + + // aapt resource value: 0x7f0a0030 + public const int end_padder = 2131361840; + + // aapt resource value: 0x7f0a000f + public const int fill = 2131361807; + + // aapt resource value: 0x7f0a0010 + public const int fill_horizontal = 2131361808; + + // aapt resource value: 0x7f0a0011 + public const int fill_vertical = 2131361809; + + // aapt resource value: 0x7f0a001a + public const int forever = 2131361818; + + // aapt resource value: 0x7f0a002b + public const int icon = 2131361835; + + // aapt resource value: 0x7f0a002f + public const int icon_group = 2131361839; + + // aapt resource value: 0x7f0a002a + public const int info = 2131361834; + + // aapt resource value: 0x7f0a001b + public const int italic = 2131361819; + + // aapt resource value: 0x7f0a0012 + public const int left = 2131361810; + + // aapt resource value: 0x7f0a0000 + public const int line1 = 2131361792; + + // aapt resource value: 0x7f0a0001 + public const int line3 = 2131361793; + + // aapt resource value: 0x7f0a0023 + public const int media_actions = 2131361827; + + // aapt resource value: 0x7f0a0016 + public const int none = 2131361814; + + // aapt resource value: 0x7f0a001c + public const int normal = 2131361820; + + // aapt resource value: 0x7f0a002d + public const int notification_background = 2131361837; + + // aapt resource value: 0x7f0a0026 + public const int notification_main_column = 2131361830; + + // aapt resource value: 0x7f0a0025 + public const int notification_main_column_container = 2131361829; + + // aapt resource value: 0x7f0a0013 + public const int right = 2131361811; + + // aapt resource value: 0x7f0a002c + public const int right_icon = 2131361836; + + // aapt resource value: 0x7f0a0027 + public const int right_side = 2131361831; + + // aapt resource value: 0x7f0a0014 + public const int start = 2131361812; + + // aapt resource value: 0x7f0a0022 + public const int status_bar_latest_event_content = 2131361826; + + // aapt resource value: 0x7f0a0002 + public const int tag_transition_group = 2131361794; + + // aapt resource value: 0x7f0a0003 + public const int tag_unhandled_key_event_manager = 2131361795; + + // aapt resource value: 0x7f0a0004 + public const int tag_unhandled_key_listeners = 2131361796; + + // aapt resource value: 0x7f0a0005 + public const int text = 2131361797; + + // aapt resource value: 0x7f0a0006 + public const int text2 = 2131361798; + + // aapt resource value: 0x7f0a0028 + public const int time = 2131361832; + + // aapt resource value: 0x7f0a0007 + public const int title = 2131361799; + + // aapt resource value: 0x7f0a0015 + public const int top = 2131361813; static Id() { @@ -130,20 +514,84 @@ private Id() } } + public partial class Integer + { + + // aapt resource value: 0x7f080000 + public const int cancel_button_image_alpha = 2131230720; + + // aapt resource value: 0x7f080001 + public const int status_bar_notification_info_maxnum = 2131230721; + + static Integer() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Integer() + { + } + } + public partial class Layout { - // aapt resource value: 0x7f030000 - public const int options = 2130903040; + // aapt resource value: 0x7f040000 + public const int notification_action = 2130968576; + + // aapt resource value: 0x7f040001 + public const int notification_action_tombstone = 2130968577; + + // aapt resource value: 0x7f040002 + public const int notification_media_action = 2130968578; + + // aapt resource value: 0x7f040003 + public const int notification_media_cancel_action = 2130968579; + + // aapt resource value: 0x7f040004 + public const int notification_template_big_media = 2130968580; + + // aapt resource value: 0x7f040005 + public const int notification_template_big_media_custom = 2130968581; + + // aapt resource value: 0x7f040006 + public const int notification_template_big_media_narrow = 2130968582; + + // aapt resource value: 0x7f040007 + public const int notification_template_big_media_narrow_custom = 2130968583; + + // aapt resource value: 0x7f040008 + public const int notification_template_custom_big = 2130968584; + + // aapt resource value: 0x7f040009 + public const int notification_template_icon_group = 2130968585; - // aapt resource value: 0x7f030001 - public const int results = 2130903041; + // aapt resource value: 0x7f04000a + public const int notification_template_lines_media = 2130968586; + + // aapt resource value: 0x7f04000b + public const int notification_template_media = 2130968587; + + // aapt resource value: 0x7f04000c + public const int notification_template_media_custom = 2130968588; - // aapt resource value: 0x7f030002 - public const int test_result = 2130903042; + // aapt resource value: 0x7f04000d + public const int notification_template_part_chronometer = 2130968589; + + // aapt resource value: 0x7f04000e + public const int notification_template_part_time = 2130968590; + + // aapt resource value: 0x7f04000f + public const int options = 2130968591; + + // aapt resource value: 0x7f040010 + public const int results = 2130968592; + + // aapt resource value: 0x7f040011 + public const int test_result = 2130968593; - // aapt resource value: 0x7f030003 - public const int test_suite = 2130903043; + // aapt resource value: 0x7f040012 + public const int test_suite = 2130968594; static Layout() { @@ -158,8 +606,8 @@ private Layout() public partial class Mipmap { - // aapt resource value: 0x7f020000 - public const int Icon = 2130837504; + // aapt resource value: 0x7f030000 + public const int Icon = 2130903040; static Mipmap() { @@ -170,6 +618,269 @@ private Mipmap() { } } + + public partial class String + { + + // aapt resource value: 0x7f090000 + public const int status_bar_notification_info_overflow = 2131296256; + + static String() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private String() + { + } + } + + public partial class Style + { + + // aapt resource value: 0x7f050006 + public const int TextAppearance_Compat_Notification = 2131034118; + + // aapt resource value: 0x7f050007 + public const int TextAppearance_Compat_Notification_Info = 2131034119; + + // aapt resource value: 0x7f050000 + public const int TextAppearance_Compat_Notification_Info_Media = 2131034112; + + // aapt resource value: 0x7f05000c + public const int TextAppearance_Compat_Notification_Line2 = 2131034124; + + // aapt resource value: 0x7f050004 + public const int TextAppearance_Compat_Notification_Line2_Media = 2131034116; + + // aapt resource value: 0x7f050001 + public const int TextAppearance_Compat_Notification_Media = 2131034113; + + // aapt resource value: 0x7f050008 + public const int TextAppearance_Compat_Notification_Time = 2131034120; + + // aapt resource value: 0x7f050002 + public const int TextAppearance_Compat_Notification_Time_Media = 2131034114; + + // aapt resource value: 0x7f050009 + public const int TextAppearance_Compat_Notification_Title = 2131034121; + + // aapt resource value: 0x7f050003 + public const int TextAppearance_Compat_Notification_Title_Media = 2131034115; + + // aapt resource value: 0x7f05000a + public const int Widget_Compat_NotificationActionContainer = 2131034122; + + // aapt resource value: 0x7f05000b + public const int Widget_Compat_NotificationActionText = 2131034123; + + // aapt resource value: 0x7f050005 + public const int Widget_Support_CoordinatorLayout = 2131034117; + + static Style() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Style() + { + } + } + + public partial class Styleable + { + + public static int[] ColorStateListItem = new int[] { + 16843173, + 16843551, + 2130771977}; + + // aapt resource value: 2 + public const int ColorStateListItem_alpha = 2; + + // aapt resource value: 1 + public const int ColorStateListItem_android_alpha = 1; + + // aapt resource value: 0 + public const int ColorStateListItem_android_color = 0; + + public static int[] CoordinatorLayout = new int[] { + 2130771969, + 2130771970}; + + // aapt resource value: 0 + public const int CoordinatorLayout_keylines = 0; + + // aapt resource value: 1 + public const int CoordinatorLayout_statusBarBackground = 1; + + public static int[] CoordinatorLayout_Layout = new int[] { + 16842931, + 2130771971, + 2130771972, + 2130771973, + 2130771974, + 2130771975, + 2130771976}; + + // aapt resource value: 0 + public const int CoordinatorLayout_Layout_android_layout_gravity = 0; + + // aapt resource value: 2 + public const int CoordinatorLayout_Layout_layout_anchor = 2; + + // aapt resource value: 4 + public const int CoordinatorLayout_Layout_layout_anchorGravity = 4; + + // aapt resource value: 1 + public const int CoordinatorLayout_Layout_layout_behavior = 1; + + // aapt resource value: 6 + public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 6; + + // aapt resource value: 5 + public const int CoordinatorLayout_Layout_layout_insetEdge = 5; + + // aapt resource value: 3 + public const int CoordinatorLayout_Layout_layout_keyline = 3; + + public static int[] FontFamily = new int[] { + 2130771978, + 2130771979, + 2130771980, + 2130771981, + 2130771982, + 2130771983}; + + // aapt resource value: 0 + public const int FontFamily_fontProviderAuthority = 0; + + // aapt resource value: 3 + public const int FontFamily_fontProviderCerts = 3; + + // aapt resource value: 4 + public const int FontFamily_fontProviderFetchStrategy = 4; + + // aapt resource value: 5 + public const int FontFamily_fontProviderFetchTimeout = 5; + + // aapt resource value: 1 + public const int FontFamily_fontProviderPackage = 1; + + // aapt resource value: 2 + public const int FontFamily_fontProviderQuery = 2; + + public static int[] FontFamilyFont = new int[] { + 16844082, + 16844083, + 16844095, + 16844143, + 16844144, + 2130771984, + 2130771985, + 2130771986, + 2130771987, + 2130771988}; + + // aapt resource value: 0 + public const int FontFamilyFont_android_font = 0; + + // aapt resource value: 2 + public const int FontFamilyFont_android_fontStyle = 2; + + // aapt resource value: 4 + public const int FontFamilyFont_android_fontVariationSettings = 4; + + // aapt resource value: 1 + public const int FontFamilyFont_android_fontWeight = 1; + + // aapt resource value: 3 + public const int FontFamilyFont_android_ttcIndex = 3; + + // aapt resource value: 6 + public const int FontFamilyFont_font = 6; + + // aapt resource value: 5 + public const int FontFamilyFont_fontStyle = 5; + + // aapt resource value: 8 + public const int FontFamilyFont_fontVariationSettings = 8; + + // aapt resource value: 7 + public const int FontFamilyFont_fontWeight = 7; + + // aapt resource value: 9 + public const int FontFamilyFont_ttcIndex = 9; + + public static int[] GradientColor = new int[] { + 16843165, + 16843166, + 16843169, + 16843170, + 16843171, + 16843172, + 16843265, + 16843275, + 16844048, + 16844049, + 16844050, + 16844051}; + + // aapt resource value: 7 + public const int GradientColor_android_centerColor = 7; + + // aapt resource value: 3 + public const int GradientColor_android_centerX = 3; + + // aapt resource value: 4 + public const int GradientColor_android_centerY = 4; + + // aapt resource value: 1 + public const int GradientColor_android_endColor = 1; + + // aapt resource value: 10 + public const int GradientColor_android_endX = 10; + + // aapt resource value: 11 + public const int GradientColor_android_endY = 11; + + // aapt resource value: 5 + public const int GradientColor_android_gradientRadius = 5; + + // aapt resource value: 0 + public const int GradientColor_android_startColor = 0; + + // aapt resource value: 8 + public const int GradientColor_android_startX = 8; + + // aapt resource value: 9 + public const int GradientColor_android_startY = 9; + + // aapt resource value: 6 + public const int GradientColor_android_tileMode = 6; + + // aapt resource value: 2 + public const int GradientColor_android_type = 2; + + public static int[] GradientColorItem = new int[] { + 16843173, + 16844052}; + + // aapt resource value: 0 + public const int GradientColorItem_android_color = 0; + + // aapt resource value: 1 + public const int GradientColorItem_android_offset = 1; + + static Styleable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Styleable() + { + } + } } } #pragma warning restore 1591 diff --git a/LaunchDarkly.Xamarin.Android.Tests/packages.config b/LaunchDarkly.Xamarin.Android.Tests/packages.config index f7f73da9..0da6d27d 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/packages.config +++ b/LaunchDarkly.Xamarin.Android.Tests/packages.config @@ -54,4 +54,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs index d02cc5f2..1f31ee3b 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs +++ b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs @@ -11,7 +11,8 @@ public class LDiOSTests [SetUp] public void Setup() { var user = LaunchDarkly.Client.User.WithKey("test-user"); - client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, TimeSpan.Zero); + var timeSpan = TimeSpan.FromSeconds(10); + client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, timeSpan); } [TearDown] @@ -28,7 +29,7 @@ public void BooleanFeatureFlag() public void IntFeatureFlag() { Console.WriteLine("Test Integer Variation"); - Assert.True(client.IntVariation("int-feature-flag") == 1); + Assert.True(client.IntVariation("int-feature-flag") == 2); } } } diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs index a4b15d37..7f0d9822 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -40,7 +40,7 @@ protected void _Dispose(bool disposing) } } - public new void Dispose() + public void Dispose() { _Dispose(true); GC.SuppressFinalize(this); From e9722151a8adebfb24fd6ad59f3ef2fdd6e84630 Mon Sep 17 00:00:00 2001 From: torchhound Date: Thu, 21 Mar 2019 17:12:53 -0700 Subject: [PATCH 063/499] feat(src/, LDAndroiTests.cs, LDiOSTests.cs): added additional platform specific unit tests, removed console.writelines, added background polling to default config, fixed polling processor start when streamin is enabled --- .../LDAndroidTests.cs | 28 ++++++++++++++++++- LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs | 28 ++++++++++++++++++- .../BackgroundAdapter.android.cs | 3 -- .../BackgroundAdapter.ios.cs | 3 -- .../Configuration.shared.cs | 1 + src/LaunchDarkly.Xamarin/LdClient.shared.cs | 11 +------- .../MobilePollingProcessor.shared.cs | 1 - 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs index 502cabd8..931b7c19 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs +++ b/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin.Android.Tests { @@ -13,7 +14,7 @@ public void Setup() { var user = LaunchDarkly.Client.User.WithKey("test-user"); var timeSpan = TimeSpan.FromSeconds(10); - client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, timeSpan); + client = LdClient.Init("MOBILE_KEY", user, timeSpan); } [TearDown] @@ -32,5 +33,30 @@ public void IntFeatureFlag() Console.WriteLine("Test Integer Variation"); Assert.True(client.IntVariation("int-feature-flag") == 2); } + + [Test] + public void StringFeatureFlag() + { + Console.WriteLine("Test String Variation"); + Assert.True(client.StringVariation("string-feature-flag", "false").Equals("bravo")); + } + + [Test] + public void JsonFeatureFlag() + { + string json = @"{ + ""test2"": ""testing2"" + }"; + Console.WriteLine("Test JSON Variation"); + JToken jsonToken = JToken.FromObject(JObject.Parse(json)); + Assert.True(JToken.DeepEquals(jsonToken, client.JsonVariation("json-feature-flag", "false"))); + } + + [Test] + public void FloatFeatureFlag() + { + Console.WriteLine("Test Float Variation"); + Assert.True(client.FloatVariation("float-feature-flag") == 1.5); + } } } diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs index 1f31ee3b..8deef83b 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs +++ b/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin.iOS.Tests { @@ -12,7 +13,7 @@ public class LDiOSTests public void Setup() { var user = LaunchDarkly.Client.User.WithKey("test-user"); var timeSpan = TimeSpan.FromSeconds(10); - client = LdClient.Init("mob-368413a0-28e1-495d-ab32-7aa389ac33b6", user, timeSpan); + client = LdClient.Init("MOBILE_KEY", user, timeSpan); } [TearDown] @@ -31,5 +32,30 @@ public void IntFeatureFlag() Console.WriteLine("Test Integer Variation"); Assert.True(client.IntVariation("int-feature-flag") == 2); } + + [Test] + public void StringFeatureFlag() + { + Console.WriteLine("Test String Variation"); + Assert.True(client.StringVariation("string-feature-flag", "false").Equals("bravo")); + } + + [Test] + public void JsonFeatureFlag() + { + string json = @"{ + ""test2"": ""testing2"" + }"; + Console.WriteLine("Test JSON Variation"); + JToken jsonToken = JToken.FromObject(JObject.Parse(json)); + Assert.True(JToken.DeepEquals(jsonToken, client.JsonVariation("json-feature-flag", "false"))); + } + + [Test] + public void FloatFeatureFlag() + { + Console.WriteLine("Test Float Variation"); + Assert.True(client.FloatVariation("float-feature-flag") == 1.5); + } } } diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs index 786690cf..ab7853f0 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs @@ -14,7 +14,6 @@ public void EnableBackgrounding(IBackgroundingState backgroundingState) { if (_callbacks == null) { - Console.WriteLine("Enable Backgrounding"); _callbacks = new ActivityLifecycleCallbacks(backgroundingState); application = (Application)Application.Context; application.RegisterActivityLifecycleCallbacks(_callbacks); @@ -66,13 +65,11 @@ public void OnActivityDestroyed(Activity activity) public void OnActivityPaused(Activity activity) { - Console.WriteLine("Entering Background"); _backgroundingState.EnterBackgroundAsync(); } public void OnActivityResumed(Activity activity) { - Console.WriteLine("Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs index 7f0d9822..457ae6f7 100644 --- a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs +++ b/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs @@ -15,7 +15,6 @@ internal class BackgroundAdapter : IPlatformAdapter public void EnableBackgrounding(IBackgroundingState backgroundingState) { - Log.Debug("Enable Backgrounding"); _foregroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, HandleWillEnterForeground); _backgroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, HandleWillEnterBackground); _backgroundingState = backgroundingState; @@ -49,13 +48,11 @@ public void Dispose() private void HandleWillEnterForeground(NSNotification notification) { - Log.Debug("Entering Foreground"); _backgroundingState.ExitBackgroundAsync(); } private void HandleWillEnterBackground(NSNotification notification) { - Log.Debug("Entering Background"); _backgroundingState.EnterBackgroundAsync(); } } diff --git a/src/LaunchDarkly.Xamarin/Configuration.shared.cs b/src/LaunchDarkly.Xamarin/Configuration.shared.cs index b2979d51..c2f965e4 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.shared.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.shared.cs @@ -208,6 +208,7 @@ public static Configuration Default(string mobileKey) EventQueueCapacity = DefaultEventQueueCapacity, EventQueueFrequency = DefaultEventQueueFrequency, PollingInterval = DefaultPollingInterval, + BackgroundPollingInterval = DefaultBackgroundPollingInterval, ReadTimeout = DefaultReadTimeout, ReconnectTime = DefaultReconnectTime, HttpClientTimeout = DefaultHttpClientTimeout, diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 32758a4a..98ab47d8 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -71,8 +71,6 @@ public sealed class LdClient : ILdMobileClient configuration.PlatformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); - Log.Debug("After Platform Adapter"); - Config = configuration; connectionLock = new SemaphoreSlim(1, 1); @@ -579,13 +577,12 @@ internal async Task EnterBackgroundAsync() // if using Streaming, processor needs to be reset if (Config.IsStreamingEnabled) { - Console.WriteLine("StreamingEnabled"); ClearUpdateProcessor(); Config.IsStreamingEnabled = false; if (Config.EnableBackgroundUpdating) { - Console.WriteLine("BackgroundEnabled"); await RestartUpdateProcessorAsync(); + await PingPollingProcessorAsync(); } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } @@ -593,7 +590,6 @@ internal async Task EnterBackgroundAsync() { if (Config.EnableBackgroundUpdating) { - Console.WriteLine("BackgroundEnabled"); await PingPollingProcessorAsync(); } } @@ -631,7 +627,6 @@ void PingPollingProcessor() var pollingProcessor = updateProcessor as MobilePollingProcessor; if (pollingProcessor != null) { - Console.WriteLine("PingPollingProcessor"); var waitTask = pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); waitTask.Wait(); } @@ -642,7 +637,6 @@ async Task PingPollingProcessorAsync() var pollingProcessor = updateProcessor as MobilePollingProcessor; if (pollingProcessor != null) { - Console.WriteLine("PingPollingProcessorAsync"); await pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); } } @@ -669,19 +663,16 @@ internal LdClientBackgroundingState(LdClient client) public async Task EnterBackgroundAsync() { - Console.WriteLine("EnterBackgroundAsyncc"); await _client.EnterBackgroundAsync(); } public async Task ExitBackgroundAsync() { - Console.WriteLine("ExitBackgroundAsync"); await _client.EnterForegroundAsync(); } public async Task BackgroundUpdateAsync() { - Console.WriteLine("BackgroundUpdateAsync"); await _client.BackgroundTickAsync(); } } diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs index 2a0b9e19..4b11ef1d 100644 --- a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs +++ b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs @@ -58,7 +58,6 @@ public async Task PingAndWait(TimeSpan backgroundPollingInterval) { while (!_disposed) { - Console.WriteLine("PingAndWait"); await UpdateTaskAsync(); await Task.Delay(backgroundPollingInterval); } From 137aa4123b1469f75793a886e50c5135e125ada7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 21 Mar 2019 18:32:44 -0700 Subject: [PATCH 064/499] fix merge --- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 80 +++++++++---------- .../{ValueType.cs => ValueType.shared.cs} | 72 ++++++++--------- 2 files changed, 76 insertions(+), 76 deletions(-) rename src/LaunchDarkly.Xamarin/{ValueType.cs => ValueType.shared.cs} (97%) diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 6399de4f..a38f064b 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -233,13 +233,13 @@ static void CreateInstance(Configuration configuration, User user) { bgPollInterval = configuration.BackgroundPollingInterval; } - try - { - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); - } - catch - { - Log.Info("Foreground/Background is only available on iOS and Android"); + try + { + Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); + } + catch + { + Log.Info("Foreground/Background is only available on iOS and Android"); } } @@ -314,61 +314,61 @@ void MobileConnectionManager_ConnectionChanged(bool isOnline) /// public bool BoolVariation(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueType.Bool, eventFactoryDefault).Value; - } - + return VariationInternal(key, defaultValue, ValueTypes.Bool, eventFactoryDefault).Value; + } + /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueType.Bool, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Bool, eventFactoryWithReasons); } /// public string StringVariation(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueType.String, eventFactoryDefault).Value; - } - + return VariationInternal(key, defaultValue, ValueTypes.String, eventFactoryDefault).Value; + } + /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueType.String, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.String, eventFactoryWithReasons); } /// public float FloatVariation(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueType.Float, eventFactoryDefault).Value; - } - + return VariationInternal(key, defaultValue, ValueTypes.Float, eventFactoryDefault).Value; + } + /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueType.Float, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Float, eventFactoryWithReasons); } /// public int IntVariation(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueType.Int, eventFactoryDefault).Value; - } - + return VariationInternal(key, defaultValue, ValueTypes.Int, eventFactoryDefault).Value; + } + /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueType.Int, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Int, eventFactoryWithReasons); } /// public JToken JsonVariation(string key, JToken defaultValue) { - return VariationInternal(key, defaultValue, ValueType.Json, eventFactoryDefault).Value; - } - + return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryDefault).Value; + } + /// public EvaluationDetail JsonVariationDetail(string key, JToken defaultValue) { - return VariationInternal(key, defaultValue, ValueType.Json, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryWithReasons); } EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) @@ -383,15 +383,15 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); - } - + } + var flag = flagCacheManager.FlagForUser(featureKey, User); - if (flag == null) - { + if (flag == null) + { Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.FLAG_NOT_FOUND)); - return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); + EvaluationErrorKind.FLAG_NOT_FOUND)); + return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); } featureFlagEvent = new FeatureFlagEvent(featureKey, flag); @@ -556,13 +556,13 @@ void Dispose(bool disposing) if (disposing) { Log.InfoFormat("Shutting down the LaunchDarkly client"); - try - { - platformAdapter.Dispose(); - } - catch - { - Log.Info("Foreground/Background is only available on iOS and Android"); + try + { + platformAdapter.Dispose(); + } + catch + { + Log.Info("Foreground/Background is only available on iOS and Android"); } updateProcessor.Dispose(); eventProcessor.Dispose(); diff --git a/src/LaunchDarkly.Xamarin/ValueType.cs b/src/LaunchDarkly.Xamarin/ValueType.shared.cs similarity index 97% rename from src/LaunchDarkly.Xamarin/ValueType.cs rename to src/LaunchDarkly.Xamarin/ValueType.shared.cs index 3874f81e..eef0c6ed 100644 --- a/src/LaunchDarkly.Xamarin/ValueType.cs +++ b/src/LaunchDarkly.Xamarin/ValueType.shared.cs @@ -9,65 +9,65 @@ internal class ValueType public Func ValueToJson { get; internal set; } } - internal class ValueType + internal class ValueTypes { - private static ArgumentException BadTypeException() - { - return new ArgumentException("unexpected data type"); + private static ArgumentException BadTypeException() + { + return new ArgumentException("unexpected data type"); } public static ValueType Bool = new ValueType { - ValueFromJson = json => - { - if (json.Type != JTokenType.Boolean) - { - throw BadTypeException(); - } - return json.Value(); + ValueFromJson = json => + { + if (json.Type != JTokenType.Boolean) + { + throw BadTypeException(); + } + return json.Value(); }, ValueToJson = value => new JValue(value) }; public static ValueType Int = new ValueType { - ValueFromJson = json => - { - if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) - { - throw BadTypeException(); - } - return json.Value(); + ValueFromJson = json => + { + if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) + { + throw BadTypeException(); + } + return json.Value(); }, ValueToJson = value => new JValue(value) }; public static ValueType Float = new ValueType { - ValueFromJson = json => - { - if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) - { - throw BadTypeException(); - } - return json.Value(); + ValueFromJson = json => + { + if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) + { + throw BadTypeException(); + } + return json.Value(); }, ValueToJson = value => new JValue(value) }; public static ValueType String = new ValueType { - ValueFromJson = json => - { - if (json == null || json.Type == JTokenType.Null) - { - return null; // strings are always nullable - } - if (json.Type != JTokenType.String) - { - throw BadTypeException(); - } - return json.Value(); + ValueFromJson = json => + { + if (json == null || json.Type == JTokenType.Null) + { + return null; // strings are always nullable + } + if (json.Type != JTokenType.String) + { + throw BadTypeException(); + } + return json.Value(); }, ValueToJson = value => value == null ? null : new JValue(value) }; From 7099e7696ddd0251b1f51522ab38459082ec68a6 Mon Sep 17 00:00:00 2001 From: torchhound Date: Mon, 25 Mar 2019 10:38:59 -0700 Subject: [PATCH 065/499] fix(LdClient.shared.cs, Factory.shared.cs, Configuration.shared.cs): Handle platform adapter dispose exception, remove unnecessary android asset, remove platform adapter as a config option --- .../Assets/AboutAssets.txt | 19 ------------------- .../Configuration.shared.cs | 13 ------------- src/LaunchDarkly.Xamarin/Factory.shared.cs | 5 ----- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 8 +++----- 4 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt diff --git a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt deleted file mode 100644 index a9b0638e..00000000 --- a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt +++ /dev/null @@ -1,19 +0,0 @@ -Any raw assets you want to be deployed with your application can be placed in -this directory (and child directories) and given a Build Action of "AndroidAsset". - -These files will be deployed with your package and will be accessible using Android's -AssetManager, like this: - -public class ReadAsset : Activity -{ - protected override void OnCreate (Bundle bundle) - { - base.OnCreate (bundle); - - InputStream input = Assets.Open ("my_asset.txt"); - } -} - -Additionally, some Android functions will automatically load asset files: - -Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/src/LaunchDarkly.Xamarin/Configuration.shared.cs b/src/LaunchDarkly.Xamarin/Configuration.shared.cs index c2f965e4..d5fc70c3 100644 --- a/src/LaunchDarkly.Xamarin/Configuration.shared.cs +++ b/src/LaunchDarkly.Xamarin/Configuration.shared.cs @@ -125,7 +125,6 @@ public class Configuration : IMobileConfiguration internal ISimplePersistance Persister { get; set; } internal IDeviceInfo DeviceInfo { get; set; } internal IFeatureFlagListenerManager FeatureFlagListenerManager { get; set; } - internal IPlatformAdapter PlatformAdapter { get; set; } /// /// Default value for . @@ -658,17 +657,5 @@ public static Configuration WithBackgroundPollingInterval(this Configuration con configuration.BackgroundPollingInterval = backgroundPollingInternal; return configuration; } - - /// - /// Specifies a component that provides special functionality for the current mobile platform. - /// - /// Configuration. - /// An implementation of . - /// the same Configuration instance - public static Configuration WithPlatformAdapter(this Configuration configuration, IPlatformAdapter adapter) - { - configuration.PlatformAdapter = adapter; - return configuration; - } } } diff --git a/src/LaunchDarkly.Xamarin/Factory.shared.cs b/src/LaunchDarkly.Xamarin/Factory.shared.cs index fa082f67..70e56e9a 100644 --- a/src/LaunchDarkly.Xamarin/Factory.shared.cs +++ b/src/LaunchDarkly.Xamarin/Factory.shared.cs @@ -92,10 +92,5 @@ internal static IFeatureFlagListenerManager CreateFeatureFlagListenerManager(Con { return configuration.FeatureFlagListenerManager ?? new FeatureFlagListenerManager(); } - - internal static IPlatformAdapter CreatePlatformAdapter(Configuration configuration) - { - return configuration.PlatformAdapter ?? new NullPlatformAdapter(); - } } } diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 98ab47d8..0b316921 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -69,8 +69,6 @@ public sealed class LdClient : ILdMobileClient throw new ArgumentNullException("user"); } - configuration.PlatformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); - Config = configuration; connectionLock = new SemaphoreSlim(1, 1); @@ -78,7 +76,7 @@ public sealed class LdClient : ILdMobileClient persister = Factory.CreatePersister(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); - platformAdapter = Factory.CreatePlatformAdapter(configuration); + platformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); // If you pass in a user with a null or blank key, one will be assigned to them. if (String.IsNullOrEmpty(user.Key)) @@ -542,9 +540,9 @@ void Dispose(bool disposing) { platformAdapter.Dispose(); } - catch + catch(Exception error) { - Log.Info("Foreground/Background is only available on iOS and Android"); + Log.Error(error); } updateProcessor.Dispose(); eventProcessor.Dispose(); From 330a711f62bc290dabe9a0460faf477d53bd025b Mon Sep 17 00:00:00 2001 From: torchhound Date: Mon, 25 Mar 2019 14:37:42 -0700 Subject: [PATCH 066/499] feat(Factory.shared.cs, IBackgroundingState.shared.cs, LdClient.shared.cs, MobilePollingProcessor.shared.cs): removed updatebackgroundasync from backgrounding state, readded android asset to fix build, added polling interval based on foreground or background and streaming disabled, removed PingAndWait --- .../Assets/AboutAssets.txt | 20 ++++++ src/LaunchDarkly.Xamarin/Factory.shared.cs | 8 ++- .../IBackgroundingState.shared.cs | 5 -- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 70 ++++--------------- .../MobilePollingProcessor.shared.cs | 9 --- 5 files changed, 37 insertions(+), 75 deletions(-) create mode 100644 LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt diff --git a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt new file mode 100644 index 00000000..ef2ad785 --- /dev/null +++ b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt @@ -0,0 +1,20 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + + These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + + public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + + Additionally, some Android functions will automatically load asset files: + + Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); + diff --git a/src/LaunchDarkly.Xamarin/Factory.shared.cs b/src/LaunchDarkly.Xamarin/Factory.shared.cs index 70e56e9a..3dac0518 100644 --- a/src/LaunchDarkly.Xamarin/Factory.shared.cs +++ b/src/LaunchDarkly.Xamarin/Factory.shared.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; @@ -33,7 +34,8 @@ internal static IConnectionManager CreateConnectionManager(Configuration configu internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, User user, - IFlagCacheManager flagCacheManager, + IFlagCacheManager flagCacheManager, + TimeSpan pollingInterval, StreamManager.EventSourceCreator source = null) { if (configuration.MobileUpdateProcessor != null) @@ -59,7 +61,7 @@ internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration confi return new MobilePollingProcessor(featureFlagRequestor, flagCacheManager, user, - configuration.PollingInterval); + pollingInterval); } } diff --git a/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs b/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs index 019c2fbd..88708ddb 100644 --- a/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs +++ b/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs @@ -24,10 +24,5 @@ public interface IBackgroundingState /// resume the regular streaming or polling process. /// Task ExitBackgroundAsync(); - - /// - /// Tells the LaunchDarkly client to initiate a request for feature flag updates while in background mode. - /// - Task BackgroundUpdateAsync(); } } diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 59437688..1717d7c9 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -91,7 +91,7 @@ public sealed class LdClient : ILdMobileClient flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager); + updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, configuration.PollingInterval); eventProcessor = Factory.CreateEventProcessor(configuration); eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); @@ -502,7 +502,7 @@ public async Task IdentifyAsync(User user) try { User = userWithKey; - await RestartUpdateProcessorAsync(); + await RestartUpdateProcessorAsync(Config.PollingInterval); } finally { @@ -512,16 +512,16 @@ public async Task IdentifyAsync(User user) eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(userWithKey)); } - async Task RestartUpdateProcessorAsync() + async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) { - ClearAndSetUpdateProcessor(); + ClearAndSetUpdateProcessor(pollingInterval); await StartUpdateProcessorAsync(); } - void ClearAndSetUpdateProcessor() + void ClearAndSetUpdateProcessor(TimeSpan pollingInterval) { ClearUpdateProcessor(); - updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager); + updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval); } void ClearUpdateProcessor() @@ -591,31 +591,19 @@ public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener l internal async Task EnterBackgroundAsync() { - // if using Streaming, processor needs to be reset - if (Config.IsStreamingEnabled) + ClearUpdateProcessor(); + Config.IsStreamingEnabled = false; + if (Config.EnableBackgroundUpdating) { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - if (Config.EnableBackgroundUpdating) - { - await RestartUpdateProcessorAsync(); - await PingPollingProcessorAsync(); - } - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); - } - else - { - if (Config.EnableBackgroundUpdating) - { - await PingPollingProcessorAsync(); - } + await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); } + persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } internal async Task EnterForegroundAsync() { ResetProcessorForForeground(); - await RestartUpdateProcessorAsync(); + await RestartUpdateProcessorAsync(Config.PollingInterval); } void ResetProcessorForForeground() @@ -629,35 +617,6 @@ void ResetProcessorForForeground() } } - internal void BackgroundTick() - { - PingPollingProcessor(); - } - - internal async Task BackgroundTickAsync() - { - await PingPollingProcessorAsync(); - } - - void PingPollingProcessor() - { - var pollingProcessor = updateProcessor as MobilePollingProcessor; - if (pollingProcessor != null) - { - var waitTask = pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); - waitTask.Wait(); - } - } - - async Task PingPollingProcessorAsync() - { - var pollingProcessor = updateProcessor as MobilePollingProcessor; - if (pollingProcessor != null) - { - await pollingProcessor.PingAndWait(Config.BackgroundPollingInterval); - } - } - private Exception UnwrapAggregateException(AggregateException e) { if (e.InnerExceptions.Count == 1) @@ -687,10 +646,5 @@ public async Task ExitBackgroundAsync() { await _client.EnterForegroundAsync(); } - - public async Task BackgroundUpdateAsync() - { - await _client.BackgroundTickAsync(); - } } } \ No newline at end of file diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs index 4b11ef1d..9119e367 100644 --- a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs +++ b/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs @@ -54,15 +54,6 @@ bool IMobileUpdateProcessor.Initialized() return _initialized == INITIALIZED; } - public async Task PingAndWait(TimeSpan backgroundPollingInterval) - { - while (!_disposed) - { - await UpdateTaskAsync(); - await Task.Delay(backgroundPollingInterval); - } - } - private async Task UpdateTaskLoopAsync() { while (!_disposed) From de6b1b708e305122b19a002602b90acd18276cd5 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:44:10 -0700 Subject: [PATCH 067/499] fix(LdClient.shared.cs): forgot an argument to RestartUpdateProcessorAsync --- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 1717d7c9..9244059c 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -289,7 +289,7 @@ public async Task SetOnlineAsync(bool value) { if (online) { - await RestartUpdateProcessorAsync(); + await RestartUpdateProcessorAsync(Config.PollingInterval); } else { From f8a38396420e9c35c1a8cd7d4873f7c0e48d0c35 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:46:59 -0700 Subject: [PATCH 068/499] fix(MobileStreamingProcessorTests.cs): added additional timespan argument to factory method --- .../LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs index 7969e83c..c61fc10e 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs @@ -41,7 +41,7 @@ public MobileStreamingProcessorTests() private IMobileUpdateProcessor MobileStreamingProcessorStarted() { - var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, eventSourceFactory.Create()); + var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, TimeSpan.FromMinutes(5), eventSourceFactory.Create()); processor.Start(); return processor; } From d9796f49eec45365b14ead8b2e064fb9da151e9c Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:53:17 -0700 Subject: [PATCH 069/499] fix(LaunchDarkly.Xamarin.Android.Tests.csproj): removed unnecessary AboutAssets.txt --- .../Assets/AboutAssets.txt | 20 ------------------- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 3 --- 2 files changed, 23 deletions(-) delete mode 100644 LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt diff --git a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt b/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt deleted file mode 100644 index ef2ad785..00000000 --- a/LaunchDarkly.Xamarin.Android.Tests/Assets/AboutAssets.txt +++ /dev/null @@ -1,20 +0,0 @@ -Any raw assets you want to be deployed with your application can be placed in -this directory (and child directories) and given a Build Action of "AndroidAsset". - - These files will be deployed with your package and will be accessible using Android's -AssetManager, like this: - - public class ReadAsset : Activity -{ - protected override void OnCreate (Bundle bundle) - { - base.OnCreate (bundle); - - InputStream input = Assets.Open ("my_asset.txt"); - } -} - - Additionally, some Android functions will automatically load asset files: - - Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); - diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index 4f15dec9..e33858d0 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -180,9 +180,6 @@ - - - From ffccf37d31942908e85c38557c28eba11e3c1369 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 25 Mar 2019 15:50:11 -0700 Subject: [PATCH 070/499] ensure that LdClient instances are always cleaned up in tests --- .../LdClientEvaluationTests.cs | 187 ++++++++++-------- .../LdClientEventTests.cs | 12 +- .../LdClientTests.cs | 30 ++- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 9 + 4 files changed, 144 insertions(+), 94 deletions(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs index 84bc3bb0..5c47ce32 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs @@ -12,8 +12,18 @@ public class LdClientEvaluationTests { static readonly string appKey = "some app key"; static readonly string nonexistentFlagKey = "some flag key"; - static readonly User user = User.WithKey("userkey"); - + static readonly User user = User.WithKey("userkey"); + + public LdClientEvaluationTests() + { + TestUtil.ClearClient(); + } + + ~LdClientEvaluationTests() + { + TestUtil.ClearClient(); + } + private static LdClient ClientWithFlagsJson(string flagsJson) { var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); @@ -24,16 +34,19 @@ private static LdClient ClientWithFlagsJson(string flagsJson) public void BoolVariationReturnsValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true)); - var client = ClientWithFlagsJson(flagsJson); - - Assert.True(client.BoolVariation("flag-key", false)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.True(client.BoolVariation("flag-key", false)); + } } [Fact] public void BoolVariationReturnsDefaultForUnknownFlag() { - var client = ClientWithFlagsJson("{}"); - Assert.False(client.BoolVariation(nonexistentFlagKey)); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.False(client.BoolVariation(nonexistentFlagKey)); + } } [Fact] @@ -41,35 +54,40 @@ public void BoolVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true), 1, reason); - var client = ClientWithFlagsJson(flagsJson); - - var expected = new EvaluationDetail(true, 1, reason); - Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail(true, 1, reason); + Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); + } } [Fact] public void IntVariationReturnsValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3)); - var client = ClientWithFlagsJson(flagsJson); - - Assert.Equal(3, client.IntVariation("flag-key", 0)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 0)); + } } [Fact] public void IntVariationCoercesFloatValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3.0f)); - var client = ClientWithFlagsJson(flagsJson); - - Assert.Equal(3, client.IntVariation("flag-key", 0)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 0)); + } } [Fact] public void IntVariationReturnsDefaultForUnknownFlag() { - var client = ClientWithFlagsJson("{}"); - Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); + } } [Fact] @@ -77,35 +95,40 @@ public void IntVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3), 1, reason); - var client = ClientWithFlagsJson(flagsJson); - - var expected = new EvaluationDetail(3, 1, reason); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail(3, 1, reason); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); + } } [Fact] public void FloatVariationReturnsValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f)); - var client = ClientWithFlagsJson(flagsJson); - - Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); + } } [Fact] public void FloatVariationCoercesIntValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2)); - var client = ClientWithFlagsJson(flagsJson); - - Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); + } } [Fact] public void FloatVariationReturnsDefaultForUnknownFlag() { - var client = ClientWithFlagsJson("{}"); - Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); + } } [Fact] @@ -113,26 +136,30 @@ public void FloatVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f), 1, reason); - var client = ClientWithFlagsJson(flagsJson); - - var expected = new EvaluationDetail(2.5f, 1, reason); - Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail(2.5f, 1, reason); + Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); + } } [Fact] public void StringVariationReturnsValue() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); - var client = ClientWithFlagsJson(flagsJson); - - Assert.Equal("string value", client.StringVariation("flag-key", "")); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal("string value", client.StringVariation("flag-key", "")); + } } [Fact] public void StringVariationReturnsDefaultForUnknownFlag() { - var client = ClientWithFlagsJson("{}"); - Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); + } } [Fact] @@ -140,10 +167,11 @@ public void StringVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value"), 1, reason); - var client = ClientWithFlagsJson(flagsJson); - - var expected = new EvaluationDetail("string value", 1, reason); - Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail("string value", 1, reason); + Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); + } } [Fact] @@ -151,17 +179,20 @@ public void JsonVariationReturnsValue() { var jsonValue = new JObject { { "thing", new JValue("stuff") } }; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); - var client = ClientWithFlagsJson(flagsJson); - - var defaultValue = new JValue(3); - Assert.Equal(jsonValue, client.JsonVariation("flag-key", defaultValue)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var defaultValue = new JValue(3); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", defaultValue)); + } } [Fact] public void JsonVariationReturnsDefaultForUnknownFlag() { - var client = ClientWithFlagsJson("{}"); - Assert.Null(client.JsonVariation(nonexistentFlagKey, null)); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.Null(client.JsonVariation(nonexistentFlagKey, null)); + } } [Fact] @@ -170,57 +201,59 @@ public void JsonVariationDetailReturnsValue() var jsonValue = new JObject { { "thing", new JValue("stuff") } }; var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); - var client = ClientWithFlagsJson(flagsJson); - - var expected = new EvaluationDetail(jsonValue, 1, reason); - var result = client.JsonVariationDetail("flag-key", new JValue(3)); - // Note, JToken.Equals() doesn't work, so we need to test each property separately - Assert.True(JToken.DeepEquals(expected.Value, result.Value)); - Assert.Equal(expected.VariationIndex, result.VariationIndex); - Assert.Equal(expected.Reason, result.Reason); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail(jsonValue, 1, reason); + var result = client.JsonVariationDetail("flag-key", new JValue(3)); + // Note, JToken.Equals() doesn't work, so we need to test each property separately + Assert.True(JToken.DeepEquals(expected.Value, result.Value)); + Assert.Equal(expected.VariationIndex, result.VariationIndex); + Assert.Equal(expected.Reason, result.Reason); + } } [Fact] public void AllFlagsReturnsAllFlagValues() { var flagsJson = @"{""flag1"":{""value"":""a""},""flag2"":{""value"":""b""}}"; - var client = ClientWithFlagsJson(flagsJson); - - var result = client.AllFlags(); - Assert.Equal(2, result.Count); - Assert.Equal(new JValue("a"), result["flag1"]); - Assert.Equal(new JValue("b"), result["flag2"]); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var result = client.AllFlags(); + Assert.Equal(2, result.Count); + Assert.Equal(new JValue("a"), result["flag1"]); + Assert.Equal(new JValue("b"), result["flag2"]); + } } [Fact] public void DefaultValueReturnedIfValueTypeIsDifferent() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); - var client = TestUtil.CreateClient(config, user); - - Assert.Equal(3, client.IntVariation("flag-key", 3)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 3)); + } } [Fact] public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); - var client = TestUtil.CreateClient(config, user); - - var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); + } } [Fact] public void DefaultValueReturnedIfFlagValueIsNull() { string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", null); - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); - var client = TestUtil.CreateClient(config, user); - - Assert.Equal(3, client.IntVariation("flag-key", 3)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 3)); + } } } } diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs index 387467a1..89973126 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs @@ -7,7 +7,17 @@ namespace LaunchDarkly.Xamarin.Tests public class LdClientEventTests { private static readonly User user = User.WithKey("userkey"); - private MockEventProcessor eventProcessor = new MockEventProcessor(); + private MockEventProcessor eventProcessor = new MockEventProcessor(); + + public LdClientEventTests() + { + TestUtil.ClearClient(); + } + + ~LdClientEventTests() + { + TestUtil.ClearClient(); + } public LdClient MakeClient(User user, string flagsJson) { diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs index 0f7f3272..46cdc271 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs @@ -9,6 +9,16 @@ public class DefaultLdClientTests static readonly string appKey = "some app key"; static readonly User simpleUser = User.WithKey("user-key"); + public DefaultLdClientTests() + { + TestUtil.ClearClient(); + } + + ~DefaultLdClientTests() + { + TestUtil.ClearClient(); + } + LdClient Client() { var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); @@ -39,14 +49,8 @@ public void CannotCreateClientWithNegativeWaitTime() public void CanCreateClientWithInfiniteWaitTime() { Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); - try - { - using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } - } - finally - { - LdClient.Instance = null; - } + using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } + TestUtil.ClearClient(); } [Fact] @@ -87,16 +91,10 @@ public void SharedClientIsTheOnlyClientAvailable() var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { - try - { - Assert.ThrowsAsync(async () => await LdClient.InitAsync(config, simpleUser)); - } - finally - { - LdClient.Instance = null; - } + Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); } } + TestUtil.ClearClient(); } [Fact] diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index 57c8b013..abdd3d4e 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -25,6 +25,15 @@ public static LdClient CreateClient(Configuration config, User user) } } + public static void ClearClient() + { + if (LdClient.Instance != null) + { + (LdClient.Instance as IDisposable).Dispose(); + LdClient.Instance = null; + } + } + public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) { JObject fo = new JObject { { "value", value } }; From ee940e3e7b16b39828d5ce9ad804fc470cbef05c Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 26 Mar 2019 09:33:37 -0700 Subject: [PATCH 071/499] Bump LD Common to beta4 --- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 0d329879..8e71ef7e 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -23,7 +23,7 @@ - + From 420c6b29865d94c522c7c7750dc786e4d2ae2ed5 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 26 Mar 2019 09:39:06 -0700 Subject: [PATCH 072/499] Fixed dotnet restore errors, forgot to bump LD Common beta4 in tests --- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 3 ++- .../packages.config | 24 +++++++++---------- .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 3 ++- .../packages.config | 24 +++++++++---------- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index e33858d0..419ce6c7 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -54,7 +54,7 @@ ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll - ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.12.0.1\lib\netstandard2.0\Newtonsoft.Json.dll @@ -210,4 +210,5 @@ + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.Android.Tests/packages.config b/LaunchDarkly.Xamarin.Android.Tests/packages.config index 0da6d27d..dab9f17d 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/packages.config +++ b/LaunchDarkly.Xamarin.Android.Tests/packages.config @@ -4,16 +4,16 @@ - - + + - - + + - + @@ -26,32 +26,32 @@ - + - + - - + + - + - + - + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj index 8d99a7f8..5c0ed07e 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -94,7 +94,7 @@ ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll - ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.12.0.1\lib\netstandard2.0\Newtonsoft.Json.dll @@ -125,4 +125,5 @@ + \ No newline at end of file diff --git a/LaunchDarkly.Xamarin.iOS.Tests/packages.config b/LaunchDarkly.Xamarin.iOS.Tests/packages.config index d0029198..6341915f 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/packages.config +++ b/LaunchDarkly.Xamarin.iOS.Tests/packages.config @@ -4,15 +4,15 @@ - - + + - - + + - + @@ -25,32 +25,32 @@ - + - + - - + + - + - + - + \ No newline at end of file diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index fc7af226..4db6963b 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + From 27c66edb3f39dcc21f36c63e1c97ef41a624a00a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 26 Mar 2019 20:40:39 -0700 Subject: [PATCH 073/499] Add "os" and "device" attributes to user --- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 44 +++++++++---------- .../UserMetadata/UserMetadata.android.cs | 17 +++++++ .../UserMetadata/UserMetadata.ios.cs | 29 ++++++++++++ .../UserMetadata/UserMetadata.netstandard.cs | 16 +++++++ .../UserMetadata/UserMetadata.shared.cs | 21 +++++++++ 5 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs create mode 100644 src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs create mode 100644 src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs create mode 100644 src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index 9244059c..ca33cde8 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -79,15 +79,7 @@ public sealed class LdClient : ILdMobileClient flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); platformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); - // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) - { - User = UserWithUniqueKey(user); - } - else - { - User = user; - } + User = DecorateUser(user); flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); connectionManager = Factory.CreateConnectionManager(configuration); @@ -492,16 +484,12 @@ public async Task IdentifyAsync(User user) throw new ArgumentNullException("user"); } - User userWithKey = user; - if (String.IsNullOrEmpty(user.Key)) - { - userWithKey = UserWithUniqueKey(user); - } + User newUser = DecorateUser(user); await connectionLock.WaitAsync(); try { - User = userWithKey; + User = newUser; await RestartUpdateProcessorAsync(Config.PollingInterval); } finally @@ -509,7 +497,7 @@ public async Task IdentifyAsync(User user) connectionLock.Release(); } - eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(userWithKey)); + eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(newUser)); } async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) @@ -533,14 +521,24 @@ void ClearUpdateProcessor() } } - User UserWithUniqueKey(User user) - { - string uniqueId = deviceInfo.UniqueDeviceId(); - return new User(user) + User DecorateUser(User user) + { + var newUser = new User(user); + if (UserMetadata.DeviceName != null) + { + newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); + } + if (UserMetadata.OSName != null) + { + newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); + } + // If you pass in a user with a null or blank key, one will be assigned to them. + if (String.IsNullOrEmpty(user.Key)) { - Key = uniqueId, - Anonymous = true - }; + newUser.Key = deviceInfo.UniqueDeviceId(); + newUser.Anonymous = true; + } + return newUser; } void IDisposable.Dispose() diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs new file mode 100644 index 00000000..e27ac884 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs @@ -0,0 +1,17 @@ +using Android.OS; + +namespace LaunchDarkly.Xamarin +{ + internal static partial class UserMetadata + { + private static string GetDevice() + { + return Build.Model + " " + Build.Product; + } + + private static string GetOS() + { + return "Android " + Build.VERSION.SdkInt; + } + } +} diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs new file mode 100644 index 00000000..da7f53e3 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs @@ -0,0 +1,29 @@ +using UIKit; + +namespace LaunchDarkly.Xamarin +{ + internal static partial class UserMetadata + { + private static string GetDevice() + { + switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + { + case UIUserInterfaceIdiom.CarPlay: + return "CarPlay"; + case UIUserInterfaceIdiom.Pad: + return "iPad"; + case UIUserInterfaceIdiom.Phone: + return "iPhone"; + case UIUserInterfaceIdiom.TV: + return "Apple TV"; + default: + return "unknown"; + } + } + + private static string GetOS() + { + return "iOS " + UIDevice.CurrentDevice.SystemVersion; + } + } +} diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs new file mode 100644 index 00000000..9ef808d8 --- /dev/null +++ b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs @@ -0,0 +1,16 @@ + +namespace LaunchDarkly.Xamarin +{ + internal static partial class UserMetadata + { + private static string GetDevice() + { + return null; + } + + private static string GetOS() + { + return null; + } + } +} diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs new file mode 100644 index 00000000..ba4e7d4e --- /dev/null +++ b/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs @@ -0,0 +1,21 @@ + +namespace LaunchDarkly.Xamarin +{ + internal static partial class UserMetadata + { + private static readonly string _os = GetOS(); + private static readonly string _device = GetDevice(); + + /// + /// Returns the string that should be passed in the "device" property for all users. + /// + /// The value for "device", or null if none. + internal static string DeviceName => _device; + + /// + /// Returns the string that should be passed in the "os" property for all users. + /// + /// The value for "os", or null if none. + internal static string OSName => _os; + } +} From 59191cd46fca30dbf9a81f9c19260174a9ea28ee Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 27 Mar 2019 16:45:28 -0700 Subject: [PATCH 074/499] create package during build + update dependency versions --- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 4 ++-- LaunchDarkly.Xamarin.Android.Tests/packages.config | 4 ++-- .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 4 ++-- LaunchDarkly.Xamarin.iOS.Tests/packages.config | 4 ++-- src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj | 3 ++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index 419ce6c7..9f01b205 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -62,10 +62,10 @@ ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll - ..\packages\LaunchDarkly.EventSource.3.2.3\lib\netstandard1.4\LaunchDarkly.EventSource.dll + ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - ..\packages\LaunchDarkly.Common.1.2.3\lib\netstandard2.0\LaunchDarkly.Common.dll + ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll ..\packages\Plugin.CurrentActivity.2.1.0.4\lib\monoandroid44\Plugin.CurrentActivity.dll diff --git a/LaunchDarkly.Xamarin.Android.Tests/packages.config b/LaunchDarkly.Xamarin.Android.Tests/packages.config index dab9f17d..ca0397ce 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/packages.config +++ b/LaunchDarkly.Xamarin.Android.Tests/packages.config @@ -2,8 +2,8 @@ - - + + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj index 5c0ed07e..3eb9908b 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -102,10 +102,10 @@ ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll - ..\packages\LaunchDarkly.EventSource.3.2.3\lib\netstandard1.4\LaunchDarkly.EventSource.dll + ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - ..\packages\LaunchDarkly.Common.1.2.3\lib\netstandard2.0\LaunchDarkly.Common.dll + ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll diff --git a/LaunchDarkly.Xamarin.iOS.Tests/packages.config b/LaunchDarkly.Xamarin.iOS.Tests/packages.config index 6341915f..db23f9b3 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/packages.config +++ b/LaunchDarkly.Xamarin.iOS.Tests/packages.config @@ -2,8 +2,8 @@ - - + + diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 8e71ef7e..3c3dcabd 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -12,6 +12,7 @@ bin\$(Configuration)\$(Framework) true latest + True @@ -23,7 +24,7 @@ - + From b1274fa728ccd436747f302748576eb1c26982ce Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 27 Mar 2019 16:51:16 -0700 Subject: [PATCH 075/499] fix dependency version --- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 4db6963b..1cde4188 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + From 94a39afc7caaea23201815feb0a302fdb943f160 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 28 Mar 2019 16:36:44 -0700 Subject: [PATCH 076/499] fix unstable test state --- tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs | 41 +++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs index abdd3d4e..ae3cbe2a 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs @@ -25,13 +25,16 @@ public static LdClient CreateClient(Configuration config, User user) } } - public static void ClearClient() - { - if (LdClient.Instance != null) - { - (LdClient.Instance as IDisposable).Dispose(); - LdClient.Instance = null; - } + public static void ClearClient() + { + lock (ClientInstanceLock) + { + if (LdClient.Instance != null) + { + (LdClient.Instance as IDisposable).Dispose(); + LdClient.Instance = null; + } + } } public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) @@ -41,9 +44,9 @@ public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? { fo["variation"] = new JValue(variation.Value); } - if (reason != null) - { - fo["reason"] = JToken.FromObject(reason); + if (reason != null) + { + fo["reason"] = JToken.FromObject(reason); } JObject o = new JObject { { flagKey, fo } }; return JsonConvert.SerializeObject(o); @@ -62,15 +65,15 @@ public static Configuration ConfigWithFlagsJson(User user, string appKey, string { stubbedFlagCache.CacheFlagsForUser(flags, user); } - - Configuration configuration = Configuration.Default(appKey) - .WithFlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) - .WithConnectionManager(new MockConnectionManager(true)) - .WithEventProcessor(new MockEventProcessor()) - .WithUpdateProcessor(new MockPollingProcessor()) - .WithPersister(new MockPersister()) - .WithDeviceInfo(new MockDeviceInfo("")) - .WithFeatureFlagListenerManager(new FeatureFlagListenerManager()); + + Configuration configuration = Configuration.Default(appKey) + .WithFlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) + .WithConnectionManager(new MockConnectionManager(true)) + .WithEventProcessor(new MockEventProcessor()) + .WithUpdateProcessor(new MockPollingProcessor()) + .WithPersister(new MockPersister()) + .WithDeviceInfo(new MockDeviceInfo("")) + .WithFeatureFlagListenerManager(new FeatureFlagListenerManager()); return configuration; } } From 8c03e880c43aa8d166b90f2abdfa9b9bc23a533e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 18 Apr 2019 11:12:54 -0700 Subject: [PATCH 077/499] support metric value in Track --- .../LaunchDarkly.Xamarin.Android.Tests.csproj | 2 +- .../packages.config | 2 +- .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 6 +-- .../packages.config | 2 +- .../ILdMobileClient.shared.cs | 18 ++++++-- .../LaunchDarkly.Xamarin.csproj | 2 +- src/LaunchDarkly.Xamarin/LdClient.shared.cs | 12 ++++-- .../LaunchDarkly.Xamarin.Tests.csproj | 2 +- .../LdClientEventTests.cs | 41 ++++++++++++++++++- 9 files changed, 71 insertions(+), 16 deletions(-) diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index 9f01b205..00e55dc4 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -65,7 +65,7 @@ ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll + ..\packages\LaunchDarkly.Common.2.1.0\lib\netstandard2.0\LaunchDarkly.Common.dll ..\packages\Plugin.CurrentActivity.2.1.0.4\lib\monoandroid44\Plugin.CurrentActivity.dll diff --git a/LaunchDarkly.Xamarin.Android.Tests/packages.config b/LaunchDarkly.Xamarin.Android.Tests/packages.config index ca0397ce..813410c8 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/packages.config +++ b/LaunchDarkly.Xamarin.Android.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj index 3eb9908b..d3861060 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -81,6 +81,9 @@ + + ..\packages\LaunchDarkly.Common.2.1.0\lib\netstandard2.0\LaunchDarkly.Common.dll + ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\xamarin.ios10\LaunchDarkly.Xamarin.dll @@ -104,9 +107,6 @@ ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - - ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll - ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll diff --git a/LaunchDarkly.Xamarin.iOS.Tests/packages.config b/LaunchDarkly.Xamarin.iOS.Tests/packages.config index db23f9b3..1d6a6560 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/packages.config +++ b/LaunchDarkly.Xamarin.iOS.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs b/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs index 9f13e1f1..1815da98 100644 --- a/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs @@ -109,6 +109,12 @@ public interface ILdMobileClient : ILdCommonClient /// an EvaluationDetail object EvaluationDetail JsonVariationDetail(string key, JToken defaultValue); + /// + /// Tracks that current user performed an event for the given event name. + /// + /// the name of the event + void Track(string eventName); + /// /// Tracks that current user performed an event for the given JToken value and given event name. /// @@ -117,10 +123,14 @@ public interface ILdMobileClient : ILdCommonClient void Track(string eventName, JToken data); /// - /// Tracks that current user performed an event for the given event name. + /// Tracks that current user performed an event for the given event name, and associates it with a + /// numeric metric value. /// /// the name of the event - void Track(string eventName); + /// a JSON string containing additional data associated with the event + /// This value is used by the LaunchDarkly experimentation feature in + /// numeric custom metrics, and will also be returned as part of the custom event for Data Export. + void Track(string eventName, JToken data, double metricValue); /// /// Gets or sets the online status of the client. @@ -130,7 +140,7 @@ public interface ILdMobileClient : ILdCommonClient /// and then use await or call Wait() on /// its return value. /// - /// true if online; otherwise, false. + /// true if online; otherwise, false. bool Online { get; set; } /// @@ -146,7 +156,7 @@ public interface ILdMobileClient : ILdCommonClient /// null entry in the map. If the client is offline or has not been initialized, a null map will be returned. /// This method will not send analytics events back to LaunchDarkly. /// - /// a map from feature flag keys to {@code JToken} for the current user + /// a map from feature flag keys to {@code JToken} for the current user IDictionary AllFlags(); /// diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj index 4f3a28d8..f97dc3f2 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.Xamarin/LdClient.shared.cs index ca33cde8..5a111b78 100644 --- a/src/LaunchDarkly.Xamarin/LdClient.shared.cs +++ b/src/LaunchDarkly.Xamarin/LdClient.shared.cs @@ -430,16 +430,22 @@ public IDictionary AllFlags() .ToDictionary(p => p.Key, p => p.Value.value); } + /// + public void Track(string eventName) + { + Track(eventName, null); + } + /// public void Track(string eventName, JToken data) { eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data)); } - /// - public void Track(string eventName) + /// + public void Track(string eventName, JToken data, double metricValue) { - Track(eventName, null); + eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data, metricValue)); } /// diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj index 1cde4188..0131ee45 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs index 89973126..27d9afbe 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs @@ -41,18 +41,57 @@ public void IdentifySendsIdentifyEvent() [Fact] public void TrackSendsCustomEvent() + { + using (LdClient client = MakeClient(user, "{}")) + { + client.Track("eventkey"); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + CustomEvent ce = Assert.IsType(e); + Assert.Equal("eventkey", ce.Key); + Assert.Equal(user.Key, ce.User.Key); + Assert.Null(ce.JsonData); + Assert.Null(ce.MetricValue); + }); + } + } + + [Fact] + public void TrackWithDataSendsCustomEvent() { using (LdClient client = MakeClient(user, "{}")) { JToken data = new JValue("hi"); client.Track("eventkey", data); - Assert.Collection(eventProcessor.Events, + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + CustomEvent ce = Assert.IsType(e); + Assert.Equal("eventkey", ce.Key); + Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(data, ce.JsonData); + Assert.Null(ce.MetricValue); + }); + } + } + + [Fact] + public void TrackWithMetricValueSendsCustomEvent() + { + using (LdClient client = MakeClient(user, "{}")) + { + JToken data = new JValue("hi"); + double metricValue = 1.5; + client.Track("eventkey", data, metricValue); + Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { CustomEvent ce = Assert.IsType(e); Assert.Equal("eventkey", ce.Key); Assert.Equal(user.Key, ce.User.Key); Assert.Equal(data, ce.JsonData); + Assert.Equal(metricValue, ce.MetricValue); }); } } From a324295888c572183e1e92a67678c20fbfae7d67 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Thu, 2 May 2019 17:19:36 -0700 Subject: [PATCH 078/499] apply markdown templates (#33) --- CHANGELOG.md | 21 ++++++ CONTRIBUTING.md | 43 ++++++++++++ .../LaunchDarkly.Xamarin.Android.Tests.csproj | 2 +- .../LaunchDarkly.Xamarin.iOS.Tests.csproj | 2 +- README.md | 68 ++++++------------- 5 files changed, 85 insertions(+), 51 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2dfdcd6d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Change log + +All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org). + +# Note on future releases + +The LaunchDarkly SDK repositories are being renamed for consistency. This repository is now `xamarin-client-sdk` rather than `xamarin-client`. + +The package name will also change. In the 1.0.0-beta16 release, the published package was `LaunchDarkly.Xamarin`; in all future releases, it will be `LaunchDarkly.XamarinSdk`. + +## [1.0.0-beta16] - 2019-04-05 +### Added: +- In Android and iOS, when an app is in the background, the SDK should turn off the streaming connection and instead poll for flag updates at an interval determined by `Configuration.BackgroundPollingInterval` (default: 60 minutes). +- The SDK now supports [evaluation reasons](https://docs.launchdarkly.com/docs/evaluation-reasons). See `Configuration.WithEvaluationReasons` and `ILdMobileClient.BoolVariationDetail`. +- The SDK now sends custom attributes called `os` and `device` as part of the user data, indicating the user's platform and OS version. This is the same as what the native Android and iOS SDKs do, except that "iOS" or "Android" is also prepended to the `os` property. +### Changed: +- This is the first version that is built specifically for iOS and Android platforms. There is also still a .NET Standard 1.0 build in the same package. +- The SDK no longer uses Xamarin Essentials. +### Fixed: +- Under some circumstances, a `CancellationTokenSource` object could be leaked. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..39bd2b70 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +Contributing to the LaunchDarkly Client-side SDK for Xamarin +================================================ + +LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK. + +Submitting bug reports and feature requests +------------------ + +The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/xamarin-client-sdk/issues) in the SDK repository. Bug reports and feature requests specific to this SDK should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days. + +Submitting pull requests +------------------ + +We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days. + +Build instructions +------------------ + +### Prerequisites + +This SDK is built against .Net Standard 1.6 and 2.0 with the `microsoft/dotnet` Docker image. See the SDK's [CI configuration](.circleci/config.yml) to determine which image version in used by LaunchDarkly. + +To set up the project and dependencies, run the following command in the root SDK directory: + +``` +dotnet restore +``` + +### Building + +To build the SDK without running any tests: + +``` +msbuild +``` + +### Testing + +To build the SDK and run all unit tests: +``` +dotnet build src/LaunchDarkly.Xamarin -f netstandard2.0 +dotnet test tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj -f netcoreapp2.0 +``` diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj index 9f01b205..93a81662 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj @@ -42,7 +42,7 @@ - ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\monoandroid81\LaunchDarkly.Xamarin.dll + ..\..\xamarin-client-sdk-private\src\LaunchDarkly.Xamarin\bin\Debug\monoandroid81\LaunchDarkly.Xamarin.dll diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj index 3eb9908b..7f486cdf 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj @@ -82,7 +82,7 @@ - ..\..\xamarin-client-private\src\LaunchDarkly.Xamarin\bin\Debug\xamarin.ios10\LaunchDarkly.Xamarin.dll + ..\..\xamarin-client-sdk-private\src\LaunchDarkly.Xamarin\bin\Debug\xamarin.ios10\LaunchDarkly.Xamarin.dll diff --git a/README.md b/README.md index ef2492cc..f3ebe7ff 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,30 @@ -LaunchDarkly SDK [BETA] for Xamarin +LaunchDarkly Client-side SDK for Xamarin =========================== -[![CircleCI](https://circleci.com/gh/launchdarkly/xamarin-client/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/xamarin-client/tree/master) -*This software is a **beta** version and should not be considered ready for production use until tagged at least 1.0.* +[![CircleCI](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master) + +*This version of the SDK is a **beta** version and should not be considered ready for production use while this message is visible.* + +LaunchDarkly overview +------------------------- +[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/docs/getting-started) using LaunchDarkly today! + +[![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly) Supported platforms ------------------- This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 1.6, 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. -Quick setup +Getting started ----------- -1. Use [NuGet](http://docs.nuget.org/docs/start-here/using-the-package-manager-console) to add the Xamarin SDK to your project: - - Install-Package LaunchDarkly.Xamarin - -2. Import the LaunchDarkly packages: - - using LaunchDarkly.Client; - using LaunchDarkly.Xamarin; - -3. Initialize the LDClient with your Mobile key and user: - - User user = User.WithKey(username); - LdClient ldClient = LdClient.Init("YOUR_MOBILE_KEY", user); - -Your first feature flag ------------------------ - -1. Create a new feature flag on your [dashboard](https://app.launchdarkly.com). -2. In your application code, use the feature's key to check whether the flag is on for each user: - - bool showFeature = ldClient.BoolVariation("your.feature.key"); - if (showFeature) { - // application code to show the feature - } - else { - // the code to run if the feature is off - } +Refer to the [SDK documentation](https://docs.launchdarkly.com/docs/xamarin-sdk-reference#section-getting-started) for instructions on getting started with using the SDK. Learn more ----------- -Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/v2.0/docs/xamarin-sdk-reference). +Check out our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/docs/xamarin-sdk-reference). Testing ------- @@ -53,7 +34,7 @@ We run integration tests for all our SDKs using a centralized test harness. This Contributing ------------ -See [Contributing](https://github.com/launchdarkly/xamarin-client/blob/master/CONTRIBUTING.md). +We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. About LaunchDarkly ----------- @@ -63,21 +44,10 @@ About LaunchDarkly * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file. * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline. -* LaunchDarkly provides feature flag SDKs for - * [Java](http://docs.launchdarkly.com/docs/java-sdk-reference "Java SDK") - * [JavaScript](http://docs.launchdarkly.com/docs/js-sdk-reference "LaunchDarkly JavaScript SDK") - * [PHP](http://docs.launchdarkly.com/docs/php-sdk-reference "LaunchDarkly PHP SDK") - * [Python](http://docs.launchdarkly.com/docs/python-sdk-reference "LaunchDarkly Python SDK") - * [Go](http://docs.launchdarkly.com/docs/go-sdk-reference "LaunchDarkly Go SDK") - * [Node.JS](http://docs.launchdarkly.com/docs/node-sdk-reference "LaunchDarkly Node SDK") - * [.NET](http://docs.launchdarkly.com/docs/dotnet-sdk-reference "LaunchDarkly .Net SDK") - * [Xamarin](http://docs.launchdarkly.com/docs/xamarin-sdk-reference "LaunchDarkly Xamarin SDK") - * [Ruby](http://docs.launchdarkly.com/docs/ruby-sdk-reference "LaunchDarkly Ruby SDK") - * [iOS](http://docs.launchdarkly.com/docs/ios-sdk-reference "LaunchDarkly iOS SDK") - * [Android](http://docs.launchdarkly.com/docs/android-sdk-reference "LaunchDarkly Android SDK") +* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/docs) for a complete list. * Explore LaunchDarkly - * [launchdarkly.com](http://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information - * [docs.launchdarkly.com](http://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDKs - * [apidocs.launchdarkly.com](http://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation - * [blog.launchdarkly.com](http://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates + * [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information + * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides + * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation + * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies From bcffc7e8f17ad74f6c2e1e6cce2e7decce7b3b56 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 14 May 2019 16:05:25 -0700 Subject: [PATCH 079/499] use new package naming convention --- .circleci/config.yml | 4 ++-- CHANGELOG.md | 4 ++++ CONTRIBUTING.md | 4 ++-- .../LDAndroidTests.cs | 0 .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 8 ++++---- .../MainActivity.cs | 0 .../Properties/AndroidManifest.xml | 0 .../Properties/AssemblyInfo.cs | 0 .../Resources/AboutResources.txt | 0 .../Resources/Resource.designer.cs | 0 .../Resources/mipmap-hdpi/Icon.png | Bin .../Resources/mipmap-mdpi/Icon.png | Bin .../Resources/mipmap-xhdpi/Icon.png | Bin .../Resources/mipmap-xxhdpi/Icon.png | Bin .../Resources/mipmap-xxxhdpi/Icon.png | Bin .../designtime/build.props | 0 .../packages.config | 2 +- .../Entitlements.plist | 0 .../Info.plist | 0 .../LDiOSTests.cs | 0 .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 10 +++++----- .../LaunchScreen.storyboard | 0 .../Main.cs | 0 .../UnitTestAppDelegate.cs | 0 .../packages.config | 2 +- ...arkly.Xamarin.sln => LaunchDarkly.XamarinSdk.sln | 8 ++++---- .../Properties/AssemblyInfo.shared.cs | 6 ------ .../BackgroundAdapter/BackgroundAdapter.android.cs | 0 .../BackgroundAdapter/BackgroundAdapter.ios.cs | 0 .../BackgroundAdapter.netstandard.cs | 0 .../ClientRunMode.shared.cs | 0 .../Configuration.shared.cs | 0 .../Connectivity/Connectivity.android.cs | 0 .../Connectivity/Connectivity.ios.cs | 0 .../Connectivity/Connectivity.ios.reachability.cs | 0 .../Connectivity/Connectivity.netstandard.cs | 0 .../Connectivity/Connectivity.shared.cs | 0 .../Connectivity/Connectivity.shared.enums.cs | 0 .../Constants.shared.cs | 0 .../DeviceInfo.shared.cs | 0 .../Extensions.shared.cs | 0 .../Factory.shared.cs | 0 .../FeatureFlag.shared.cs | 0 .../FeatureFlagListenerManager.shared.cs | 0 .../FeatureFlagRequestor.shared.cs | 0 .../FlagCacheManager.shared.cs | 0 .../IBackgroundingState.shared.cs | 0 .../IConnectionManager.shared.cs | 0 .../IDeviceInfo.shared.cs | 0 .../IFeatureFlagListener.shared.cs | 0 .../IFeatureFlagListenerManager.shared.cs | 0 .../IFlagCacheManager.shared.cs | 0 .../ILdMobileClient.shared.cs | 0 .../IMobileConfiguration.shared.cs | 0 .../IMobileUpdateProcessor.shared.cs | 0 .../IPlatformAdapter.shared.cs | 0 .../ISimplePersistance.shared.cs | 0 .../IUserFlagCache.shared.cs | 0 .../LaunchDarkly.XamarinSdk.csproj} | 8 ++++---- .../LdClient.shared.cs | 0 .../MainThread/MainThread.android.cs | 0 .../MainThread/MainThread.ios.cs | 0 .../MainThread/MainThread.netstandard.cs | 0 .../MainThread/MainThread.shared.cs | 0 .../MobileConnectionManager.shared.cs | 0 .../MobilePollingProcessor.shared.cs | 0 .../MobileSideClientEnvironment.shared.cs | 0 .../MobileStreamingProcessor.shared.cs | 0 .../Permissions/Permissions.android.cs | 0 .../Permissions/Permissions.ios.cs | 0 .../Permissions/Permissions.netstandard.cs | 0 .../Permissions/Permissions.shared.cs | 0 .../Permissions/Permissions.shared.enums.cs | 0 .../Platform/Platform.android.cs | 0 .../Platform/Platform.ios.cs | 0 .../Platform/Platform.shared.cs | 0 .../Preferences/Preferences.android.cs | 0 .../Preferences/Preferences.ios.cs | 0 .../Preferences/Preferences.netstandard.cs | 0 .../Preferences/Preferences.shared.cs | 0 .../Properties/AssemblyInfo.shared.cs | 6 ++++++ .../SimpleInMemoryPersistance.shared.cs | 0 .../SimpleMobileDevicePersistance.shared.cs | 0 .../UserFlagDeviceCache.shared.cs | 0 .../UserFlagInMemoryCache.shared.cs | 0 .../UserMetadata/UserMetadata.android.cs | 0 .../UserMetadata/UserMetadata.ios.cs | 0 .../UserMetadata/UserMetadata.netstandard.cs | 0 .../UserMetadata/UserMetadata.shared.cs | 0 .../ValueType.shared.cs | 0 .../ConfigurationTest.cs | 0 .../FeatureFlagListenerTests.cs | 0 .../FeatureFlagTests.cs | 0 .../FlagCacheManagerTests.cs | 0 .../LaunchDarkly.XamarinSdk.Tests.csproj} | 5 +++-- .../LdClientEvaluationTests.cs | 0 .../LdClientEventTests.cs | 0 .../LdClientTests.cs | 0 .../MobilePollingProcessorTests.cs | 0 .../MobileStreamingProcessorTests.cs | 0 .../MockComponents.cs | 0 .../TestUtil.cs | 0 .../UserFlagCacheTests.cs | 0 .../xunit-to-junit.xslt | 0 104 files changed, 36 insertions(+), 31 deletions(-) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/LDAndroidTests.cs (100%) rename LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj => LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj (97%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/MainActivity.cs (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Properties/AndroidManifest.xml (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Properties/AssemblyInfo.cs (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/AboutResources.txt (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/Resource.designer.cs (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/mipmap-hdpi/Icon.png (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/mipmap-mdpi/Icon.png (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/mipmap-xhdpi/Icon.png (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/mipmap-xxhdpi/Icon.png (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/Resources/mipmap-xxxhdpi/Icon.png (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/designtime/build.props (100%) rename {LaunchDarkly.Xamarin.Android.Tests => LaunchDarkly.XamarinSdk.Android.Tests}/packages.config (98%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/Entitlements.plist (100%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/Info.plist (100%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/LDiOSTests.cs (100%) rename LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj => LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj (91%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/LaunchScreen.storyboard (100%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/Main.cs (100%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/UnitTestAppDelegate.cs (100%) rename {LaunchDarkly.Xamarin.iOS.Tests => LaunchDarkly.XamarinSdk.iOS.Tests}/packages.config (98%) rename LaunchDarkly.Xamarin.sln => LaunchDarkly.XamarinSdk.sln (89%) delete mode 100644 src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/BackgroundAdapter/BackgroundAdapter.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/BackgroundAdapter/BackgroundAdapter.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/BackgroundAdapter/BackgroundAdapter.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/ClientRunMode.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Configuration.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.ios.reachability.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Connectivity/Connectivity.shared.enums.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Constants.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/DeviceInfo.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Extensions.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Factory.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/FeatureFlag.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/FeatureFlagListenerManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/FeatureFlagRequestor.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/FlagCacheManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IBackgroundingState.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IConnectionManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IDeviceInfo.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IFeatureFlagListener.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IFeatureFlagListenerManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IFlagCacheManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/ILdMobileClient.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IMobileConfiguration.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IMobileUpdateProcessor.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IPlatformAdapter.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/ISimplePersistance.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/IUserFlagCache.shared.cs (100%) rename src/{LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj => LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj} (91%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/LdClient.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MainThread/MainThread.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MainThread/MainThread.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MainThread/MainThread.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MainThread/MainThread.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MobileConnectionManager.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MobilePollingProcessor.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MobileSideClientEnvironment.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/MobileStreamingProcessor.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Permissions/Permissions.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Permissions/Permissions.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Permissions/Permissions.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Permissions/Permissions.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Permissions/Permissions.shared.enums.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Platform/Platform.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Platform/Platform.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Platform/Platform.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Preferences/Preferences.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Preferences/Preferences.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Preferences/Preferences.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/Preferences/Preferences.shared.cs (100%) create mode 100644 src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/SimpleInMemoryPersistance.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/SimpleMobileDevicePersistance.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserFlagDeviceCache.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserFlagInMemoryCache.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserMetadata/UserMetadata.android.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserMetadata/UserMetadata.ios.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserMetadata/UserMetadata.netstandard.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/UserMetadata/UserMetadata.shared.cs (100%) rename src/{LaunchDarkly.Xamarin => LaunchDarkly.XamarinSdk}/ValueType.shared.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/ConfigurationTest.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/FeatureFlagListenerTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/FeatureFlagTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/FlagCacheManagerTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj => LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj} (78%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/LdClientEvaluationTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/LdClientEventTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/LdClientTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/MobilePollingProcessorTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/MobileStreamingProcessorTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/MockComponents.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/TestUtil.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/UserFlagCacheTests.cs (100%) rename tests/{LaunchDarkly.Xamarin.Tests => LaunchDarkly.XamarinSdk.Tests}/xunit-to-junit.xslt (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 93ae6397..da05f2db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,5 +11,5 @@ jobs: steps: - checkout - run: dotnet restore - - run: dotnet build src/LaunchDarkly.Xamarin -f netstandard2.0 - - run: dotnet test tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj -f netcoreapp2.0 + - run: dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 + - run: dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dfdcd6d..0b78f488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ The LaunchDarkly SDK repositories are being renamed for consistency. This reposi The package name will also change. In the 1.0.0-beta16 release, the published package was `LaunchDarkly.Xamarin`; in all future releases, it will be `LaunchDarkly.XamarinSdk`. +## [1.0.0-beta17] - 2019-05-15 +### Changed: +- The NuGet package name for this SDK is now `LaunchDarkly.XamarinSdk`. There are no other changes. Substituting `LaunchDarkly.Xamarin` 1.0.0-beta16 with `LaunchDarkly.XamarinSdk` 1.0.0-beta17 should not affect functionality. + ## [1.0.0-beta16] - 2019-04-05 ### Added: - In Android and iOS, when an app is in the background, the SDK should turn off the streaming connection and instead poll for flag updates at an interval determined by `Configuration.BackgroundPollingInterval` (default: 60 minutes). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39bd2b70..9f8918e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,6 @@ msbuild To build the SDK and run all unit tests: ``` -dotnet build src/LaunchDarkly.Xamarin -f netstandard2.0 -dotnet test tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj -f netcoreapp2.0 +dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 +dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 ``` diff --git a/LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/LDAndroidTests.cs rename to LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs diff --git a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj b/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj similarity index 97% rename from LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj rename to LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 93a81662..4874f79b 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/LaunchDarkly.Xamarin.Android.Tests.csproj +++ b/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -7,7 +7,7 @@ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Library LaunchDarkly.Xamarin.Android.Tests - LaunchDarkly.Xamarin.Android.Tests + LaunchDarkly.XamarinSdk.Android.Tests v9.0 MonoAndroid90 True @@ -42,7 +42,7 @@ - ..\..\xamarin-client-sdk-private\src\LaunchDarkly.Xamarin\bin\Debug\monoandroid81\LaunchDarkly.Xamarin.dll + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\monoandroid81\LaunchDarkly.XamarinSdk.dll @@ -64,8 +64,8 @@ ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - - ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll + + ..\packages\LaunchDarkly.CommonSdk.2.1.2\lib\netstandard2.0\LaunchDarkly.CommonSdk.dll ..\packages\Plugin.CurrentActivity.2.1.0.4\lib\monoandroid44\Plugin.CurrentActivity.dll diff --git a/LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs b/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/MainActivity.cs rename to LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs diff --git a/LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml b/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Properties/AndroidManifest.xml rename to LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml diff --git a/LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs b/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Properties/AssemblyInfo.cs rename to LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/AboutResources.txt rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/Resource.designer.cs rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-hdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/Icon.png similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-hdpi/Icon.png rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/Icon.png diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-mdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/Icon.png similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-mdpi/Icon.png rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/Icon.png diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xhdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/Icon.png similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xhdpi/Icon.png rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/Icon.png diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxhdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/Icon.png similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxhdpi/Icon.png rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/Icon.png diff --git a/LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png rename to LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png diff --git a/LaunchDarkly.Xamarin.Android.Tests/designtime/build.props b/LaunchDarkly.XamarinSdk.Android.Tests/designtime/build.props similarity index 100% rename from LaunchDarkly.Xamarin.Android.Tests/designtime/build.props rename to LaunchDarkly.XamarinSdk.Android.Tests/designtime/build.props diff --git a/LaunchDarkly.Xamarin.Android.Tests/packages.config b/LaunchDarkly.XamarinSdk.Android.Tests/packages.config similarity index 98% rename from LaunchDarkly.Xamarin.Android.Tests/packages.config rename to LaunchDarkly.XamarinSdk.Android.Tests/packages.config index ca0397ce..6619a7d0 100644 --- a/LaunchDarkly.Xamarin.Android.Tests/packages.config +++ b/LaunchDarkly.XamarinSdk.Android.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist b/LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/Entitlements.plist rename to LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Info.plist b/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/Info.plist rename to LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/LDiOSTests.cs rename to LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj similarity index 91% rename from LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj rename to LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 7f486cdf..9260bad4 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/LaunchDarkly.Xamarin.iOS.Tests.csproj +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -7,7 +7,7 @@ {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Exe LaunchDarkly.Xamarin.iOS.Tests - LaunchDarkly.Xamarin.iOS.Tests + LaunchDarkly.XamarinSdk.iOS.Tests Resources @@ -81,8 +81,8 @@ - - ..\..\xamarin-client-sdk-private\src\LaunchDarkly.Xamarin\bin\Debug\xamarin.ios10\LaunchDarkly.Xamarin.dll + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\xamarin.ios10\LaunchDarkly.XamarinSdk.dll @@ -104,8 +104,8 @@ ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - - ..\packages\LaunchDarkly.Common.2.0.0\lib\netstandard2.0\LaunchDarkly.Common.dll + + ..\packages\LaunchDarkly.CommonSdk.2.1.2\lib\netstandard2.0\LaunchDarkly.CommonSdk.dll ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll diff --git a/LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/LaunchScreen.storyboard rename to LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard diff --git a/LaunchDarkly.Xamarin.iOS.Tests/Main.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/Main.cs rename to LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs diff --git a/LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs similarity index 100% rename from LaunchDarkly.Xamarin.iOS.Tests/UnitTestAppDelegate.cs rename to LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs diff --git a/LaunchDarkly.Xamarin.iOS.Tests/packages.config b/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config similarity index 98% rename from LaunchDarkly.Xamarin.iOS.Tests/packages.config rename to LaunchDarkly.XamarinSdk.iOS.Tests/packages.config index db23f9b3..3a5708a9 100644 --- a/LaunchDarkly.Xamarin.iOS.Tests/packages.config +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config @@ -2,7 +2,7 @@ - + diff --git a/LaunchDarkly.Xamarin.sln b/LaunchDarkly.XamarinSdk.sln similarity index 89% rename from LaunchDarkly.Xamarin.sln rename to LaunchDarkly.XamarinSdk.sln index 82f88ff0..348c10fb 100644 --- a/LaunchDarkly.Xamarin.sln +++ b/LaunchDarkly.XamarinSdk.sln @@ -2,13 +2,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin", "src\LaunchDarkly.Xamarin\LaunchDarkly.Xamarin.csproj", "{7717A2B2-9905-40A7-989F-790139D69543}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk", "src\LaunchDarkly.XamarinSdk\LaunchDarkly.XamarinSdk.csproj", "{7717A2B2-9905-40A7-989F-790139D69543}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.Tests", "tests\LaunchDarkly.Xamarin.Tests\LaunchDarkly.Xamarin.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Tests", "tests\LaunchDarkly.XamarinSdk.Tests\LaunchDarkly.XamarinSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.iOS.Tests", "LaunchDarkly.Xamarin.iOS.Tests\LaunchDarkly.Xamarin.iOS.Tests.csproj", "{066AA0F9-449A-48F5-9492-D698F0EFD923}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.iOS.Tests", "LaunchDarkly.XamarinSdk.iOS.Tests\LaunchDarkly.XamarinSdk.iOS.Tests.csproj", "{066AA0F9-449A-48F5-9492-D698F0EFD923}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.Xamarin.Android.Tests", "LaunchDarkly.Xamarin.Android.Tests\LaunchDarkly.Xamarin.Android.Tests.csproj", "{0B18C336-C770-42C1-B77A-E4A49F789677}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Android.Tests", "LaunchDarkly.XamarinSdk.Android.Tests\LaunchDarkly.XamarinSdk.Android.Tests.csproj", "{0B18C336-C770-42C1-B77A-E4A49F789677}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs b/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs deleted file mode 100644 index 25bb9808..00000000 --- a/src/LaunchDarkly.Xamarin/Properties/AssemblyInfo.shared.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.iOS.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.Xamarin.Android.Tests")] diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.android.cs rename to src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.android.cs diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.ios.cs rename to src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs diff --git a/src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/BackgroundAdapter/BackgroundAdapter.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/ClientRunMode.shared.cs b/src/LaunchDarkly.XamarinSdk/ClientRunMode.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ClientRunMode.shared.cs rename to src/LaunchDarkly.XamarinSdk/ClientRunMode.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Configuration.shared.cs b/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Configuration.shared.cs rename to src/LaunchDarkly.XamarinSdk/Configuration.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.android.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.android.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.reachability.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.ios.reachability.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.reachability.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Connectivity/Connectivity.shared.enums.cs rename to src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs diff --git a/src/LaunchDarkly.Xamarin/Constants.shared.cs b/src/LaunchDarkly.XamarinSdk/Constants.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Constants.shared.cs rename to src/LaunchDarkly.XamarinSdk/Constants.shared.cs diff --git a/src/LaunchDarkly.Xamarin/DeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/DeviceInfo.shared.cs rename to src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Extensions.shared.cs b/src/LaunchDarkly.XamarinSdk/Extensions.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Extensions.shared.cs rename to src/LaunchDarkly.XamarinSdk/Extensions.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Factory.shared.cs b/src/LaunchDarkly.XamarinSdk/Factory.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Factory.shared.cs rename to src/LaunchDarkly.XamarinSdk/Factory.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlag.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlag.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlag.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlagListenerManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FeatureFlagRequestor.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FeatureFlagRequestor.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/FlagCacheManager.shared.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/FlagCacheManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs b/src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IBackgroundingState.shared.cs rename to src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IConnectionManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IConnectionManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IConnectionManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IConnectionManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IDeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IDeviceInfo.shared.cs rename to src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFeatureFlagListener.shared.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFeatureFlagListener.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFeatureFlagListenerManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IFlagCacheManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IFlagCacheManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFlagCacheManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ILdMobileClient.shared.cs rename to src/LaunchDarkly.XamarinSdk/ILdMobileClient.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IMobileConfiguration.shared.cs b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IMobileConfiguration.shared.cs rename to src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IMobileUpdateProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs b/src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IPlatformAdapter.shared.cs rename to src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs diff --git a/src/LaunchDarkly.Xamarin/ISimplePersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ISimplePersistance.shared.cs rename to src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs diff --git a/src/LaunchDarkly.Xamarin/IUserFlagCache.shared.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/IUserFlagCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs diff --git a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj similarity index 91% rename from src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj rename to src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 4f3a28d8..83070170 100644 --- a/src/LaunchDarkly.Xamarin/LaunchDarkly.Xamarin.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -4,10 +4,10 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta16 + 1.0.0-beta17 Library - LaunchDarkly.Xamarin - LaunchDarkly.Xamarin + LaunchDarkly.XamarinSdk + LaunchDarkly.XamarinSdk false bin\$(Configuration)\$(Framework) true @@ -24,7 +24,7 @@ - + diff --git a/src/LaunchDarkly.Xamarin/LdClient.shared.cs b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/LdClient.shared.cs rename to src/LaunchDarkly.XamarinSdk/LdClient.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MainThread/MainThread.android.cs rename to src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MainThread/MainThread.ios.cs rename to src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MainThread/MainThread.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MainThread/MainThread.shared.cs rename to src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobileConnectionManager.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobileConnectionManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobilePollingProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobileSideClientEnvironment.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.shared.cs diff --git a/src/LaunchDarkly.Xamarin/MobileStreamingProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/MobileStreamingProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Permissions/Permissions.android.cs rename to src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Permissions/Permissions.ios.cs rename to src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Permissions/Permissions.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.cs rename to src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.enums.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Permissions/Permissions.shared.enums.cs rename to src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.enums.cs diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.android.cs b/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Platform/Platform.android.cs rename to src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs b/src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Platform/Platform.ios.cs rename to src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs diff --git a/src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs b/src/LaunchDarkly.XamarinSdk/Platform/Platform.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Platform/Platform.shared.cs rename to src/LaunchDarkly.XamarinSdk/Platform/Platform.shared.cs diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Preferences/Preferences.android.cs rename to src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Preferences/Preferences.ios.cs rename to src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Preferences/Preferences.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/Preferences/Preferences.shared.cs rename to src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs new file mode 100644 index 00000000..ea362dc7 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs @@ -0,0 +1,6 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.iOS.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.Android.Tests")] diff --git a/src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/SimpleInMemoryPersistance.shared.cs rename to src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs diff --git a/src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/SimpleMobileDevicePersistance.shared.cs rename to src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs diff --git a/src/LaunchDarkly.Xamarin/UserFlagDeviceCache.shared.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserFlagDeviceCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs diff --git a/src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.shared.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserFlagInMemoryCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.shared.cs diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.android.cs rename to src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.ios.cs rename to src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs diff --git a/src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/UserMetadata/UserMetadata.shared.cs rename to src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs diff --git a/src/LaunchDarkly.Xamarin/ValueType.shared.cs b/src/LaunchDarkly.XamarinSdk/ValueType.shared.cs similarity index 100% rename from src/LaunchDarkly.Xamarin/ValueType.shared.cs rename to src/LaunchDarkly.XamarinSdk/ValueType.shared.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/ConfigurationTest.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagListenerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/FeatureFlagListenerTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/FeatureFlagTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/FlagCacheManagerTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj similarity index 78% rename from tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj rename to tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 1cde4188..1b212463 100644 --- a/tests/LaunchDarkly.Xamarin.Tests/LaunchDarkly.Xamarin.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -3,13 +3,14 @@ netcoreapp2.0 2.0.0 + LaunchDarkly.XamarinSdk.Tests - + @@ -21,6 +22,6 @@ - + diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/LdClientEvaluationTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/LdClientEventTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/LdClientTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/MobilePollingProcessorTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/MobileStreamingProcessorTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/MockComponents.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/TestUtil.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/UserFlagCacheTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs diff --git a/tests/LaunchDarkly.Xamarin.Tests/xunit-to-junit.xslt b/tests/LaunchDarkly.XamarinSdk.Tests/xunit-to-junit.xslt similarity index 100% rename from tests/LaunchDarkly.Xamarin.Tests/xunit-to-junit.xslt rename to tests/LaunchDarkly.XamarinSdk.Tests/xunit-to-junit.xslt From 32437cbb5101b5448c46674cb3a3f5bf52e207a7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 17 May 2019 17:09:44 -0700 Subject: [PATCH 080/499] clean up platform code, don't throw NotImplementedException --- .../BackgroundAdapter.netstandard.cs | 4 +- .../Connectivity/Connectivity.netstandard.cs | 56 ++--- .../Connectivity/Connectivity.shared.cs | 6 +- .../MainThread/MainThread.android.cs | 51 ----- .../MainThread/MainThread.ios.cs | 38 ---- .../MainThread/MainThread.netstandard.cs | 35 --- .../MainThread/MainThread.shared.cs | 100 -------- .../MobileConnectionManager.shared.cs | 17 +- .../Permissions/Permissions.android.cs | 205 +++++++++-------- .../Permissions/Permissions.ios.cs | 124 ---------- .../Permissions/Permissions.netstandard.cs | 39 ---- .../Permissions/Permissions.shared.cs | 46 ---- .../Platform/Platform.android.cs | 214 +++++++++--------- .../Platform/Platform.ios.cs | 104 --------- .../Preferences/Preferences.netstandard.cs | 42 +--- .../SimpleMobileDevicePersistance.shared.cs | 15 +- .../UserMetadata/UserMetadata.android.cs | 12 +- .../UserMetadata/UserMetadata.ios.cs | 33 +-- .../UserMetadata/UserMetadata.netstandard.cs | 12 +- .../UserMetadata/UserMetadata.shared.cs | 8 +- 20 files changed, 287 insertions(+), 874 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs index b7e2038b..047c801a 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs @@ -3,16 +3,16 @@ namespace LaunchDarkly.Xamarin.BackgroundAdapter { + // This is a stub implementation for .NET Standard where there's no such thing as backgrounding. + internal class BackgroundAdapter : IPlatformAdapter { public void Dispose() { - throw new NotImplementedException(); } public void EnableBackgrounding(IBackgroundingState backgroundingState) { - throw new NotImplementedException(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs index 738f7738..d0fcf671 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs @@ -1,42 +1,34 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace LaunchDarkly.Xamarin.Connectivity { + // This code is not from Xamarin Essentials, though it implements the same Connectivity abstraction. + // It is a stub that always reports that we do have network connectivity. + // + // Unfortunately, in .NET Standard that is the best we can do. There is (at least in 2.0) a + // NetworkInterface.GetIsNetworkAvailable() method, but that doesn't test whether we actuually have + // Internet access, just whether we have a network interface (i.e. if we're running a desktop app + // on a laptop, and the wi-fi is turned off, it will still return true as long as the laptop has an + // Ethernet card-- even if it's not plugged in). + // + // So, in order to support connectivity detection on non-mobile platforms, we would need to add more + // platform-specific variants. For instance, here's how Xamarin Essentials does it for UWP: + // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/Connectivity/Connectivity.uwp.cs + internal static partial class Connectivity { - static NetworkAccess PlatformNetworkAccess => - throw new NotImplementedException(); + static NetworkAccess PlatformNetworkAccess => NetworkAccess.Internet; - static IEnumerable PlatformConnectionProfiles => - throw new NotImplementedException(); + static IEnumerable PlatformConnectionProfiles + { + get + { + yield return ConnectionProfile.Unknown; + } + } - static void StartListeners() => - throw new NotImplementedException(); + static void StartListeners() { } - static void StopListeners() => - throw new NotImplementedException(); + static void StopListeners() { } } } diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs index 3282deeb..1889f9e4 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs @@ -82,7 +82,11 @@ static void OnConnectivityChanged(ConnectivityChangedEventArgs e) if (currentAccess != e.NetworkAccess || !currentProfiles.SequenceEqual(e.ConnectionProfiles)) { SetCurrent(); - MainThread.MainThread.BeginInvokeOnMainThread(() => ConnectivityChangedInternal?.Invoke(null, e)); + // Modified: Xamarin Essentials did this to guarantee that event handlers would always fire on the + // main thread, but that is not how event handlers normally work in .NET, and our SDK does not have + // any such requirement. + // MainThread.MainThread.BeginInvokeOnMainThread(() => ConnectivityChangedInternal?.Invoke(null, e)); + ConnectivityChangedInternal?.Invoke(null, e); } } } diff --git a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs deleted file mode 100644 index 1162805d..00000000 --- a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.android.cs +++ /dev/null @@ -1,51 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using Android.OS; - -namespace LaunchDarkly.Xamarin.MainThread -{ - internal static partial class MainThread - { - static Handler handler; - - static bool PlatformIsMainThread - { - get - { - if (Platform.Platform.HasApiLevel(BuildVersionCodes.M)) - return Looper.MainLooper.IsCurrentThread; - - return Looper.MyLooper() == Looper.MainLooper; - } - } - - static void PlatformBeginInvokeOnMainThread(Action action) - { - if (handler?.Looper != Looper.MainLooper) - handler = new Handler(Looper.MainLooper); - - handler.Post(action); - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs deleted file mode 100644 index 3ae74c81..00000000 --- a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.ios.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using Foundation; - -namespace LaunchDarkly.Xamarin.MainThread -{ - internal static partial class MainThread - { - static bool PlatformIsMainThread => - NSThread.Current.IsMainThread; - - static void PlatformBeginInvokeOnMainThread(Action action) - { - NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke); - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs deleted file mode 100644 index 1a80cc56..00000000 --- a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.netstandard.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; - -namespace LaunchDarkly.Xamarin.MainThread -{ - internal static partial class MainThread - { - static void PlatformBeginInvokeOnMainThread(Action action) => - throw new NotImplementedException(); - - static bool PlatformIsMainThread => - throw new NotImplementedException(); - } -} diff --git a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs b/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs deleted file mode 100644 index 021164e2..00000000 --- a/src/LaunchDarkly.XamarinSdk/MainThread/MainThread.shared.cs +++ /dev/null @@ -1,100 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin.MainThread -{ - internal static partial class MainThread - { - public static bool IsMainThread => - PlatformIsMainThread; - - public static void BeginInvokeOnMainThread(Action action) - { - if (IsMainThread) - { - action(); - } - else - { - PlatformBeginInvokeOnMainThread(action); - } - } - - internal static Task InvokeOnMainThread(Action action) - { - if (IsMainThread) - { - action(); -#if NETSTANDARD1_0 - return Task.FromResult(true); -#else - return Task.CompletedTask; -#endif - } - - var tcs = new TaskCompletionSource(); - - BeginInvokeOnMainThread(() => - { - try - { - action(); - tcs.TrySetResult(true); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }); - - return tcs.Task; - } - - internal static Task InvokeOnMainThread(Func action) - { - if (IsMainThread) - { - return Task.FromResult(action()); - } - - var tcs = new TaskCompletionSource(); - - BeginInvokeOnMainThread(() => - { - try - { - var result = action(); - tcs.TrySetResult(result); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }); - - return tcs.Task; - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs index c3c835c2..cf2bb06c 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs @@ -9,12 +9,7 @@ internal class MobileConnectionManager : IConnectionManager internal MobileConnectionManager() { UpdateConnectedStatus(); - try - { - LaunchDarkly.Xamarin.Connectivity.Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; - } - catch (NotImplementedException) - { } + LaunchDarkly.Xamarin.Connectivity.Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } bool isConnected; @@ -35,15 +30,7 @@ void Connectivity_ConnectivityChanged(object sender, Connectivity.ConnectivityCh private void UpdateConnectedStatus() { - try - { - isConnected = LaunchDarkly.Xamarin.Connectivity.Connectivity.NetworkAccess == LaunchDarkly.Xamarin.Connectivity.NetworkAccess.Internet; - } - catch (NotImplementedException) - { - // .NET Standard has no way to detect network connectivity - isConnected = true; - } + isConnected = LaunchDarkly.Xamarin.Connectivity.Connectivity.NetworkAccess == LaunchDarkly.Xamarin.Connectivity.NetworkAccess.Internet; } } } diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs index 17499e08..ab7a9bc8 100644 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs +++ b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs @@ -32,15 +32,20 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Permissions { + // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. + // Note that we are no longer using the shared Permissions abstraction at all; this Android code is being used directly + // by other Android-specific code. + internal static partial class Permissions { - static readonly object locker = new object(); - static int requestCode = 0; + //static readonly object locker = new object(); + //static int requestCode = 0; - static Dictionary tcs)> requests = - new Dictionary)>(); + //static Dictionary tcs)> requests = + //new Dictionary)>(); - static void PlatformEnsureDeclared(PermissionType permission) + //static void PlatformEnsureDeclared(PermissionType permission) + internal static void EnsureDeclared(PermissionType permission) { var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: false); @@ -61,101 +66,101 @@ static void PlatformEnsureDeclared(PermissionType permission) } } - static Task PlatformCheckStatusAsync(PermissionType permission) - { - EnsureDeclared(permission); - - // If there are no android permissions for the given permission type - // just return granted since we have none to ask for - var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true); - - if (androidPermissions == null || !androidPermissions.Any()) - return Task.FromResult(PermissionStatus.Granted); - - var context = Platform.Platform.AppContext; - var targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= BuildVersionCodes.M; - - foreach (var ap in androidPermissions) - { - if (targetsMOrHigher) - { - if (ContextCompat.CheckSelfPermission(context, ap) != Permission.Granted) - return Task.FromResult(PermissionStatus.Denied); - } - else - { - if (PermissionChecker.CheckSelfPermission(context, ap) != PermissionChecker.PermissionGranted) - return Task.FromResult(PermissionStatus.Denied); - } - } - - return Task.FromResult(PermissionStatus.Granted); - } - - static async Task PlatformRequestAsync(PermissionType permission) - { - // Check status before requesting first - if (await PlatformCheckStatusAsync(permission) == PermissionStatus.Granted) - return PermissionStatus.Granted; - - TaskCompletionSource tcs; - var doRequest = true; - - lock (locker) - { - if (requests.ContainsKey(permission)) - { - tcs = requests[permission].tcs; - doRequest = false; - } - else - { - tcs = new TaskCompletionSource(); - - // Get new request code and wrap it around for next use if it's going to reach max - if (++requestCode >= int.MaxValue) - requestCode = 1; - - requests.Add(permission, (requestCode, tcs)); - } - } - - if (!doRequest) - return await tcs.Task; - - if (!MainThread.MainThread.IsMainThread) - throw new System.UnauthorizedAccessException("Permission request must be invoked on main thread."); - - var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true).ToArray(); - - ActivityCompat.RequestPermissions(Platform.Platform.GetCurrentActivity(true), androidPermissions, requestCode); - - return await tcs.Task; - } - - internal static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) - { - lock (locker) - { - // Check our pending requests for one with a matching request code - foreach (var kvp in requests) - { - if (kvp.Value.requestCode == requestCode) - { - var tcs = kvp.Value.tcs; - - // Look for any denied requests, and deny the whole request if so - // Remember, each PermissionType is tied to 1 or more android permissions - // so if any android permissions denied the whole PermissionType is considered denied - if (grantResults.Any(g => g == Permission.Denied)) - tcs.TrySetResult(PermissionStatus.Denied); - else - tcs.TrySetResult(PermissionStatus.Granted); - break; - } - } - } - } + //static Task PlatformCheckStatusAsync(PermissionType permission) + //{ + // EnsureDeclared(permission); + + // // If there are no android permissions for the given permission type + // // just return granted since we have none to ask for + // var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true); + + // if (androidPermissions == null || !androidPermissions.Any()) + // return Task.FromResult(PermissionStatus.Granted); + + // var context = Platform.Platform.AppContext; + // var targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= BuildVersionCodes.M; + + // foreach (var ap in androidPermissions) + // { + // if (targetsMOrHigher) + // { + // if (ContextCompat.CheckSelfPermission(context, ap) != Permission.Granted) + // return Task.FromResult(PermissionStatus.Denied); + // } + // else + // { + // if (PermissionChecker.CheckSelfPermission(context, ap) != PermissionChecker.PermissionGranted) + // return Task.FromResult(PermissionStatus.Denied); + // } + // } + + // return Task.FromResult(PermissionStatus.Granted); + //} + + //static async Task PlatformRequestAsync(PermissionType permission) + //{ + // // Check status before requesting first + // if (await PlatformCheckStatusAsync(permission) == PermissionStatus.Granted) + // return PermissionStatus.Granted; + + // TaskCompletionSource tcs; + // var doRequest = true; + + // lock (locker) + // { + // if (requests.ContainsKey(permission)) + // { + // tcs = requests[permission].tcs; + // doRequest = false; + // } + // else + // { + // tcs = new TaskCompletionSource(); + + // // Get new request code and wrap it around for next use if it's going to reach max + // if (++requestCode >= int.MaxValue) + // requestCode = 1; + + // requests.Add(permission, (requestCode, tcs)); + // } + // } + + // if (!doRequest) + // return await tcs.Task; + + // if (!MainThread.MainThread.IsMainThread) + // throw new System.UnauthorizedAccessException("Permission request must be invoked on main thread."); + + // var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true).ToArray(); + + // ActivityCompat.RequestPermissions(Platform.Platform.GetCurrentActivity(true), androidPermissions, requestCode); + + // return await tcs.Task; + //} + + //internal static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) + //{ + // lock (locker) + // { + // // Check our pending requests for one with a matching request code + // foreach (var kvp in requests) + // { + // if (kvp.Value.requestCode == requestCode) + // { + // var tcs = kvp.Value.tcs; + + // // Look for any denied requests, and deny the whole request if so + // // Remember, each PermissionType is tied to 1 or more android permissions + // // so if any android permissions denied the whole PermissionType is considered denied + // if (grantResults.Any(g => g == Permission.Denied)) + // tcs.TrySetResult(PermissionStatus.Denied); + // else + // tcs.TrySetResult(PermissionStatus.Granted); + // break; + // } + // } + // } + //} } internal static class PermissionTypeExtensions diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs deleted file mode 100644 index 1d2807b1..00000000 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.ios.cs +++ /dev/null @@ -1,124 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System.Threading.Tasks; -using System; -using CoreLocation; -using Foundation; - -namespace LaunchDarkly.Xamarin.Permissions -{ - internal static partial class Permissions - { - static void PlatformEnsureDeclared(PermissionType permission) - { - var info = NSBundle.MainBundle.InfoDictionary; - - if (permission == PermissionType.LocationWhenInUse) - { - if (!info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) - throw new UnauthorizedAccessException("You must set `NSLocationWhenInUseUsageDescription` in your Info.plist file to enable Authorization Requests for Location updates."); - } - } - - static Task PlatformCheckStatusAsync(PermissionType permission) - { - EnsureDeclared(permission); - - switch (permission) - { - case PermissionType.LocationWhenInUse: - return Task.FromResult(GetLocationStatus()); - } - - return Task.FromResult(PermissionStatus.Granted); - } - - static async Task PlatformRequestAsync(PermissionType permission) - { - // Check status before requesting first and only request if Unknown - var status = await PlatformCheckStatusAsync(permission); - if (status != PermissionStatus.Unknown) - return status; - - EnsureDeclared(permission); - - switch (permission) - { - case PermissionType.LocationWhenInUse: - - if (!MainThread.MainThread.IsMainThread) - throw new UnauthorizedAccessException("Permission request must be invoked on main thread."); - - return await RequestLocationAsync(); - default: - return PermissionStatus.Granted; - } - } - - static PermissionStatus GetLocationStatus() - { - if (!CLLocationManager.LocationServicesEnabled) - return PermissionStatus.Disabled; - - var status = CLLocationManager.Status; - - switch (status) - { - case CLAuthorizationStatus.AuthorizedAlways: - case CLAuthorizationStatus.AuthorizedWhenInUse: - return PermissionStatus.Granted; - case CLAuthorizationStatus.Denied: - return PermissionStatus.Denied; - case CLAuthorizationStatus.Restricted: - return PermissionStatus.Restricted; - default: - return PermissionStatus.Unknown; - } - } - - static CLLocationManager locationManager; - - static Task RequestLocationAsync() - { - locationManager = new CLLocationManager(); - - var tcs = new TaskCompletionSource(locationManager); - - locationManager.AuthorizationChanged += LocationAuthCallback; - locationManager.RequestWhenInUseAuthorization(); - - return tcs.Task; - - void LocationAuthCallback(object sender, CLAuthorizationChangedEventArgs e) - { - if (e.Status == CLAuthorizationStatus.NotDetermined) - return; - - locationManager.AuthorizationChanged -= LocationAuthCallback; - tcs.TrySetResult(GetLocationStatus()); - locationManager.Dispose(); - locationManager = null; - } - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs deleted file mode 100644 index 2495b379..00000000 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.netstandard.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin.Permissions -{ - internal static partial class Permissions - { - static void PlatformEnsureDeclared(PermissionType permission) => - throw new NotImplementedException(); - - static Task PlatformCheckStatusAsync(PermissionType permission) => - throw new NotImplementedException(); - - static Task PlatformRequestAsync(PermissionType permission) => - throw new NotImplementedException(); - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs b/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs deleted file mode 100644 index f7d28c98..00000000 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin.Permissions -{ - internal static partial class Permissions - { - internal static void EnsureDeclared(PermissionType permission) => - PlatformEnsureDeclared(permission); - - internal static Task CheckStatusAsync(PermissionType permission) => - PlatformCheckStatusAsync(permission); - - internal static Task RequestAsync(PermissionType permission) => - PlatformRequestAsync(permission); - - internal static async Task RequireAsync(PermissionType permission) - { - if (await RequestAsync(permission) != PermissionStatus.Granted) - throw new System.UnauthorizedAccessException($"{permission} was not granted."); - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs b/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs index 435768ac..8ea21d35 100644 --- a/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs +++ b/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs @@ -32,146 +32,148 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.Net.Wifi; using Android.OS; +// All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. + namespace LaunchDarkly.Xamarin.Platform { internal static partial class Platform { - static ActivityLifecycleContextListener lifecycleListener; + //static ActivityLifecycleContextListener lifecycleListener; internal static Context AppContext => Application.Context; - internal static Activity GetCurrentActivity(bool throwOnNull) - { - var activity = lifecycleListener?.Activity; - if (throwOnNull && activity == null) - throw new NullReferenceException("The current Activity can not be detected. Ensure that you have called Init in your Activity or Application class."); - - return activity; - } - - public static void Init(Application application) - { - lifecycleListener = new ActivityLifecycleContextListener(); - application.RegisterActivityLifecycleCallbacks(lifecycleListener); - } - - public static void Init(Activity activity, Bundle bundle) - { - Init(activity.Application); - lifecycleListener.Activity = activity; - } - - public static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) => - Permissions.Permissions.OnRequestPermissionsResult(requestCode, permissions, grantResults); - - internal static bool HasSystemFeature(string systemFeature) - { - var packageManager = AppContext.PackageManager; - foreach (var feature in packageManager.GetSystemAvailableFeatures()) - { - if (feature.Name.Equals(systemFeature, StringComparison.OrdinalIgnoreCase)) - return true; - } - return false; - } - - internal static bool IsIntentSupported(Intent intent) - { - var manager = AppContext.PackageManager; - var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly); - return activities.Any(); - } + //internal static Activity GetCurrentActivity(bool throwOnNull) + //{ + // var activity = lifecycleListener?.Activity; + // if (throwOnNull && activity == null) + // throw new NullReferenceException("The current Activity can not be detected. Ensure that you have called Init in your Activity or Application class."); + + // return activity; + //} + + //public static void Init(Application application) + //{ + // lifecycleListener = new ActivityLifecycleContextListener(); + // application.RegisterActivityLifecycleCallbacks(lifecycleListener); + //} + + //public static void Init(Activity activity, Bundle bundle) + //{ + // Init(activity.Application); + // lifecycleListener.Activity = activity; + //} + + //public static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) => + // Permissions.Permissions.OnRequestPermissionsResult(requestCode, permissions, grantResults); + + //internal static bool HasSystemFeature(string systemFeature) + //{ + // var packageManager = AppContext.PackageManager; + // foreach (var feature in packageManager.GetSystemAvailableFeatures()) + // { + // if (feature.Name.Equals(systemFeature, StringComparison.OrdinalIgnoreCase)) + // return true; + // } + // return false; + //} + + //internal static bool IsIntentSupported(Intent intent) + //{ + // var manager = AppContext.PackageManager; + // var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly); + // return activities.Any(); + //} internal static bool HasApiLevel(BuildVersionCodes versionCode) => (int)Build.VERSION.SdkInt >= (int)versionCode; - internal static CameraManager CameraManager => - AppContext.GetSystemService(Context.CameraService) as CameraManager; + //internal static CameraManager CameraManager => + // AppContext.GetSystemService(Context.CameraService) as CameraManager; internal static ConnectivityManager ConnectivityManager => AppContext.GetSystemService(Context.ConnectivityService) as ConnectivityManager; - internal static Vibrator Vibrator => - AppContext.GetSystemService(Context.VibratorService) as Vibrator; + //internal static Vibrator Vibrator => + // AppContext.GetSystemService(Context.VibratorService) as Vibrator; - internal static WifiManager WifiManager => - AppContext.GetSystemService(Context.WifiService) as WifiManager; + //internal static WifiManager WifiManager => + // AppContext.GetSystemService(Context.WifiService) as WifiManager; - internal static SensorManager SensorManager => - AppContext.GetSystemService(Context.SensorService) as SensorManager; + //internal static SensorManager SensorManager => + // AppContext.GetSystemService(Context.SensorService) as SensorManager; - internal static ClipboardManager ClipboardManager => - AppContext.GetSystemService(Context.ClipboardService) as ClipboardManager; + //internal static ClipboardManager ClipboardManager => + // AppContext.GetSystemService(Context.ClipboardService) as ClipboardManager; - internal static LocationManager LocationManager => - AppContext.GetSystemService(Context.LocationService) as LocationManager; + //internal static LocationManager LocationManager => + // AppContext.GetSystemService(Context.LocationService) as LocationManager; - internal static PowerManager PowerManager => - AppContext.GetSystemService(Context.PowerService) as PowerManager; + //internal static PowerManager PowerManager => + //AppContext.GetSystemService(Context.PowerService) as PowerManager; - internal static Java.Util.Locale GetLocale() - { - var resources = AppContext.Resources; - var config = resources.Configuration; - if (HasApiLevel(BuildVersionCodes.N)) - return config.Locales.Get(0); + //internal static Java.Util.Locale GetLocale() + //{ + // var resources = AppContext.Resources; + // var config = resources.Configuration; + // if (HasApiLevel(BuildVersionCodes.N)) + // return config.Locales.Get(0); - return config.Locale; - } + // return config.Locale; + //} - internal static void SetLocale(Java.Util.Locale locale) - { - Java.Util.Locale.Default = locale; - var resources = AppContext.Resources; - var config = resources.Configuration; - if (HasApiLevel(BuildVersionCodes.N)) - config.SetLocale(locale); - else - config.Locale = locale; +// internal static void SetLocale(Java.Util.Locale locale) +// { +// Java.Util.Locale.Default = locale; +// var resources = AppContext.Resources; +// var config = resources.Configuration; +// if (HasApiLevel(BuildVersionCodes.N)) +// config.SetLocale(locale); +// else +// config.Locale = locale; -#pragma warning disable CS0618 // Type or member is obsolete - resources.UpdateConfiguration(config, resources.DisplayMetrics); -#pragma warning restore CS0618 // Type or member is obsolete - } +//#pragma warning disable CS0618 // Type or member is obsolete +// resources.UpdateConfiguration(config, resources.DisplayMetrics); +//#pragma warning restore CS0618 // Type or member is obsolete + //} } - internal class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks - { - WeakReference currentActivity = new WeakReference(null); + //internal class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks + //{ + // WeakReference currentActivity = new WeakReference(null); - internal Context Context => - Activity ?? Application.Context; + // internal Context Context => + // Activity ?? Application.Context; - internal Activity Activity - { - get => currentActivity.TryGetTarget(out var a) ? a : null; - set => currentActivity.SetTarget(value); - } + // internal Activity Activity + // { + // get => currentActivity.TryGetTarget(out var a) ? a : null; + // set => currentActivity.SetTarget(value); + // } - void Application.IActivityLifecycleCallbacks.OnActivityCreated(Activity activity, Bundle savedInstanceState) => - Activity = activity; + // void Application.IActivityLifecycleCallbacks.OnActivityCreated(Activity activity, Bundle savedInstanceState) => + // Activity = activity; - void Application.IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity) - { - } + // void Application.IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity) + // { + // } - void Application.IActivityLifecycleCallbacks.OnActivityPaused(Activity activity) => - Activity = activity; + // void Application.IActivityLifecycleCallbacks.OnActivityPaused(Activity activity) => + // Activity = activity; - void Application.IActivityLifecycleCallbacks.OnActivityResumed(Activity activity) => - Activity = activity; + // void Application.IActivityLifecycleCallbacks.OnActivityResumed(Activity activity) => + // Activity = activity; - void Application.IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState) - { - } + // void Application.IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState) + // { + // } - void Application.IActivityLifecycleCallbacks.OnActivityStarted(Activity activity) - { - } + // void Application.IActivityLifecycleCallbacks.OnActivityStarted(Activity activity) + // { + // } - void Application.IActivityLifecycleCallbacks.OnActivityStopped(Activity activity) - { - } - } + // void Application.IActivityLifecycleCallbacks.OnActivityStopped(Activity activity) + // { + // } + //} } diff --git a/src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs b/src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs deleted file mode 100644 index b416338f..00000000 --- a/src/LaunchDarkly.XamarinSdk/Platform/Platform.ios.cs +++ /dev/null @@ -1,104 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Linq; -using System.Runtime.InteropServices; -using CoreMotion; -using Foundation; -using ObjCRuntime; -using UIKit; - -namespace LaunchDarkly.Xamarin.Platform -{ - internal static partial class Platform - { - [DllImport(ObjCRuntime.Constants.SystemLibrary, EntryPoint = "sysctlbyname")] - internal static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); - - internal static string GetSystemLibraryProperty(string property) - { - var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); - SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); - - var propertyLength = Marshal.ReadInt32(lengthPtr); - - if (propertyLength == 0) - { - Marshal.FreeHGlobal(lengthPtr); - throw new InvalidOperationException("Unable to read length of property."); - } - - var valuePtr = Marshal.AllocHGlobal(propertyLength); - SysctlByName(property, valuePtr, lengthPtr, IntPtr.Zero, 0); - - var returnValue = Marshal.PtrToStringAnsi(valuePtr); - - Marshal.FreeHGlobal(lengthPtr); - Marshal.FreeHGlobal(valuePtr); - - return returnValue; - } - - internal static bool HasOSVersion(int major, int minor) => - UIDevice.CurrentDevice.CheckSystemVersion(major, minor); - - internal static UIViewController GetCurrentViewController(bool throwIfNull = true) - { - UIViewController viewController = null; - - var window = UIApplication.SharedApplication.KeyWindow; - - if (window.WindowLevel == UIWindowLevel.Normal) - viewController = window.RootViewController; - - if (viewController == null) - { - window = UIApplication.SharedApplication - .Windows - .OrderByDescending(w => w.WindowLevel) - .FirstOrDefault(w => w.RootViewController != null && w.WindowLevel == UIWindowLevel.Normal); - - if (window == null) - throw new InvalidOperationException("Could not find current view controller."); - else - viewController = window.RootViewController; - } - - while (viewController.PresentedViewController != null) - viewController = viewController.PresentedViewController; - - if (throwIfNull && viewController == null) - throw new InvalidOperationException("Could not find current view controller."); - - return viewController; - } - - static CMMotionManager motionManager; - - internal static CMMotionManager MotionManager => - motionManager ?? (motionManager = new CMMotionManager()); - - internal static NSOperationQueue GetCurrentQueue() => - NSOperationQueue.CurrentQueue ?? new NSOperationQueue(); - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs index 6aa16790..ae33089d 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs @@ -1,44 +1,20 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; +using System; namespace LaunchDarkly.Xamarin.Preferences { + // This code is not from Xamarin Essentials, though it implements the same Preferences abstraction. + // It is a stub with no underlying data store. + internal static partial class Preferences { - static bool PlatformContainsKey(string key, string sharedName) => - throw new NotImplementedException(); + static bool PlatformContainsKey(string key, string sharedName) => false; - static void PlatformRemove(string key, string sharedName) => - throw new NotImplementedException(); + static void PlatformRemove(string key, string sharedName) { } - static void PlatformClear(string sharedName) => - throw new NotImplementedException(); + static void PlatformClear(string sharedName) { } - static void PlatformSet(string key, T value, string sharedName) => - throw new NotImplementedException(); + static void PlatformSet(string key, T value, string sharedName) { } - static T PlatformGet(string key, T defaultValue, string sharedName) => - throw new NotImplementedException(); + static T PlatformGet(string key, T defaultValue, string sharedName) => defaultValue; } } diff --git a/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs index 5809d1b3..e373d732 100644 --- a/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs @@ -6,23 +6,12 @@ internal class SimpleMobileDevicePersistance : ISimplePersistance { public void Save(string key, string value) { - try - { - LaunchDarkly.Xamarin.Preferences.Preferences.Set(key, value); - } - catch (NotImplementedException) { } + LaunchDarkly.Xamarin.Preferences.Preferences.Set(key, value); } public string GetValue(string key) { - try - { - return LaunchDarkly.Xamarin.Preferences.Preferences.Get(key, null); - } - catch (NotImplementedException) - { - return null; - } + return LaunchDarkly.Xamarin.Preferences.Preferences.Get(key, null); } } } diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs index e27ac884..a992e4d0 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs +++ b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs @@ -4,14 +4,10 @@ namespace LaunchDarkly.Xamarin { internal static partial class UserMetadata { - private static string GetDevice() - { - return Build.Model + " " + Build.Product; - } + private static string PlatformDevice => + Build.Model + " " + Build.Product; - private static string GetOS() - { - return "Android " + Build.VERSION.SdkInt; - } + private static string PlatformOS => + "Android " + Build.VERSION.SdkInt; } } diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs index da7f53e3..6f66f57f 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs @@ -4,26 +4,27 @@ namespace LaunchDarkly.Xamarin { internal static partial class UserMetadata { - private static string GetDevice() + private static string PlatformDevice { - switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + get { - case UIUserInterfaceIdiom.CarPlay: - return "CarPlay"; - case UIUserInterfaceIdiom.Pad: - return "iPad"; - case UIUserInterfaceIdiom.Phone: - return "iPhone"; - case UIUserInterfaceIdiom.TV: - return "Apple TV"; - default: - return "unknown"; + switch (UIDevice.CurrentDevice.UserInterfaceIdiom) + { + case UIUserInterfaceIdiom.CarPlay: + return "CarPlay"; + case UIUserInterfaceIdiom.Pad: + return "iPad"; + case UIUserInterfaceIdiom.Phone: + return "iPhone"; + case UIUserInterfaceIdiom.TV: + return "Apple TV"; + default: + return "unknown"; + } } } - private static string GetOS() - { - return "iOS " + UIDevice.CurrentDevice.SystemVersion; - } + private static string PlatformOS => + "iOS " + UIDevice.CurrentDevice.SystemVersion; } } diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs index 9ef808d8..013ebf0e 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs @@ -3,14 +3,8 @@ namespace LaunchDarkly.Xamarin { internal static partial class UserMetadata { - private static string GetDevice() - { - return null; - } - - private static string GetOS() - { - return null; - } + private static string PlatformDevice => null; + + private static string PlatformOS => null; } } diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs index ba4e7d4e..3c769c16 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs @@ -1,10 +1,14 @@  namespace LaunchDarkly.Xamarin { + // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. + internal static partial class UserMetadata { - private static readonly string _os = GetOS(); - private static readonly string _device = GetDevice(); + // These values are obtained from the platform-specific code once and then stored in static fields, + // to avoid having to recompute them many times. + private static readonly string _os = PlatformOS; + private static readonly string _device = PlatformDevice; /// /// Returns the string that should be passed in the "device" property for all users. From b3fdb736cea4bcc5b469a1abc321332f3008f926 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 4 Jun 2019 14:09:23 -0700 Subject: [PATCH 081/499] .NET Standard 2.0 implementation of persistent storage --- .../Preferences/Preferences.android.cs | 77 ++--------- .../Preferences/Preferences.ios.cs | 64 ++------- .../Preferences/Preferences.netstandard.cs | 129 +++++++++++++++++- .../Preferences/Preferences.shared.cs | 104 +++++++------- 4 files changed, 195 insertions(+), 179 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs index 381029fb..1e07aec0 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs @@ -28,6 +28,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { + // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class + // to store them. Therefore, the overloads for non-string types have been removed, thereby + // reducing the amount of multi-platform implementation code that won't be used. + internal static partial class Preferences { static readonly object locker = new object(); @@ -67,7 +71,7 @@ static void PlatformClear(string sharedName) } } - static void PlatformSet(string key, T value, string sharedName) + static void PlatformSet(string key, string value, string sharedName) { lock (locker) { @@ -80,87 +84,22 @@ static void PlatformSet(string key, T value, string sharedName) } else { - switch (value) - { - case string s: - editor.PutString(key, s); - break; - case int i: - editor.PutInt(key, i); - break; - case bool b: - editor.PutBoolean(key, b); - break; - case long l: - editor.PutLong(key, l); - break; - case double d: - var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); - editor.PutString(key, valueString); - break; - case float f: - editor.PutFloat(key, f); - break; - } + editor.PutString(key, s); } editor.Apply(); } } } - static T PlatformGet(string key, T defaultValue, string sharedName) + static string PlatformGet(string key, string defaultValue, string sharedName) { lock (locker) { object value = null; using (var sharedPreferences = GetSharedPreferences(sharedName)) { - if (defaultValue == null) - { - value = sharedPreferences.GetString(key, null); - } - else - { - switch (defaultValue) - { - case int i: - value = sharedPreferences.GetInt(key, i); - break; - case bool b: - value = sharedPreferences.GetBoolean(key, b); - break; - case long l: - value = sharedPreferences.GetLong(key, l); - break; - case double d: - var savedDouble = sharedPreferences.GetString(key, null); - if (string.IsNullOrWhiteSpace(savedDouble)) - { - value = defaultValue; - } - else - { - if (!double.TryParse(savedDouble, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var outDouble)) - { - var maxString = Convert.ToString(double.MaxValue, CultureInfo.InvariantCulture); - outDouble = savedDouble.Equals(maxString) ? double.MaxValue : double.MinValue; - } - - value = outDouble; - } - break; - case float f: - value = sharedPreferences.GetFloat(key, f); - break; - case string s: - // the case when the string is not null - value = sharedPreferences.GetString(key, s); - break; - } - } + return sharedPreferences.GetString(key, defaultValue); } - - return (T)value; } } diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs index 9497cd09..616cd03b 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs @@ -26,6 +26,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { + // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class + // to store them. Therefore, the overloads for non-string types have been removed, thereby + // reducing the amount of multi-platform implementation code that won't be used. + internal static partial class Preferences { static readonly object locker = new object(); @@ -67,7 +71,7 @@ static void PlatformClear(string sharedName) } } - static void PlatformSet(string key, T value, string sharedName) + static void PlatformSet(string key, string value, string sharedName) { lock (locker) { @@ -80,36 +84,13 @@ static void PlatformSet(string key, T value, string sharedName) return; } - switch (value) - { - case string s: - userDefaults.SetString(s, key); - break; - case int i: - userDefaults.SetInt(i, key); - break; - case bool b: - userDefaults.SetBool(b, key); - break; - case long l: - var valueString = Convert.ToString(value, CultureInfo.InvariantCulture); - userDefaults.SetString(valueString, key); - break; - case double d: - userDefaults.SetDouble(d, key); - break; - case float f: - userDefaults.SetFloat(f, key); - break; - } + userDefaults.SetString(value, key); } } } - static T PlatformGet(string key, T defaultValue, string sharedName) + static string PlatformGet(string key, string defaultValue, string sharedName) { - object value = null; - lock (locker) { using (var userDefaults = GetUserDefaults(sharedName)) @@ -117,38 +98,9 @@ static T PlatformGet(string key, T defaultValue, string sharedName) if (userDefaults[key] == null) return defaultValue; - switch (defaultValue) - { - case int i: - value = (int)(nint)userDefaults.IntForKey(key); - break; - case bool b: - value = userDefaults.BoolForKey(key); - break; - case long l: - var savedLong = userDefaults.StringForKey(key); - value = Convert.ToInt64(savedLong, CultureInfo.InvariantCulture); - break; - case double d: - value = userDefaults.DoubleForKey(key); - break; - case float f: - value = userDefaults.FloatForKey(key); - break; - case string s: - // the case when the string is not null - value = userDefaults.StringForKey(key); - break; - default: - // the case when the string is null - if (typeof(T) == typeof(string)) - value = userDefaults.StringForKey(key); - break; - } + return userDefaults.StringForKey(key); } } - - return (T)value; } static NSUserDefaults GetUserDefaults(string sharedName) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs index ae33089d..0023d194 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs @@ -1,20 +1,141 @@ -using System; +#if NETSTANDARD1_6 +#else +using System.IO; +using System.IO.IsolatedStorage; +using System.Linq; +using System.Text; +#endif namespace LaunchDarkly.Xamarin.Preferences { // This code is not from Xamarin Essentials, though it implements the same Preferences abstraction. - // It is a stub with no underlying data store. + // + // In .NET Standard 2.0, we use the IsolatedStorage API to store per-user data. The .NET Standard implementation + // of IsolatedStorage puts these files under ~/.local/share/IsolatedStorage followed by a subpath of obfuscated + // strings that are apparently based on the application and assembly name, so the data should be specific to both + // the OS user account and the current app. + // + // In .NET Standard 1.6, there is no data store. internal static partial class Preferences { +#if NETSTANDARD1_6 static bool PlatformContainsKey(string key, string sharedName) => false; static void PlatformRemove(string key, string sharedName) { } static void PlatformClear(string sharedName) { } - static void PlatformSet(string key, T value, string sharedName) { } + static void PlatformSet(string key, string value, string sharedName) { } - static T PlatformGet(string key, T defaultValue, string sharedName) => defaultValue; + static string PlatformGet(string key, string defaultValue, string sharedName) => defaultValue; +#else + private const string ConfigDirectoryName = "LaunchDarkly"; + + // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. + private static IsolatedStorageFile Store => IsolatedStorageFile.GetUserStoreForDomain(); + + static bool PlatformContainsKey(string key, string sharedName) + { + return Store.FileExists(MakeFilePath(key, sharedName)); + } + + static void PlatformRemove(string key, string sharedName) + { + try + { + Store.DeleteFile(MakeFilePath(key, sharedName)); + } + catch (IsolatedStorageException) {} // Preferences implementations shouldn't throw exceptions except for a code error like a null reference + } + + static void PlatformClear(string sharedName) + { + try + { + Store.DeleteDirectory(MakeDirectoryPath(sharedName)); + } + catch (IsolatedStorageException) {} + } + + static void PlatformSet(string key, string value, string sharedName) + { + try + { + var path = MakeDirectoryPath(sharedName); + if (!Store.DirectoryExists(path)) + { + Store.CreateDirectory(path); + } + } + catch (IsolatedStorageException) {} + using (var stream = Store.OpenFile(MakeFilePath(key, sharedName), FileMode.Create, FileAccess.Write)) + { + using (var sw = new StreamWriter(stream)) + { + sw.Write(value); + } + } + } + + static string PlatformGet(string key, string defaultValue, string sharedName) + { + try + { + using (var stream = Store.OpenFile(MakeFilePath(key, sharedName), FileMode.Open)) + { + using (var sr = new StreamReader(stream)) + { + return sr.ReadToEnd(); + } + } + } + catch (IsolatedStorageException) {} + catch (DirectoryNotFoundException) {} + catch (FileNotFoundException) {} + return null; + } + + static string MakeDirectoryPath(string sharedName) + { + if (string.IsNullOrEmpty(sharedName)) + { + return ConfigDirectoryName; + } + return ConfigDirectoryName + "." + EscapeFilenameComponent(sharedName); + } + + static string MakeFilePath(string key, string sharedName) + { + return MakeDirectoryPath(sharedName) + "/" + EscapeFilenameComponent(key); + } + + static string EscapeFilenameComponent(string name) + { + // In actual usage for LaunchDarkly this should not be an issue, because keys are really feature flag keys + // which have a very limited character set, and we don't actually use sharedName. It's just good practice. + StringBuilder buf = null; + var badChars = Path.GetInvalidFileNameChars(); + const char escapeChar = '%'; + for (var i = 0; i < name.Length; i++) + { + var ch = name[i]; + if (badChars.Contains(ch) || ch == escapeChar) + { + if (buf == null) // create StringBuilder lazily since most names will be valid + { + buf = new StringBuilder(name.Length); + buf.Append(name.Substring(0, i)); + } + buf.Append(escapeChar).Append(((int)ch).ToString("X")); // hex value + } + else + { + buf?.Append(ch); + } + } + return buf == null ? name : buf.ToString(); + } +#endif } } diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs index 62997c12..ef221963 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs @@ -24,6 +24,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace LaunchDarkly.Xamarin.Preferences { + // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class + // to store them. Therefore, the overloads for non-string types have been removed, thereby + // reducing the amount of multi-platform implementation code that won't be used. + internal static partial class Preferences { internal static string GetPrivatePreferencesSharedName(string feature) => @@ -43,38 +47,38 @@ public static void Clear() => public static string Get(string key, string defaultValue) => Get(key, defaultValue, null); - public static bool Get(string key, bool defaultValue) => - Get(key, defaultValue, null); + //public static bool Get(string key, bool defaultValue) => + // Get(key, defaultValue, null); - public static int Get(string key, int defaultValue) => - Get(key, defaultValue, null); + //public static int Get(string key, int defaultValue) => + // Get(key, defaultValue, null); - public static double Get(string key, double defaultValue) => - Get(key, defaultValue, null); + //public static double Get(string key, double defaultValue) => + // Get(key, defaultValue, null); - public static float Get(string key, float defaultValue) => - Get(key, defaultValue, null); + //public static float Get(string key, float defaultValue) => + // Get(key, defaultValue, null); - public static long Get(string key, long defaultValue) => - Get(key, defaultValue, null); + //public static long Get(string key, long defaultValue) => + // Get(key, defaultValue, null); public static void Set(string key, string value) => Set(key, value, null); - public static void Set(string key, bool value) => - Set(key, value, null); + //public static void Set(string key, bool value) => + // Set(key, value, null); - public static void Set(string key, int value) => - Set(key, value, null); + //public static void Set(string key, int value) => + // Set(key, value, null); - public static void Set(string key, double value) => - Set(key, value, null); + //public static void Set(string key, double value) => + // Set(key, value, null); - public static void Set(string key, float value) => - Set(key, value, null); + //public static void Set(string key, float value) => + // Set(key, value, null); - public static void Set(string key, long value) => - Set(key, value, null); + //public static void Set(string key, long value) => + // Set(key, value, null); // shared -> platform @@ -88,53 +92,53 @@ public static void Clear(string sharedName) => PlatformClear(sharedName); public static string Get(string key, string defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + PlatformGet(key, defaultValue, sharedName); - public static bool Get(string key, bool defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + //public static bool Get(string key, bool defaultValue, string sharedName) => + // PlatformGet(key, defaultValue, sharedName); - public static int Get(string key, int defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + //public static int Get(string key, int defaultValue, string sharedName) => + // PlatformGet(key, defaultValue, sharedName); - public static double Get(string key, double defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + //public static double Get(string key, double defaultValue, string sharedName) => + // PlatformGet(key, defaultValue, sharedName); - public static float Get(string key, float defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + //public static float Get(string key, float defaultValue, string sharedName) => + // PlatformGet(key, defaultValue, sharedName); - public static long Get(string key, long defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + //public static long Get(string key, long defaultValue, string sharedName) => + // PlatformGet(key, defaultValue, sharedName); public static void Set(string key, string value, string sharedName) => - PlatformSet(key, value, sharedName); + PlatformSet(key, value, sharedName); - public static void Set(string key, bool value, string sharedName) => - PlatformSet(key, value, sharedName); + //public static void Set(string key, bool value, string sharedName) => + // PlatformSet(key, value, sharedName); - public static void Set(string key, int value, string sharedName) => - PlatformSet(key, value, sharedName); + //public static void Set(string key, int value, string sharedName) => + // PlatformSet(key, value, sharedName); - public static void Set(string key, double value, string sharedName) => - PlatformSet(key, value, sharedName); + //public static void Set(string key, double value, string sharedName) => + // PlatformSet(key, value, sharedName); - public static void Set(string key, float value, string sharedName) => - PlatformSet(key, value, sharedName); + //public static void Set(string key, float value, string sharedName) => + // PlatformSet(key, value, sharedName); - public static void Set(string key, long value, string sharedName) => - PlatformSet(key, value, sharedName); + //public static void Set(string key, long value, string sharedName) => + // PlatformSet(key, value, sharedName); // DateTime - public static DateTime Get(string key, DateTime defaultValue) => - Get(key, defaultValue, null); + //public static DateTime Get(string key, DateTime defaultValue) => + // Get(key, defaultValue, null); - public static void Set(string key, DateTime value) => - Set(key, value, null); + //public static void Set(string key, DateTime value) => + // Set(key, value, null); - public static DateTime Get(string key, DateTime defaultValue, string sharedName) => - DateTime.FromBinary(PlatformGet(key, defaultValue.ToBinary(), sharedName)); + //public static DateTime Get(string key, DateTime defaultValue, string sharedName) => + // DateTime.FromBinary(PlatformGet(key, defaultValue.ToBinary(), sharedName)); - public static void Set(string key, DateTime value, string sharedName) => - PlatformSet(key, value.ToBinary(), sharedName); + //public static void Set(string key, DateTime value, string sharedName) => + // PlatformSet(key, value.ToBinary(), sharedName); } } From eb01cde35db93d0dbe62313c79dd8671597ed93e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 4 Jun 2019 15:27:49 -0700 Subject: [PATCH 082/499] better error handling for persistent storage --- .../Preferences/Preferences.netstandard.cs | 128 +++++++++++++----- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs index 0023d194..b46621c8 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs @@ -1,9 +1,12 @@ #if NETSTANDARD1_6 #else +using System; using System.IO; using System.IO.IsolatedStorage; using System.Linq; using System.Text; +using Common.Logging; +using LaunchDarkly.Common; #endif namespace LaunchDarkly.Xamarin.Preferences @@ -30,73 +33,132 @@ static void PlatformSet(string key, string value, string sharedName) { } static string PlatformGet(string key, string defaultValue, string sharedName) => defaultValue; #else - private const string ConfigDirectoryName = "LaunchDarkly"; + private static readonly ILog Log = LogManager.GetLogger(typeof(Preferences)); + + private static AtomicBoolean _loggedOSError = new AtomicBoolean(false); // AtomicBoolean is defined in LaunchDarkly.CommonSdk - // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. - private static IsolatedStorageFile Store => IsolatedStorageFile.GetUserStoreForDomain(); + private const string ConfigDirectoryName = "LaunchDarkly"; static bool PlatformContainsKey(string key, string sharedName) { - return Store.FileExists(MakeFilePath(key, sharedName)); + return WithStore(store => store.FileExists(MakeFilePath(key, sharedName))); } static void PlatformRemove(string key, string sharedName) { - try + WithStore(store => { - Store.DeleteFile(MakeFilePath(key, sharedName)); - } - catch (IsolatedStorageException) {} // Preferences implementations shouldn't throw exceptions except for a code error like a null reference + try + { + store.DeleteFile(MakeFilePath(key, sharedName)); + } + catch (IsolatedStorageException) { } // file didn't exist - that's OK + }); } static void PlatformClear(string sharedName) { - try + WithStore(store => { - Store.DeleteDirectory(MakeDirectoryPath(sharedName)); - } - catch (IsolatedStorageException) {} + try + { + store.DeleteDirectory(MakeDirectoryPath(sharedName)); + // The directory will be recreated next time PlatformSet is called with the same sharedName. + } + catch (IsolatedStorageException) { } // directory didn't exist - that's OK + }); } static void PlatformSet(string key, string value, string sharedName) { - try + WithStore(store => { var path = MakeDirectoryPath(sharedName); - if (!Store.DirectoryExists(path)) + store.CreateDirectory(path); // has no effect if directory already exists + using (var stream = store.OpenFile(MakeFilePath(key, sharedName), FileMode.Create, FileAccess.Write)) { - Store.CreateDirectory(path); + using (var sw = new StreamWriter(stream)) + { + sw.Write(value); + } } - } - catch (IsolatedStorageException) {} - using (var stream = Store.OpenFile(MakeFilePath(key, sharedName), FileMode.Create, FileAccess.Write)) + }); + } + + static string PlatformGet(string key, string defaultValue, string sharedName) + { + return WithStore(store => { - using (var sw = new StreamWriter(stream)) + try { - sw.Write(value); + using (var stream = store.OpenFile(MakeFilePath(key, sharedName), FileMode.Open)) + { + using (var sr = new StreamReader(stream)) + { + return sr.ReadToEnd(); + } + } } - } + catch (DirectoryNotFoundException) { } // just return null if no preferences have ever been set + catch (FileNotFoundException) { } // just return null if this preference was never set + return null; + }); } - static string PlatformGet(string key, string defaultValue, string sharedName) + private static T WithStore(Func callback) { + IsolatedStorageFile store; try { - using (var stream = Store.OpenFile(MakeFilePath(key, sharedName), FileMode.Open)) + // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. + store = IsolatedStorageFile.GetUserStoreForDomain(); + } + catch (Exception e) + { + HandleStoreException(e); + return default; + } + return callback(store); + } + + private static void HandleStoreException(Exception e) + { + if (e is IsolatedStorageException || + e is InvalidOperationException) + { + // These exceptions are ones that IsolatedStorageFile methods may throw under conditions that are + // unrelated to our code, e.g. filesystem permissions don't allow the store to be used. Since such a + // condition is unlikely to change during the application's lifetime, we only want to log it once. + // We won't log a stacktrace since it'll just point to somewhere in the standard library. + + // Note that we specifically catch IsolatedStorageException in a couple places above, when it would + // indicate a particular error condition that we want to handle differently. In all other cases it + // is unexpected and should be considered a platform/configuration issue. + + if (!_loggedOSError.GetAndSet(true)) { - using (var sr = new StreamReader(stream)) - { - return sr.ReadToEnd(); - } + Log.WarnFormat("Persistent storage is unavailable and has been disabled ({0}: {1})", e.GetType(), e.Message); } } - catch (IsolatedStorageException) {} - catch (DirectoryNotFoundException) {} - catch (FileNotFoundException) {} - return null; + else + { + // All other errors probably indicate an error in our own code. We don't want to throw these up + // into the SDK; the Preferences API is expected to either work or silently fail. + Log.ErrorFormat("Error in accessing persistent storage: {0}: {1}", e.GetType(), e.Message); + Log.Debug(e.StackTrace); + } + } + + private static void WithStore(Action callback) + { + WithStore(store => + { + callback(store); + return true; + }); } - static string MakeDirectoryPath(string sharedName) + private static string MakeDirectoryPath(string sharedName) { if (string.IsNullOrEmpty(sharedName)) { @@ -105,7 +167,7 @@ static string MakeDirectoryPath(string sharedName) return ConfigDirectoryName + "." + EscapeFilenameComponent(sharedName); } - static string MakeFilePath(string key, string sharedName) + private static string MakeFilePath(string key, string sharedName) { return MakeDirectoryPath(sharedName) + "/" + EscapeFilenameComponent(key); } From 2f8cdfb21311fdc46507ab3a93f4205b0ec3392a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 4 Jun 2019 15:41:08 -0700 Subject: [PATCH 083/499] fix try block --- .../Preferences/Preferences.netstandard.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs index b46621c8..5891f2d6 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs @@ -107,18 +107,17 @@ static string PlatformGet(string key, string defaultValue, string sharedName) private static T WithStore(Func callback) { - IsolatedStorageFile store; try { // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. - store = IsolatedStorageFile.GetUserStoreForDomain(); + var store = IsolatedStorageFile.GetUserStoreForDomain(); + return callback(store); } catch (Exception e) { HandleStoreException(e); return default; } - return callback(store); } private static void HandleStoreException(Exception e) @@ -172,7 +171,7 @@ private static string MakeFilePath(string key, string sharedName) return MakeDirectoryPath(sharedName) + "/" + EscapeFilenameComponent(key); } - static string EscapeFilenameComponent(string name) + private static string EscapeFilenameComponent(string name) { // In actual usage for LaunchDarkly this should not be an issue, because keys are really feature flag keys // which have a very limited character set, and we don't actually use sharedName. It's just good practice. From 2a49acb723e522b37fa8f40dbd33844a5b4d61b3 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 4 Jun 2019 16:01:03 -0700 Subject: [PATCH 084/499] comment --- .../Preferences/Preferences.netstandard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs index 5891f2d6..71fc87f0 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs @@ -18,6 +18,9 @@ namespace LaunchDarkly.Xamarin.Preferences // strings that are apparently based on the application and assembly name, so the data should be specific to both // the OS user account and the current app. // + // This is based on the Plugin.Settings plugin (which is what Xamarin Essentials uses for preferences), but greatly + // simplified since we only need one data type. See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs + // // In .NET Standard 1.6, there is no data store. internal static partial class Preferences From 37844f13c9ef3c2970f844349f534d2e0857b3d4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 14:55:57 -0700 Subject: [PATCH 085/499] simplify backgrounding implementation, cleanup async usage --- .../AsyncUtils.shared.cs | 73 +++++++++++ .../BackgroundAdapter.ios.cs | 59 --------- .../BackgroundAdapter.netstandard.cs | 18 --- .../BackgroundDetection.android.cs} | 47 ++------ .../BackgroundDetection.ios.cs | 37 ++++++ .../BackgroundDetection.netstandard.cs | 15 +++ .../BackgroundDetection.shared.cs | 58 +++++++++ .../IBackgroundingState.shared.cs | 28 ----- .../IPlatformAdapter.shared.cs | 37 ------ .../LdClient.shared.cs | 113 ++++-------------- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 2 +- 11 files changed, 221 insertions(+), 266 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs rename src/LaunchDarkly.XamarinSdk/{BackgroundAdapter/BackgroundAdapter.android.cs => BackgroundDetection/BackgroundDetection.android.cs} (50%) create mode 100644 src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs create mode 100644 src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs create mode 100644 src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs b/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs new file mode 100644 index 00000000..d76b72e7 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin +{ + internal static class AsyncUtils + { + private static readonly TaskFactory _taskFactory = new TaskFactory(CancellationToken.None, + TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); + + // This procedure for blocking on a Task without using Task.Wait is derived from the MIT-licensed ASP.NET + // code here: https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs + // In general, mixing sync and async code is not recommended, and if done in other ways can result in + // deadlocks. See: https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c + // Task.Wait would only be safe if we could guarantee that every intermediate Task within the async + // code had been modified with ConfigureAwait(false), but that is very error-prone and we can't depend + // on feature store implementors doing so. + + internal static void WaitSafely(Func taskFn) + { + try + { + _taskFactory.StartNew(taskFn) + .Unwrap() + .GetAwaiter() + .GetResult(); + } + catch (AggregateException e) + { + throw UnwrapAggregateException(e); + } + } + + internal static bool WaitSafely(Func taskFn, TimeSpan timeout) + { + try + { + return _taskFactory.StartNew(taskFn) + .Unwrap() + .Wait(timeout); + } + catch (AggregateException e) + { + throw UnwrapAggregateException(e); + } + } + + internal static T WaitSafely(Func> taskFn) + { + try + { + return _taskFactory.StartNew(taskFn) + .Unwrap() + .GetAwaiter() + .GetResult(); + } + catch (AggregateException e) + { + throw UnwrapAggregateException(e); + } + } + + private static Exception UnwrapAggregateException(AggregateException e) + { + if (e.InnerExceptions.Count == 1) + { + return e.InnerExceptions[0]; + } + return e; + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs deleted file mode 100644 index 457ae6f7..00000000 --- a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.ios.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using LaunchDarkly.Xamarin; -using UIKit; -using Common.Logging; -using Foundation; - -namespace LaunchDarkly.Xamarin.BackgroundAdapter -{ - internal class BackgroundAdapter : IPlatformAdapter - { - private IBackgroundingState _backgroundingState; - private NSObject _foregroundHandle; - private NSObject _backgroundHandle; - private static readonly ILog Log = LogManager.GetLogger(typeof(BackgroundAdapter)); - - public void EnableBackgrounding(IBackgroundingState backgroundingState) - { - _foregroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, HandleWillEnterForeground); - _backgroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, HandleWillEnterBackground); - _backgroundingState = backgroundingState; - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected void _Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - _backgroundingState = null; - _foregroundHandle = null; - - disposedValue = true; - } - } - - public void Dispose() - { - _Dispose(true); - GC.SuppressFinalize(this); - } - #endregion - - private void HandleWillEnterForeground(NSNotification notification) - { - _backgroundingState.ExitBackgroundAsync(); - } - - private void HandleWillEnterBackground(NSNotification notification) - { - _backgroundingState.EnterBackgroundAsync(); - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs b/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs deleted file mode 100644 index 047c801a..00000000 --- a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.netstandard.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using LaunchDarkly.Xamarin; - -namespace LaunchDarkly.Xamarin.BackgroundAdapter -{ - // This is a stub implementation for .NET Standard where there's no such thing as backgrounding. - - internal class BackgroundAdapter : IPlatformAdapter - { - public void Dispose() - { - } - - public void EnableBackgrounding(IBackgroundingState backgroundingState) - { - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.android.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs similarity index 50% rename from src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.android.cs rename to src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs index ab7853f0..096e0ed4 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundAdapter/BackgroundAdapter.android.cs +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs @@ -3,48 +3,25 @@ using Android.App; using Android.OS; -namespace LaunchDarkly.Xamarin.BackgroundAdapter +namespace LaunchDarkly.Xamarin.BackgroundDetection { - internal class BackgroundAdapter : IPlatformAdapter + internal static partial class BackgroundDetection { private static ActivityLifecycleCallbacks _callbacks; - private Application application; + private static Application _application; - public void EnableBackgrounding(IBackgroundingState backgroundingState) + private static void StartListening() { - if (_callbacks == null) - { - _callbacks = new ActivityLifecycleCallbacks(backgroundingState); - application = (Application)Application.Context; - application.RegisterActivityLifecycleCallbacks(_callbacks); - } - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void _Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - application = null; - _callbacks = null; - - disposedValue = true; - } + _callbacks = new ActivityLifecycleCallbacks(); + _application = (Application)Application.Context; + _application.RegisterActivityLifecycleCallbacks(_callbacks); } - public void Dispose() + private static void StopListening() { - _Dispose(true); - GC.SuppressFinalize(this); + _callbacks = null; + _application = null; } - #endregion private class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks { @@ -65,12 +42,12 @@ public void OnActivityDestroyed(Activity activity) public void OnActivityPaused(Activity activity) { - _backgroundingState.EnterBackgroundAsync(); + BackgroundDetection.UpdateBackgroundMode(true); } public void OnActivityResumed(Activity activity) { - _backgroundingState.ExitBackgroundAsync(); + BackgroundDetection.UpdateBackgroundMode(false); } public void OnActivitySaveInstanceState(Activity activity, Bundle outState) diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs new file mode 100644 index 00000000..59943320 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs @@ -0,0 +1,37 @@ +using System; +using LaunchDarkly.Xamarin; +using UIKit; +using Common.Logging; +using Foundation; + +namespace LaunchDarkly.Xamarin.BackgroundDetection +{ + internal static partial class BackgroundDetection + { + private static NSObject _foregroundHandle; + private static NSObject _backgroundHandle; + private static readonly ILog Log = LogManager.GetLogger(typeof(BackgroundAdapter)); + + private static void StartListening() + { + _foregroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, HandleWillEnterForeground); + _backgroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, HandleWillEnterBackground); + } + + private static void StopListening() + { + _foregroundHandle = null; + _backgroundHandle = null; + } + + private static void HandleWillEnterForeground(NSNotification notification) + { + UpdateBackgroundMode(false); + } + + private static void HandleWillEnterBackground(NSNotification notification) + { + UpdateBackgroundMode(true); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs new file mode 100644 index 00000000..5779eb39 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs @@ -0,0 +1,15 @@ +using System; + +namespace LaunchDarkly.Xamarin.BackgroundDetection +{ + internal static partial class BackgroundDetection + { + private static void PlatformStartListening() + { + } + + private static void PlatformStopListening() + { + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs new file mode 100644 index 00000000..80dfc9c6 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs @@ -0,0 +1,58 @@ +using System; + +namespace LaunchDarkly.Xamarin.BackgroundDetection +{ + internal static partial class BackgroundDetection + { + private static event EventHandler _backgroundModeChanged; + + private static object _backgroundModeChangedHandlersLock = new object(); + + private static bool HasHandlers => _backgroundModeChanged != null && _backgroundModeChanged.GetInvocationList().Length != 0; + + public static event EventHandler BackgroundModeChanged + { + add + { + lock (_backgroundModeChangedHandlersLock) + { + var hadHandlers = HasHandlers; + _backgroundModeChanged += value; + if (!hadHandlers) + { + PlatformStartListening(); + } + } + } + remove + { + lock (_backgroundModeChangedHandlersLock) + { + var hadHandlers = HasHandlers; + _backgroundModeChanged -= value; + if (hadHandlers && !HasHandlers) + { + PlatformStopListening(); + } + } + } + } + + internal static void UpdateBackgroundMode(bool isInBackground) + { + var args = new BackgroundModeChangedEventArgs(isInBackground); + var handlers = _backgroundModeChanged; + handlers?.Invoke(null, args); + } + } + + internal class BackgroundModeChangedEventArgs + { + public bool IsInBackground { get; private set; } + + public BackgroundModeChangedEventArgs(bool isInBackground) + { + IsInBackground = isInBackground; + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs b/src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs deleted file mode 100644 index 88708ddb..00000000 --- a/src/LaunchDarkly.XamarinSdk/IBackgroundingState.shared.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin -{ - /// - /// An interface that is used internally by implementations of - /// to update the state of the LaunchDarkly client in background mode. Application code does not need - /// to interact with this interface. - /// - public interface IBackgroundingState - { - /// - /// Tells the LaunchDarkly client that the application is entering background mode. The client will - /// suspend the regular streaming or polling process, except when - /// is called. - /// - Task EnterBackgroundAsync(); - - /// - /// Tells the LaunchDarkly client that the application is exiting background mode. The client will - /// resume the regular streaming or polling process. - /// - Task ExitBackgroundAsync(); - } -} diff --git a/src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs b/src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs deleted file mode 100644 index ad49c2c1..00000000 --- a/src/LaunchDarkly.XamarinSdk/IPlatformAdapter.shared.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace LaunchDarkly.Xamarin -{ - /// - /// Interface for a component that helps LdClient interact with a specific mobile platform. - /// Currently this is necessary in order to handle features that are not part of the portable - /// Xamarin.Essentials API; in the future it may be handled automatically when you - /// create an LdClient. - /// - /// To obtain an instance of this interface, use the implementation of `PlatformComponents.CreatePlatformAdapter` - /// that is provided by the add-on library for your specific platform (e.g. LaunchDarkly.Xamarin.Android). - /// Then pass the object to - /// when you are building your client configuration. - /// - /// Application code should not call any methods of this interface directly; they are used internally - /// by LdClient. - /// - public interface IPlatformAdapter : IDisposable - { - /// - /// Tells the IPlatformAdapter to start monitoring the foreground/background state of - /// the application, and provides a callback object for it to use when the state changes. - /// - /// An implementation of IBackgroundingState provided by the client - void EnableBackgrounding(IBackgroundingState backgroundingState); - } - - internal class NullPlatformAdapter : IPlatformAdapter - { - public void EnableBackgrounding(IBackgroundingState backgroundingState) { } - - public void Dispose() { } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs index ca33cde8..e5f1cdcc 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs @@ -51,7 +51,6 @@ public sealed class LdClient : ILdMobileClient readonly EventFactory eventFactoryDefault = EventFactory.Default; readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; IFeatureFlagListenerManager flagListenerManager; - IPlatformAdapter platformAdapter; SemaphoreSlim connectionLock; @@ -77,7 +76,6 @@ public sealed class LdClient : ILdMobileClient persister = Factory.CreatePersister(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); - platformAdapter = new LaunchDarkly.Xamarin.BackgroundAdapter.BackgroundAdapter(); User = DecorateUser(user); @@ -89,6 +87,7 @@ public sealed class LdClient : ILdMobileClient eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); SetupConnectionManager(); + BackgroundDetection.BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; } /// @@ -216,34 +215,12 @@ static void CreateInstance(Configuration configuration, User user) Instance = new LdClient(configuration, user); Log.InfoFormat("Initialized LaunchDarkly Client {0}", - Instance.Version); - - TimeSpan? bgPollInterval = null; - if (configuration.EnableBackgroundUpdating) - { - bgPollInterval = configuration.BackgroundPollingInterval; - } - try - { - Instance.platformAdapter.EnableBackgrounding(new LdClientBackgroundingState(Instance)); - } - catch - { - Log.Info("Foreground/Background is only available on iOS and Android"); - } + Instance.Version); } bool StartUpdateProcessor(TimeSpan maxWaitTime) { - var initTask = updateProcessor.Start(); - try - { - return initTask.Wait(maxWaitTime); - } - catch (AggregateException e) - { - throw UnwrapAggregateException(e); - } + return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); } Task StartUpdateProcessorAsync() @@ -463,17 +440,7 @@ public void Flush() /// public void Identify(User user) { - try - { - // Note that we must use Task.Run here, rather than just doing IdentifyAsync(user).Wait(), - // to avoid a deadlock if we are on the main thread. See: - // https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/ - Task.Run(() => IdentifyAsync(user)).Wait(); - } - catch (AggregateException e) - { - throw UnwrapAggregateException(e); - } + AsyncUtils.WaitSafely(() => IdentifyAsync(user)); } /// @@ -553,14 +520,7 @@ void Dispose(bool disposing) { Log.InfoFormat("Shutting down the LaunchDarkly client"); - try - { - platformAdapter.Dispose(); - } - catch(Exception error) - { - Log.Error(error); - } + BackgroundDetection.BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; updateProcessor.Dispose(); eventProcessor.Dispose(); } @@ -585,23 +545,30 @@ public void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener lis public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) { flagListenerManager.UnregisterListener(listener, flagKey); - } - - internal async Task EnterBackgroundAsync() + } + + internal void OnBackgroundModeChanged(object sender, BackgroundDetection.BackgroundModeChangedEventArgs args) { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - if (Config.EnableBackgroundUpdating) - { - await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); - } - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); + AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); } - internal async Task EnterForegroundAsync() + internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundDetection.BackgroundModeChangedEventArgs args) { - ResetProcessorForForeground(); - await RestartUpdateProcessorAsync(Config.PollingInterval); + if (args.IsInBackground) + { + ClearUpdateProcessor(); + Config.IsStreamingEnabled = false; + if (Config.EnableBackgroundUpdating) + { + await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); + } + persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); + } + else + { + ResetProcessorForForeground(); + await RestartUpdateProcessorAsync(Config.PollingInterval); + } } void ResetProcessorForForeground() @@ -614,35 +581,5 @@ void ResetProcessorForForeground() Config.IsStreamingEnabled = true; } } - - private Exception UnwrapAggregateException(AggregateException e) - { - if (e.InnerExceptions.Count == 1) - { - return e.InnerExceptions[0]; - } - return e; - } - } - - // Implementation of IBackgroundingState - this allows us to keep these methods out of the public LdClient API - internal class LdClientBackgroundingState : IBackgroundingState - { - private readonly LdClient _client; - - internal LdClientBackgroundingState(LdClient client) - { - _client = client; - } - - public async Task EnterBackgroundAsync() - { - await _client.EnterBackgroundAsync(); - } - - public async Task ExitBackgroundAsync() - { - await _client.EnterForegroundAsync(); - } } } \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index ae3cbe2a..107db885 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -19,7 +19,7 @@ public static LdClient CreateClient(Configuration config, User user) { lock (ClientInstanceLock) { - LdClient client = LdClient.Init(config, user, TimeSpan.Zero); + LdClient client = LdClient.Init(config, user, TimeSpan.FromSeconds(1)); LdClient.Instance = null; return client; } From 1e91ef2883d6d591489f5859238ca1d34f8a6658 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 15:20:58 -0700 Subject: [PATCH 086/499] misc fixes --- .../BackgroundDetection.android.cs | 11 ++--------- .../BackgroundDetection/BackgroundDetection.ios.cs | 6 ++---- .../Preferences/Preferences.android.cs | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs index 096e0ed4..ad27eb29 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs @@ -10,14 +10,14 @@ internal static partial class BackgroundDetection private static ActivityLifecycleCallbacks _callbacks; private static Application _application; - private static void StartListening() + private static void PlatformStartListening() { _callbacks = new ActivityLifecycleCallbacks(); _application = (Application)Application.Context; _application.RegisterActivityLifecycleCallbacks(_callbacks); } - private static void StopListening() + private static void PlatformStopListening() { _callbacks = null; _application = null; @@ -25,13 +25,6 @@ private static void StopListening() private class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks { - private IBackgroundingState _backgroundingState; - - public ActivityLifecycleCallbacks(IBackgroundingState backgroundingState) - { - _backgroundingState = backgroundingState; - } - public void OnActivityCreated(Activity activity, Bundle savedInstanceState) { } diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs index 59943320..fcd1a1ba 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs @@ -1,7 +1,6 @@ using System; using LaunchDarkly.Xamarin; using UIKit; -using Common.Logging; using Foundation; namespace LaunchDarkly.Xamarin.BackgroundDetection @@ -10,15 +9,14 @@ internal static partial class BackgroundDetection { private static NSObject _foregroundHandle; private static NSObject _backgroundHandle; - private static readonly ILog Log = LogManager.GetLogger(typeof(BackgroundAdapter)); - private static void StartListening() + private static void PlatformStartListening() { _foregroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, HandleWillEnterForeground); _backgroundHandle = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, HandleWillEnterBackground); } - private static void StopListening() + private static void PlatformStopListening() { _foregroundHandle = null; _backgroundHandle = null; diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs index 1e07aec0..1e6f8a7c 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs @@ -84,7 +84,7 @@ static void PlatformSet(string key, string value, string sharedName) } else { - editor.PutString(key, s); + editor.PutString(key, value); } editor.Apply(); } From 3e0b5b811c94b62bb6bbec85baaa1c87f11fe4e6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 15:36:50 -0700 Subject: [PATCH 087/499] basic components of Xamarin build on Mac --- .circleci/config.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index da05f2db..36d24c06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,11 +1,14 @@ version: 2 + workflows: version: 2 test: jobs: - - test-2.0 + - test-netstandard2.0 + - test-android + jobs: - test-2.0: + test-netstandard2.0: docker: - image: microsoft/dotnet:2.0-sdk-jessie steps: @@ -13,3 +16,20 @@ jobs: - run: dotnet restore - run: dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 - run: dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 + + test-android: + macos: + xcode: '10.1.0' + + steps: + - run: + name: Set up package source for Xamarin tools + command: brew install caskroom/cask/brew-cask + + - run: + name: Install Xamarin tools + command: brew cask install xamarin xamarin-jdk xamarin-ios xamarin-studio + + - run: + name: Build SDK + command: msbuild From c701fd884f8013e2859e3ff1a253a0aa6d658707 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 15:45:03 -0700 Subject: [PATCH 088/499] misc fixes --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36d24c06..d4db86e5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ jobs: test-android: macos: - xcode: '10.1.0' + xcode: "10.2.1" steps: - run: @@ -28,7 +28,7 @@ jobs: - run: name: Install Xamarin tools - command: brew cask install xamarin xamarin-jdk xamarin-ios xamarin-studio + command: brew cask install xamarin xamarin-android xamarin-ios - run: name: Build SDK From 531633d092fb60f713e355705fd516d5b4ccebe5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 16:07:34 -0700 Subject: [PATCH 089/499] misc fixes --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d4db86e5..cbd925b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,10 @@ jobs: - run: name: Install Xamarin tools - command: brew cask install xamarin xamarin-android xamarin-ios + command: brew cask install xamarin xamarin-android xamarin-ios && brew install mono + # Note, "mono" provides the msbuild CLI tool + + - checkout - run: name: Build SDK From c763ea98a05fdead1340ad2cf36b1bc37c5330aa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 16:14:06 -0700 Subject: [PATCH 090/499] fix var name --- src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs index 1e07aec0..1e6f8a7c 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs @@ -84,7 +84,7 @@ static void PlatformSet(string key, string value, string sharedName) } else { - editor.PutString(key, s); + editor.PutString(key, value); } editor.Apply(); } From 71f9979398269e2036c8190e6fc6848ef3948f6f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 7 Jun 2019 16:14:53 -0700 Subject: [PATCH 091/499] restore packages --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cbd925b3..17174325 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,4 +35,4 @@ jobs: - run: name: Build SDK - command: msbuild + command: msbuild /restore From 42758670c65c793029d020136f5413f195f92453 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Jun 2019 13:35:06 -0700 Subject: [PATCH 092/499] install Android SDK components --- .circleci/config.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 17174325..59d2d96d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,9 @@ jobs: macos: xcode: "10.2.1" + environment: + ANDROID_SDK_ROOT: "/usr/local/share/android-sdk" + steps: - run: name: Set up package source for Xamarin tools @@ -28,9 +31,13 @@ jobs: - run: name: Install Xamarin tools - command: brew cask install xamarin xamarin-android xamarin-ios && brew install mono + command: brew cask install xamarin xamarin-android xamarin-ios android-sdk && brew install mono # Note, "mono" provides the msbuild CLI tool + - run: + name: Install Android SDK components + command: yes | $ANDROID_SDK_ROOT/tools/bin/sdkmanager "system-images;android-24;default;armeabi-v7a" + - checkout - run: From 0177b39fa693027086a38967bd0ce6ee840f6ac0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Jun 2019 13:40:55 -0700 Subject: [PATCH 093/499] clarifying comment --- .../BackgroundDetection/BackgroundDetection.netstandard.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs index 5779eb39..0aeef6bf 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs @@ -2,6 +2,10 @@ namespace LaunchDarkly.Xamarin.BackgroundDetection { + // This code is not from Xamarin Essentials, though it implements the same abstraction. It is a stub + // that does nothing, since in .NET Standard there is no notion of an application being in the + // background or the foreground. + internal static partial class BackgroundDetection { private static void PlatformStartListening() From 75c4b8fdea82d8bff39eff55a795aa7408318381 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Jun 2019 13:42:28 -0700 Subject: [PATCH 094/499] remove unnecessary checks for AggregateException --- .../AsyncUtils.shared.cs | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs b/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs index d76b72e7..cf4ca1a6 100644 --- a/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs @@ -19,17 +19,11 @@ internal static class AsyncUtils internal static void WaitSafely(Func taskFn) { - try - { - _taskFactory.StartNew(taskFn) - .Unwrap() - .GetAwaiter() - .GetResult(); - } - catch (AggregateException e) - { - throw UnwrapAggregateException(e); - } + _taskFactory.StartNew(taskFn) + .Unwrap() + .GetAwaiter() + .GetResult(); + // Note, GetResult does not throw AggregateException so we don't need to post-process exceptions } internal static bool WaitSafely(Func taskFn, TimeSpan timeout) @@ -48,17 +42,10 @@ internal static bool WaitSafely(Func taskFn, TimeSpan timeout) internal static T WaitSafely(Func> taskFn) { - try - { - return _taskFactory.StartNew(taskFn) - .Unwrap() - .GetAwaiter() - .GetResult(); - } - catch (AggregateException e) - { - throw UnwrapAggregateException(e); - } + return _taskFactory.StartNew(taskFn) + .Unwrap() + .GetAwaiter() + .GetResult(); } private static Exception UnwrapAggregateException(AggregateException e) From 490889aceb4437feefa15f15ee0369b219e34e4c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 10 Jun 2019 18:17:53 -0700 Subject: [PATCH 095/499] add config option for whether to persist flags --- .../Configuration.shared.cs | 97 ++++++++----------- ....cs => DefaultPersistentStorage.shared.cs} | 2 +- src/LaunchDarkly.XamarinSdk/Factory.shared.cs | 56 +++++------ .../FlagCacheManager.shared.cs | 5 +- .../IDeviceInfo.shared.cs | 2 +- .../IMobileConfiguration.shared.cs | 16 ++- .../IPersistentStorage.shared.cs | 14 +++ .../ISimplePersistance.shared.cs | 8 -- .../IUserFlagCache.shared.cs | 6 ++ .../LdClient.shared.cs | 6 +- .../SimpleInMemoryPersistance.shared.cs | 21 ---- .../UserFlagDeviceCache.shared.cs | 4 +- .../LdClientTests.cs | 59 ++++++++++- .../MobileStreamingProcessorTests.cs | 2 +- .../MockComponents.cs | 35 ++++++- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 4 +- 16 files changed, 208 insertions(+), 129 deletions(-) rename src/LaunchDarkly.XamarinSdk/{SimpleMobileDevicePersistance.shared.cs => DefaultPersistentStorage.shared.cs} (83%) create mode 100644 src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs b/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs index 04c128c2..f65d032e 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs @@ -125,12 +125,14 @@ public class Configuration : IMobileConfiguration public bool EnableBackgroundUpdating { get; internal set; } /// public bool UseReport { get; internal set; } + /// + public bool PersistFlagValues { get; internal set; } internal IFlagCacheManager FlagCacheManager { get; set; } internal IConnectionManager ConnectionManager { get; set; } internal IEventProcessor EventProcessor { get; set; } - internal IMobileUpdateProcessor MobileUpdateProcessor { get; set; } - internal ISimplePersistance Persister { get; set; } + internal Func UpdateProcessorFactory { get; set; } + internal IPersistentStorage PersistentStorage { get; set; } internal IDeviceInfo DeviceInfo { get; set; } internal IFeatureFlagListenerManager FeatureFlagListenerManager { get; set; } @@ -229,7 +231,8 @@ public static Configuration Default(string mobileKey) UserKeysFlushInterval = DefaultUserKeysFlushInterval, InlineUsersInEvents = false, EnableBackgroundUpdating = true, - UseReport = true + UseReport = true, + PersistFlagValues = true }; return defaultConfiguration; @@ -541,18 +544,6 @@ public static Configuration WithInlineUsersInEvents(this Configuration configura return configuration; } - /// - /// Sets the IFlagCacheManager instance, used internally for stubbing mock instances. - /// - /// Configuration. - /// FlagCacheManager. - /// the same Configuration instance - internal static Configuration WithFlagCacheManager(this Configuration configuration, IFlagCacheManager flagCacheManager) - { - configuration.FlagCacheManager = flagCacheManager; - return configuration; - } - /// /// Sets the IConnectionManager instance, used internally for stubbing mock instances. /// @@ -606,79 +597,77 @@ public static Configuration WithEvaluationReasons(this Configuration configurati } /// - /// Sets the IMobileUpdateProcessor instance, used internally for stubbing mock instances. + /// Sets whether to enable background polling. /// /// Configuration. - /// Mobile update processor. + /// If set to true enable background updating. /// the same Configuration instance - internal static Configuration WithUpdateProcessor(this Configuration configuration, IMobileUpdateProcessor mobileUpdateProcessor) + public static Configuration WithEnableBackgroundUpdating(this Configuration configuration, bool enableBackgroundUpdating) { - configuration.MobileUpdateProcessor = mobileUpdateProcessor; + configuration.EnableBackgroundUpdating = enableBackgroundUpdating; return configuration; } /// - /// Sets the ISimplePersistance instance, used internally for stubbing mock instances. + /// Sets the interval for background polling. /// /// Configuration. - /// Persister. + /// Background polling internal. /// the same Configuration instance - public static Configuration WithPersister(this Configuration configuration, ISimplePersistance persister) + public static Configuration WithBackgroundPollingInterval(this Configuration configuration, TimeSpan backgroundPollingInternal) { - configuration.Persister = persister; + if (backgroundPollingInternal.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) + { + Log.WarnFormat("BackgroundPollingInterval cannot be less than the default of {0}.", Configuration.MinimumBackgroundPollingInterval); + backgroundPollingInternal = Configuration.MinimumBackgroundPollingInterval; + } + configuration.BackgroundPollingInterval = backgroundPollingInternal; return configuration; } /// - /// Sets the IDeviceInfo instance, used internally for stubbing mock instances. + /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be + /// immediately available the next time the SDK is started for the same user. The default is . /// - /// Configuration. - /// Device info. + /// the configuration + /// true or false /// the same Configuration instance - public static Configuration WithDeviceInfo(this Configuration configuration, IDeviceInfo deviceInfo) + /// + public static Configuration WithPersistFlagValues(this Configuration configuration, bool persistFlagValues) + { + configuration.PersistFlagValues = persistFlagValues; + return configuration; + } + + // The following properties can only be set internally. They are used for providing stub implementations in unit tests. + + internal static Configuration WithDeviceInfo(this Configuration configuration, IDeviceInfo deviceInfo) { configuration.DeviceInfo = deviceInfo; return configuration; } - /// - /// Sets the IFeatureFlagListenerManager instance, used internally for stubbing mock instances. - /// - /// Configuration. - /// Feature flag listener manager. - /// the same Configuration instance internal static Configuration WithFeatureFlagListenerManager(this Configuration configuration, IFeatureFlagListenerManager featureFlagListenerManager) { configuration.FeatureFlagListenerManager = featureFlagListenerManager; return configuration; } - /// - /// Sets whether to enable background polling. - /// - /// Configuration. - /// If set to true enable background updating. - /// the same Configuration instance - public static Configuration WithEnableBackgroundUpdating(this Configuration configuration, bool enableBackgroundUpdating) + internal static Configuration WithFlagCacheManager(this Configuration configuration, IFlagCacheManager flagCacheManager) { - configuration.EnableBackgroundUpdating = enableBackgroundUpdating; + configuration.FlagCacheManager = flagCacheManager; return configuration; } - /// - /// Sets the interval for background polling. - /// - /// Configuration. - /// Background polling internal. - /// the same Configuration instance - public static Configuration WithBackgroundPollingInterval(this Configuration configuration, TimeSpan backgroundPollingInternal) + internal static Configuration WithPersistentStorage(this Configuration configuration, IPersistentStorage persistentStorage) { - if (backgroundPollingInternal.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) - { - Log.WarnFormat("BackgroundPollingInterval cannot be less than the default of {0}.", Configuration.MinimumBackgroundPollingInterval); - backgroundPollingInternal = Configuration.MinimumBackgroundPollingInterval; - } - configuration.BackgroundPollingInterval = backgroundPollingInternal; + configuration.PersistentStorage = persistentStorage; + return configuration; + } + + internal static Configuration WithUpdateProcessorFactory(this Configuration configuration, Func factory) + { + configuration.UpdateProcessorFactory = factory; return configuration; } } diff --git a/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs similarity index 83% rename from src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs rename to src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs index e373d732..10a69193 100644 --- a/src/LaunchDarkly.XamarinSdk/SimpleMobileDevicePersistance.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs @@ -2,7 +2,7 @@ namespace LaunchDarkly.Xamarin { - internal class SimpleMobileDevicePersistance : ISimplePersistance + internal class DefaultPersistentStorage : IPersistentStorage { public void Save(string key, string value) { diff --git a/src/LaunchDarkly.XamarinSdk/Factory.shared.cs b/src/LaunchDarkly.XamarinSdk/Factory.shared.cs index 3dac0518..8815fe43 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.shared.cs @@ -11,7 +11,7 @@ internal static class Factory private static readonly ILog Log = LogManager.GetLogger(typeof(Factory)); internal static IFlagCacheManager CreateFlagCacheManager(Configuration configuration, - ISimplePersistance persister, + IPersistentStorage persister, IFlagListenerUpdater updater, User user) { @@ -22,7 +22,7 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura else { var inMemoryCache = new UserFlagInMemoryCache(); - var deviceCache = new UserFlagDeviceCache(persister); + var deviceCache = configuration.PersistFlagValues ? new UserFlagDeviceCache(persister) as IUserFlagCache : new NullUserFlagCache(); return new FlagCacheManager(inMemoryCache, deviceCache, updater, user); } } @@ -32,38 +32,32 @@ internal static IConnectionManager CreateConnectionManager(Configuration configu return configuration.ConnectionManager ?? new MobileConnectionManager(); } - internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, - User user, - IFlagCacheManager flagCacheManager, - TimeSpan pollingInterval, - StreamManager.EventSourceCreator source = null) - { - if (configuration.MobileUpdateProcessor != null) + internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, User user, IFlagCacheManager flagCacheManager, TimeSpan? overridePollingInterval) + { + if (configuration.Offline) + { + Log.InfoFormat("Starting LaunchDarkly client in offline mode"); + return new NullUpdateProcessor(); + } + + if (configuration.UpdateProcessorFactory != null) { - return configuration.MobileUpdateProcessor; + return configuration.UpdateProcessorFactory(configuration, flagCacheManager, user); } - if (configuration.Offline) - { - Log.InfoFormat("Starting LaunchDarkly client in offline mode"); - return new NullUpdateProcessor(); + if (configuration.IsStreamingEnabled) + { + return new MobileStreamingProcessor(configuration, flagCacheManager, user, null); } - - if (configuration.IsStreamingEnabled) - { - return new MobileStreamingProcessor(configuration, - flagCacheManager, - user, source); - } - else - { - var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); - return new MobilePollingProcessor(featureFlagRequestor, - flagCacheManager, - user, - pollingInterval); + else + { + var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); + return new MobilePollingProcessor(featureFlagRequestor, + flagCacheManager, + user, + overridePollingInterval ?? configuration.PollingInterval); } - } + } internal static IEventProcessor CreateEventProcessor(Configuration configuration) { @@ -80,9 +74,9 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration return new DefaultEventProcessor(configuration, null, httpClient, Constants.EVENTS_PATH); } - internal static ISimplePersistance CreatePersister(Configuration configuration) + internal static IPersistentStorage CreatePersistentStorage(Configuration configuration) { - return configuration.Persister ?? new SimpleMobileDevicePersistance(); + return configuration.PersistentStorage ?? new DefaultPersistentStorage(); } internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs index 92e0acda..d12680f9 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs @@ -22,7 +22,10 @@ public FlagCacheManager(IUserFlagCache inMemoryCache, this.flagListenerUpdater = flagListenerUpdater; var flagsFromDevice = deviceCache.RetrieveFlags(user); - inMemoryCache.CacheFlagsForUser(flagsFromDevice, user); + if (flagsFromDevice != null) + { + inMemoryCache.CacheFlagsForUser(flagsFromDevice, user); + } } public IDictionary FlagsForUser(User user) diff --git a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs index 5b07db18..53bbad27 100644 --- a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs @@ -1,6 +1,6 @@ namespace LaunchDarkly.Xamarin { - public interface IDeviceInfo + internal interface IDeviceInfo { string UniqueDeviceId(); } diff --git a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs index 8a98fde0..8fb923a8 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs @@ -50,6 +50,20 @@ public interface IMobileConfiguration : IBaseConfiguration /// to true. /// /// true if evaluation reasons are desired. - bool EvaluationReasons { get; } + bool EvaluationReasons { get; } + + /// + /// True if the SDK should save flag values for each user in persistent storage, so they will be + /// immediately available the next time the SDK is started for the same user. This is true by + /// default; set it to false to disable this behavior. + /// + /// + /// The implementation of persistent storage depends on the target platform. In Android and iOS, it + /// uses the standard user preferences mechanism. In .NET Standard, it uses the IsolatedStorageFile + /// API, which stores file data under the current account's home directory at + /// ~/.local/share/IsolateStorage/. + /// + /// true if flag values should be stored locally (the default). + bool PersistFlagValues { get; } } } diff --git a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs b/src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs new file mode 100644 index 00000000..11a2b8d8 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs @@ -0,0 +1,14 @@ +namespace LaunchDarkly.Xamarin +{ + internal interface IPersistentStorage + { + string GetValue(string key); + void Save(string key, string value); + } + + internal class NullPersistentStorage : IPersistentStorage + { + public string GetValue(string key) => null; + public void Save(string key, string value) { } + } +} \ No newline at end of file diff --git a/src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs deleted file mode 100644 index 6681c06e..00000000 --- a/src/LaunchDarkly.XamarinSdk/ISimplePersistance.shared.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LaunchDarkly.Xamarin -{ - public interface ISimplePersistance - { - string GetValue(string key); - void Save(string key, string value); - } -} \ No newline at end of file diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs index 8994715b..99211ba4 100644 --- a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs @@ -8,4 +8,10 @@ internal interface IUserFlagCache void CacheFlagsForUser(IDictionary flags, User user); IDictionary RetrieveFlags(User user); } + + internal class NullUserFlagCache : IUserFlagCache + { + public void CacheFlagsForUser(IDictionary flags, User user) { } + public IDictionary RetrieveFlags(User user) => null; + } } diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs index e5f1cdcc..022d7a1f 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs @@ -46,7 +46,7 @@ public sealed class LdClient : ILdMobileClient IConnectionManager connectionManager; IMobileUpdateProcessor updateProcessor; IEventProcessor eventProcessor; - ISimplePersistance persister; + IPersistentStorage persister; IDeviceInfo deviceInfo; readonly EventFactory eventFactoryDefault = EventFactory.Default; readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; @@ -73,7 +73,7 @@ public sealed class LdClient : ILdMobileClient connectionLock = new SemaphoreSlim(1, 1); - persister = Factory.CreatePersister(configuration); + persister = Factory.CreatePersistentStorage(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); @@ -81,7 +81,7 @@ public sealed class LdClient : ILdMobileClient flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, configuration.PollingInterval); + updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null); eventProcessor = Factory.CreateEventProcessor(configuration); eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); diff --git a/src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs b/src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs deleted file mode 100644 index 5f6d81c7..00000000 --- a/src/LaunchDarkly.XamarinSdk/SimpleInMemoryPersistance.shared.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace LaunchDarkly.Xamarin -{ - public class SimpleInMemoryPersistance : ISimplePersistance - { - IDictionary map = new Dictionary(); - - public string GetValue(string key) - { - string value = null; - map.TryGetValue(key, out value); - return value; - } - - public void Save(string key, string value) - { - map[key] = value; - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs index b3fc8ff3..29e616e6 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs @@ -10,9 +10,9 @@ namespace LaunchDarkly.Xamarin internal class UserFlagDeviceCache : IUserFlagCache { private static readonly ILog Log = LogManager.GetLogger(typeof(UserFlagDeviceCache)); - private readonly ISimplePersistance persister; + private readonly IPersistentStorage persister; - public UserFlagDeviceCache(ISimplePersistance persister) + public UserFlagDeviceCache(IPersistentStorage persister) { this.persister = persister; } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 46cdc271..d172cf6a 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -114,12 +117,14 @@ public void ConnectionManagerShouldKnowIfOnlineOrNot() [Fact] public void ConnectionChangeShouldStopUpdateProcessor() { - using (var client = Client()) + var mockUpdateProc = new MockPollingProcessor(null); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithUpdateProcessorFactory(mockUpdateProc.AsFactory()); + using (var client = TestUtil.CreateClient(config, simpleUser)) { var connMgr = client.Config.ConnectionManager as MockConnectionManager; connMgr.ConnectionChanged += (bool obj) => client.Online = obj; connMgr.Connect(false); - var mockUpdateProc = client.Config.MobileUpdateProcessor as MockPollingProcessor; Assert.False(mockUpdateProc.IsRunning); } } @@ -245,5 +250,55 @@ public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerMan Assert.NotEqual(12, listener.FeatureFlags["user2-flag"]); } } + + [Fact] + public void FlagsAreLoadedFromPersistentStorageByDefault() + { + var storage = new MockPersistentStorage(); + var flagsJson = "{\"flag\": {\"value\": 100}}"; + storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithOffline(true) + .WithPersistentStorage(storage) + .WithFlagCacheManager(null); // use actual cache logic, not mock component (even though persistence layer is a mock) + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.Equal(100, client.IntVariation("flag", 99)); + } + } + + [Fact] + public void FlagsAreNotLoadedFromPersistentStorageIfPersistFlagValuesIsFalse() + { + var storage = new MockPersistentStorage(); + var flagsJson = "{\"flag\": {\"value\": 100}}"; + storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .WithOffline(true) + .WithPersistFlagValues(false) + .WithPersistentStorage(storage) + .WithFlagCacheManager(null); // use actual cache logic, not mock component (even though persistence layer is a mock) + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.Equal(99, client.IntVariation("flag", 99)); // returns default value + } + } + + [Fact] + public void FlagsAreSavedToPersistentStorageByDefault() + { + var storage = new MockPersistentStorage(); + var flagsJson = "{\"flag\": {\"value\": 100}}"; + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, flagsJson) + .WithPersistentStorage(storage) + .WithFlagCacheManager(null) + .WithUpdateProcessorFactory(MockPollingProcessor.Factory(flagsJson)); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + var storedJson = storage.GetValue(Constants.FLAGS_KEY_PREFIX + simpleUser.Key); + var flags = JsonConvert.DeserializeObject>(storedJson); + Assert.Equal(new JValue(100), flags["flag"].value); + } + } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index c61fc10e..03d4db6d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -41,7 +41,7 @@ public MobileStreamingProcessorTests() private IMobileUpdateProcessor MobileStreamingProcessorStarted() { - var processor = Factory.CreateUpdateProcessor(config, user, mockFlagCacheMgr, TimeSpan.FromMinutes(5), eventSourceFactory.Create()); + IMobileUpdateProcessor processor = new MobileStreamingProcessor(config, mockFlagCacheMgr, user, eventSourceFactory.Create()); processor.Start(); return processor; } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 83645b1f..69f96cd0 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LaunchDarkly.Client; +using Newtonsoft.Json; namespace LaunchDarkly.Xamarin.Tests { @@ -133,7 +134,7 @@ public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user } } - internal class MockPersister : ISimplePersistance + internal class MockPersistentStorage : IPersistentStorage { private IDictionary map = new Dictionary(); @@ -153,6 +154,34 @@ public void Save(string key, string value) internal class MockPollingProcessor : IMobileUpdateProcessor { + private IFlagCacheManager _cacheManager; + private User _user; + private string _flagsJson; + + public MockPollingProcessor(string flagsJson) : this(null, null, flagsJson) { } + + private MockPollingProcessor(IFlagCacheManager cacheManager, User user, string flagsJson) + { + _cacheManager = cacheManager; + _user = user; + _flagsJson = flagsJson; + } + + public static Func Factory(string flagsJson) + { + return (config, manager, user) => new MockPollingProcessor(manager, user, flagsJson); + } + + public Func AsFactory() + { + return (config, manager, user) => + { + _cacheManager = manager; + _user = user; + return this; + }; + } + public bool IsRunning { get; @@ -172,6 +201,10 @@ public bool Initialized() public Task Start() { IsRunning = true; + if (_cacheManager != null && _flagsJson != null) + { + _cacheManager.CacheFlagsFromService(JsonConvert.DeserializeObject>(_flagsJson), _user); + } return Task.FromResult(true); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 107db885..71d8cb13 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -70,8 +70,8 @@ public static Configuration ConfigWithFlagsJson(User user, string appKey, string .WithFlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) .WithConnectionManager(new MockConnectionManager(true)) .WithEventProcessor(new MockEventProcessor()) - .WithUpdateProcessor(new MockPollingProcessor()) - .WithPersister(new MockPersister()) + .WithUpdateProcessorFactory(MockPollingProcessor.Factory(null)) + .WithPersistentStorage(new MockPersistentStorage()) .WithDeviceInfo(new MockDeviceInfo("")) .WithFeatureFlagListenerManager(new FeatureFlagListenerManager()); return configuration; From 7ef51d9dd37c281a4b458ccead39ad3d447a1e5d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 13 Jun 2019 13:22:31 -0700 Subject: [PATCH 096/499] suppress spurious sdkmanager error --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 59d2d96d..848a4c81 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,8 @@ jobs: - run: name: Install Android SDK components - command: yes | $ANDROID_SDK_ROOT/tools/bin/sdkmanager "system-images;android-24;default;armeabi-v7a" + command: yes | $ANDROID_SDK_ROOT/tools/bin/sdkmanager "system-images;android-24;default;armeabi-v7a" || true + # Note, "|| true" is because sdkmanager is known to return a non-zero exit code (141) when there's no error - checkout From 6c80e5a7610c3777c0fe3fbc5068bdeca6e6298f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 14:57:47 -0700 Subject: [PATCH 097/499] split out Android job --- .circleci/config.yml | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 848a4c81..ceed00f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,12 +18,39 @@ jobs: - run: dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 test-android: + docker: + - image: circleci/android:api-28 + + steps: + - checkout + + - run: sdkmanager "system-images;android-24;default;armeabi-v7a" || true + - run: sdkmanager --licenses + + - run: sudo apt install apt-transport-https dirmngr gnupg ca-certificates && sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update + - run: echo y | sudo apt install mono-devel nuget libzip4 + + - run: nuget restore + + - restore_cache: + key: xamarin-android-cache-v9-2-99-172 + - run: chmod +x scripts/check_xamarin_android_cache.sh && ./scripts/check_xamarin_android_cache.sh + - save_cache: + key: xamarin-android-cache-v9-2-99-172 + paths: + - ~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release + + - run: sudo mkdir "/usr/lib/xamarin.android" && sudo mkdir "/usr/lib/mono/xbuild/Xamarin/" + - run: cd ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release && sudo cp -a "bin/Debug/lib/xamarin.android/." "/usr/lib/xamarin.android/" + - run: rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" + - run: sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" && sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" + + - run: msbuild /restorer /p:TargetFramework=MonoAndroid81 + + test-ios: macos: xcode: "10.2.1" - environment: - ANDROID_SDK_ROOT: "/usr/local/share/android-sdk" - steps: - run: name: Set up package source for Xamarin tools @@ -31,16 +58,11 @@ jobs: - run: name: Install Xamarin tools - command: brew cask install xamarin xamarin-android xamarin-ios android-sdk && brew install mono + command: brew cask install xamarin xamarin-ios && brew install mono # Note, "mono" provides the msbuild CLI tool - - run: - name: Install Android SDK components - command: yes | $ANDROID_SDK_ROOT/tools/bin/sdkmanager "system-images;android-24;default;armeabi-v7a" || true - # Note, "|| true" is because sdkmanager is known to return a non-zero exit code (141) when there's no error - - checkout - run: name: Build SDK - command: msbuild /restore + command: msbuild /restore /p:TargetFramework=Xamarin.iOS10 From 6cc44a96ded25ca101249382face1e394df7d428 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 15:28:02 -0700 Subject: [PATCH 098/499] add missing script --- .circleci/config.yml | 1 + scripts/check_xamarin_android_cache.sh | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 scripts/check_xamarin_android_cache.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index ceed00f9..60513c5d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,7 @@ workflows: jobs: - test-netstandard2.0 - test-android + - test-ios jobs: test-netstandard2.0: diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh new file mode 100644 index 00000000..6aa443f6 --- /dev/null +++ b/scripts/check_xamarin_android_cache.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# used only for CI build + +if [ -f "~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then + echo "Xamarin Android cache exists" +else + wget https://jenkins.mono-project.com/view/Xamarin.Android/job/xamarin-android-linux/lastSuccessfulBuild/Azure/processDownloadRequest/xamarin-android/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 + tar xjf ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 + echo "Downloaded Xamarin Android from Mono Jenkins" +fi From 75e5bfba4e1cfe0d79f0a5a45d7e5eabd9bce203 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 15:54:54 -0700 Subject: [PATCH 099/499] build only main project --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 60513c5d..c150129d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,4 +66,4 @@ jobs: - run: name: Build SDK - command: msbuild /restore /p:TargetFramework=Xamarin.iOS10 + command: msbuild /restore /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj From 69dce011c565033ec69f75a399120a1e336500b4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 15:55:38 -0700 Subject: [PATCH 100/499] fix Android build --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c150129d..9e73ccac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,7 +46,9 @@ jobs: - run: rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - run: sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" && sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - - run: msbuild /restorer /p:TargetFramework=MonoAndroid81 + - run: + name: Build SDK + command: msbuild /restore /p:TargetFramework=MonoAndroid81 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj test-ios: macos: From 1f1f1c99d04f89104f50a44846b87f84b3ec42c8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 16:09:39 -0700 Subject: [PATCH 101/499] make script executable --- scripts/check_xamarin_android_cache.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/check_xamarin_android_cache.sh diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh old mode 100644 new mode 100755 From 9591da7fe1e951d2978c93fc4859e9f4111f60ff Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 16:10:01 -0700 Subject: [PATCH 102/499] make script executable --- .circleci/config.yml | 2 +- scripts/check_xamarin_android_cache.sh | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 scripts/check_xamarin_android_cache.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e73ccac..875b472e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ jobs: - restore_cache: key: xamarin-android-cache-v9-2-99-172 - - run: chmod +x scripts/check_xamarin_android_cache.sh && ./scripts/check_xamarin_android_cache.sh + - run: ./scripts/check_xamarin_android_cache.sh - save_cache: key: xamarin-android-cache-v9-2-99-172 paths: diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh old mode 100755 new mode 100644 From 2cfb3783444def6ee0321b55e8c79f64561c87ac Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 16:49:29 -0700 Subject: [PATCH 103/499] try Android 27 image --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 875b472e..e49cf9e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: test-android: docker: - - image: circleci/android:api-28 + - image: circleci/android:api-27 steps: - checkout From 1215254d6d0a763172d1fdd09f3274c15f714ff9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 18 Jun 2019 18:02:05 -0700 Subject: [PATCH 104/499] fix script permission --- scripts/check_xamarin_android_cache.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/check_xamarin_android_cache.sh diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh old mode 100644 new mode 100755 From 9b8719ec63701c0ccb6b19e04977f7031cf66a54 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 19 Jun 2019 11:52:20 -0700 Subject: [PATCH 105/499] cached data is a directory, not a file --- scripts/check_xamarin_android_cache.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh index 6aa443f6..e25f02a4 100755 --- a/scripts/check_xamarin_android_cache.sh +++ b/scripts/check_xamarin_android_cache.sh @@ -2,7 +2,7 @@ # used only for CI build -if [ -f "~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then +if [ -e "~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then echo "Xamarin Android cache exists" else wget https://jenkins.mono-project.com/view/Xamarin.Android/job/xamarin-android-linux/lastSuccessfulBuild/Azure/processDownloadRequest/xamarin-android/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 From 75422e2226311c398b8c5c80942bcbdba63fac80 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 19 Jun 2019 13:08:17 -0700 Subject: [PATCH 106/499] fix path in if test --- scripts/check_xamarin_android_cache.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh index e25f02a4..41eae00a 100755 --- a/scripts/check_xamarin_android_cache.sh +++ b/scripts/check_xamarin_android_cache.sh @@ -2,7 +2,7 @@ # used only for CI build -if [ -e "~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then +if [ -e "xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then echo "Xamarin Android cache exists" else wget https://jenkins.mono-project.com/view/Xamarin.Android/job/xamarin-android-linux/lastSuccessfulBuild/Azure/processDownloadRequest/xamarin-android/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 From 6c05c2ef702b6c433ecfac47225ed236a8d0748e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sun, 23 Jun 2019 15:47:13 -0700 Subject: [PATCH 107/499] tests of LdClient with actual HTTP, + misc test cleanup --- .circleci/config.yml | 2 + .../FeatureFlagRequestor.shared.cs | 2 - .../LdClient.shared.cs | 19 +- .../MobilePollingProcessor.shared.cs | 3 +- .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 49 +++ .../ConfigurationTest.cs | 3 +- .../FeatureFlagListenerTests.cs | 5 +- .../FeatureFlagRequestorTests.cs | 127 +++++++ .../FeatureFlagTests.cs | 5 +- .../FlagCacheManagerTests.cs | 2 +- .../LDClientEndToEndTests.cs | 341 ++++++++++++++++++ .../LaunchDarkly.XamarinSdk.Tests.csproj | 1 + .../LdClientEvaluationTests.cs | 8 +- .../LdClientEventTests.cs | 2 +- .../LdClientTests.cs | 24 +- .../LaunchDarkly.XamarinSdk.Tests/LogSink.cs | 73 ++++ .../MobilePollingProcessorTests.cs | 2 +- .../MobileStreamingProcessorTests.cs | 4 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 24 +- .../UserFlagCacheTests.cs | 2 +- .../WireMockExtensions.cs | 43 +++ 21 files changed, 685 insertions(+), 56 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs diff --git a/.circleci/config.yml b/.circleci/config.yml index e49cf9e7..4de92fef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,6 +12,8 @@ jobs: test-netstandard2.0: docker: - image: microsoft/dotnet:2.0-sdk-jessie + env: + - ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests steps: - checkout - run: dotnet restore diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs index 10174600..832b90b2 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -9,7 +8,6 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs index 022d7a1f..9dedb090 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs @@ -193,29 +193,30 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static Task InitAsync(Configuration config, User user) { - CreateInstance(config, user); + var c = CreateInstance(config, user); - if (Instance.Online) + if (c.Online) { - Task t = Instance.StartUpdateProcessorAsync(); - return t.ContinueWith((result) => Instance); + Task t = c.StartUpdateProcessorAsync(); + return t.ContinueWith((result) => c); } else { - return Task.FromResult(Instance); + return Task.FromResult(c); } } - static void CreateInstance(Configuration configuration, User user) + static LdClient CreateInstance(Configuration configuration, User user) { if (Instance != null) { throw new Exception("LdClient instance already exists."); } - Instance = new LdClient(configuration, user); - Log.InfoFormat("Initialized LaunchDarkly Client {0}", - Instance.Version); + var c = new LdClient(configuration, user); + Instance = c; + Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); + return c; } bool StartUpdateProcessor(TimeSpan maxWaitTime) diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs index 9119e367..44668ae5 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs @@ -42,8 +42,7 @@ Task IMobileUpdateProcessor.Start() if (pollingInterval.Equals(TimeSpan.Zero)) throw new Exception("Timespan for polling can't be zero"); - Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0} milliseconds", - pollingInterval); + Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0}", pollingInterval); Task.Run(() => UpdateTaskLoopAsync()); return _startTask.Task; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs new file mode 100644 index 00000000..09b1acc5 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Common.Logging; +using WireMock.Server; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + [Collection("serialize all tests")] + public class BaseTest + { + public BaseTest() + { + LogManager.Adapter = new LogSinkFactoryAdapter(); + TestUtil.ClearClient(); + } + + ~BaseTest() + { + TestUtil.ClearClient(); + } + + protected void WithServer(Action a) + { + var s = FluentMockServer.Start(); + try + { + a(s); + } + finally + { + s.Stop(); + } + } + + protected async Task WithServerAsync(Func a) + { + var s = FluentMockServer.Start(); + try + { + await a(s); + } + finally + { + s.Stop(); + } + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index 8f5b4f8d..641a3c22 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -1,11 +1,10 @@ using System; using System.Net.Http; -using LaunchDarkly.Client; using Xunit; namespace LaunchDarkly.Xamarin.Tests { - public class ConfigurationTest + public class ConfigurationTest : BaseTest { [Fact] public void CanOverrideConfiguration() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs index cf0d5b26..d20f2d9b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests { - public class FeatureFlagListenerTests + public class FeatureFlagListenerTests : BaseTest { private const string INT_FLAG = "int-flag"; private const string DOUBLE_FLAG = "double-flag"; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs new file mode 100644 index 00000000..fd07d09e --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -0,0 +1,127 @@ +using System.Threading.Tasks; +using LaunchDarkly.Client; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + // End-to-end tests of this component against an embedded HTTP server. + public class FeatureFlagRequestorTests : BaseTest + { + private const string _mobileKey = "FAKE_KEY"; + + private static readonly User _user = User.WithKey("foo"); + private const string _encodedUser = "eyJrZXkiOiJmb28iLCJjdXN0b20iOnt9fQ=="; + // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom + // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. + + private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response + + [Fact] + public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync() + { + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) + .WithUseReport(false); + + using (var requestor = new FeatureFlagRequestor(config, _user)) + { + var resp = await requestor.FeatureFlagsAsync(); + Assert.Equal(200, resp.statusCode); + Assert.Equal(_allDataJson, resp.jsonResponse); + + var req = server.GetLastRequest(); + Assert.Equal("GET", req.Method); + Assert.Equal($"/msdk/evalx/users/{_encodedUser}", req.Path); + Assert.Equal("", req.RawQuery); + Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + Assert.Null(req.Body); + } + }); + } + + [Fact] + public async Task GetFlagsUsesCorrectUriAndMethodInGetModeWithReasonsAsync() + { + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) + .WithUseReport(false).WithEvaluationReasons(true); + + using (var requestor = new FeatureFlagRequestor(config, _user)) + { + var resp = await requestor.FeatureFlagsAsync(); + Assert.Equal(200, resp.statusCode); + Assert.Equal(_allDataJson, resp.jsonResponse); + + var req = server.GetLastRequest(); + Assert.Equal("GET", req.Method); + Assert.Equal($"/msdk/evalx/users/{_encodedUser}", req.Path); + Assert.Equal("?withReasons=true", req.RawQuery); + Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + Assert.Null(req.Body); + } + }); + } + + [Fact] + public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() + { + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) + .WithUseReport(true); + + using (var requestor = new FeatureFlagRequestor(config, _user)) + { + var resp = await requestor.FeatureFlagsAsync(); + Assert.Equal(200, resp.statusCode); + Assert.Equal(_allDataJson, resp.jsonResponse); + + var req = server.GetLastRequest(); + Assert.Equal("REPORT", req.Method); + Assert.Equal($"/msdk/evalx/user", req.Path); + Assert.Equal("", req.RawQuery); + Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + + //Assert.Equal("{\"key\":\"foo\"}", req.Body); + // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, for unknown + // reasons the current version of WireMock.Net does not seem to be capturing that information; Content-Type and + // Content-Length are correct, and directing the request at a different listener (e.g. "nc -l") shows that the body + // is definitely being sent, but req.Body and req.BodyAsJson are both null no matter what. + } + }); + } + + [Fact] + public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() + { + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) + .WithUseReport(true).WithEvaluationReasons(true); + + using (var requestor = new FeatureFlagRequestor(config, _user)) + { + var resp = await requestor.FeatureFlagsAsync(); + Assert.Equal(200, resp.statusCode); + Assert.Equal(_allDataJson, resp.jsonResponse); + + var req = server.GetLastRequest(); + Assert.Equal("REPORT", req.Method); + Assert.Equal($"/msdk/evalx/user", req.Path); + Assert.Equal("?withReasons=true", req.RawQuery); + Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + } + }); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs index 5b804a88..92a5b569 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests { - public class FeatureFlagEventTests + public class FeatureFlagEventTests : BaseTest { [Fact] public void ReturnsFlagVersionAsVersion() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index 574cbfd0..fdf00d8d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class FlagCacheManagerTests + public class FlagCacheManagerTests : BaseTest { private const string initialFlagsJson = "{" + "\"int-flag\":{\"value\":15}," + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs new file mode 100644 index 00000000..4034c769 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -0,0 +1,341 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Common.Logging; +using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WireMock.Server; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + // Tests of an LDClient instance doing actual HTTP against an embedded server. These aren't intended to cover + // every possible type of interaction, since the lower-level component tests like FeatureFlagRequestorTests + // (and the DefaultEventProcessor and StreamManager tests in LaunchDarkly.CommonSdk) cover those more thoroughly. + // These are more of a smoke test to ensure that the SDK is initializing and using those components in the + // expected ways. + public class LdClientEndToEndTests : BaseTest + { + private const string _mobileKey = "FAKE_KEY"; + + private static readonly User _user = User.WithKey("foo"); + private static readonly User _otherUser = User.WithKey("bar"); + + private static readonly IDictionary _flagData1 = new Dictionary + { + { "flag1", "value1" } + }; + + private static readonly IDictionary _flagData2 = new Dictionary + { + { "flag1", "value2" } + }; + + [Fact] + public void InitGetsFlagsInPollingModeSync() + { + InitGetsFlagsSync(UpdateMode.Polling); + } + + [Fact] + public void InitGetsFlagsInStreamingModeSync() + { + InitGetsFlagsSync(UpdateMode.Streaming); + } + + private void InitGetsFlagsSync(UpdateMode mode) + { + WithServer(server => + { + SetupResponse(server, _flagData1, mode); + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyRequest(server, mode); + VerifyFlagValues(client, _flagData1); + } + }); + } + + [Fact] + public async Task InitGetsFlagsInPollingModeAsync() + { + await InitGetsFlagsAsync(UpdateMode.Polling); + } + + [Fact] + public async Task InitGetsFlagsInStreamingModeAsync() + { + // Note: since WireMock.Net doesn't seem to support streaming responses, the response will close after the end of the + // data, the SDK will enter retry mode and we may get another identical streaming request. For the purposes of these tests, + // that doesn't matter. The correct processing of a chunked stream is tested in the LaunchDarkly.EventSource tests, and + // the retry logic is tested in LaunchDarkly.CommonSdk. + await InitGetsFlagsAsync(UpdateMode.Streaming); + } + + private async Task InitGetsFlagsAsync(UpdateMode mode) + { + await WithServerAsync(async server => + { + SetupResponse(server, _flagData1, mode); + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + VerifyRequest(server, mode); + } + }); + } + + [Fact] + public void InitCanTimeOutSync() + { + WithServer(server => + { + server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); + + using (var log = new LogSinkScope()) + { + var config = BaseConfig(server).WithIsStreamingEnabled(false); + using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) + { + Assert.False(client.Initialized()); + Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && + m.Text == "Client did not successfully initialize within 200 milliseconds."); + } + } + }); + } + + [Fact] + public void InitFailsOn401InPollingModeSync() + { + InitFailsOn401Sync(UpdateMode.Polling); + } + + [Fact] + public void InitFailsOn401InStreamingModeSync() + { + InitFailsOn401Sync(UpdateMode.Streaming); + } + + private void InitFailsOn401Sync(UpdateMode mode) + { + WithServer(server => + { + server.ForAllRequests(r => r.WithStatusCode(401)); + + using (var log = new LogSinkScope()) + { + try + { + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + using (var client = TestUtil.CreateClient(config, _user)) { } + } + catch (Exception e) + { + // Currently the exact class of this exception is undefined: the polling processor throws + // LaunchDarkly.Client.UnsuccessfulResponseException, while the streaming processor throws + // a lower-level exception that is defined by LaunchDarkly.EventSource. + Assert.Contains("401", e.Message); + return; + } + throw new Exception("Expected exception from LdClient.Init"); + } + }); + } + + [Fact] + public async Task InitFailsOn401InPollingModeAsync() + { + await InitFailsOn401Async(UpdateMode.Polling); + } + + [Fact] + public async Task InitFailsOn401InStreamingModeAsync() + { + await InitFailsOn401Async(UpdateMode.Streaming); + } + + private async Task InitFailsOn401Async(UpdateMode mode) + { + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithStatusCode(401)); + + using (var log = new LogSinkScope()) + { + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + + // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is + // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that + // will complete successfully with an uninitialized client. + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + Assert.False(client.Initialized()); + } + } + }); + } + + [Fact] + public void IdentifySwitchesUserAndGetsFlagsInPollingModeSync() + { + IdentifySwitchesUserAndGetsFlagsSync(UpdateMode.Polling); + } + + [Fact] + public void IdentifySwitchesUserAndGetsFlagsInStreamingModeSync() + { + IdentifySwitchesUserAndGetsFlagsSync(UpdateMode.Streaming); + } + + private void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) + { + WithServer(server => + { + SetupResponse(server, _flagData1, mode); + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyRequest(server, mode); + VerifyFlagValues(client, _flagData1); + var user1RequestPath = server.GetLastRequest().Path; + + server.Reset(); + SetupResponse(server, _flagData2, mode); + + client.Identify(_otherUser); + Assert.Equal(_otherUser, client.User); + + VerifyRequest(server, mode); + Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); + VerifyFlagValues(client, _flagData2); + } + }); + } + + [Fact] + public async Task IdentifySwitchesUserAndGetsFlagsInPollingModeAsync() + { + await IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode.Polling); + } + + [Fact] + public async Task IdentifySwitchesUserAndGetsFlagsInStreamingModeAsync() + { + await IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode.Streaming); + } + + private async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) + { + await WithServerAsync(async server => + { + SetupResponse(server, _flagData1, mode); + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + VerifyRequest(server, mode); + VerifyFlagValues(client, _flagData1); + var user1RequestPath = server.GetLastRequest().Path; + + server.Reset(); + SetupResponse(server, _flagData2, mode); + + await client.IdentifyAsync(_otherUser); + Assert.Equal(_otherUser, client.User); + + VerifyRequest(server, mode); + Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); + VerifyFlagValues(client, _flagData2); + } + }); + } + + private Configuration BaseConfig(FluentMockServer server) + { + return Configuration.Default(_mobileKey) + .WithBaseUri(server.GetUrl()) + .WithStreamUri(server.GetUrl()) + .WithEventProcessor(new NullEventProcessor()); + } + + private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) + { + server.ForAllRequests(r => + mode.IsStreaming ? r.WithEventsBody(StreamingData(data)) : r.WithJsonBody(PollingData(data))); + } + + private void VerifyRequest(FluentMockServer server, UpdateMode mode) + { + var req = server.GetLastRequest(); + Assert.Equal("GET", req.Method); + + // Note, we don't check for an exact match of the encoded user string in Req.Path because it is not determinate - the + // SDK may add custom attributes to the user ("os" etc.) and since we don't canonicalize the JSON representation, + // properties could be serialized in any order causing the encoding to vary. Also, we don't test REPORT mode here + // because it is already covered in FeatureFlagRequestorTest. + Assert.Matches(mode.FlagsPathRegex, req.Path); + + Assert.Equal("", req.RawQuery); + Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + Assert.Null(req.Body); + } + + private void VerifyFlagValues(ILdMobileClient client, IDictionary flags) + { + Assert.True(client.Initialized()); + foreach (var e in flags) + { + Assert.Equal(e.Value, client.StringVariation(e.Key, null)); + } + } + + private JToken FlagJson(string key, string value) + { + var o = new JObject(); + o.Add("key", key); + o.Add("value", value); + return o; + } + + private string PollingData(IDictionary flags) + { + var o = new JObject(); + foreach (var e in flags) + { + o.Add(e.Key, FlagJson(e.Key, e.Value)); + } + return JsonConvert.SerializeObject(o); + } + + private string StreamingData(IDictionary flags) + { + return "event: put\ndata: " + PollingData(flags) + "\n\n"; + } + } + + class UpdateMode + { + public bool IsStreaming { get; private set; } + public string FlagsPathRegex { get; private set; } + + public static readonly UpdateMode Streaming = new UpdateMode + { + IsStreaming = true, + FlagsPathRegex = "^/meval/[^/?]+" + }; + + public static readonly UpdateMode Polling = new UpdateMode + { + IsStreaming = false, + FlagsPathRegex = "^/msdk/evalx/users/[^/?]+" + }; + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 1b212463..0f8ebb7f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 5c47ce32..9d324328 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; -using LaunchDarkly.Client; -using Newtonsoft.Json; +using LaunchDarkly.Client; using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests { - public class LdClientEvaluationTests + public class LdClientEvaluationTests : BaseTest { static readonly string appKey = "some app key"; static readonly string nonexistentFlagKey = "some flag key"; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 89973126..240e9efc 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class LdClientEventTests + public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); private MockEventProcessor eventProcessor = new MockEventProcessor(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index d172cf6a..ae66e813 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -7,21 +7,11 @@ namespace LaunchDarkly.Xamarin.Tests { - public class DefaultLdClientTests + public class DefaultLdClientTests : BaseTest { static readonly string appKey = "some app key"; static readonly User simpleUser = User.WithKey("user-key"); - public DefaultLdClientTests() - { - TestUtil.ClearClient(); - } - - ~DefaultLdClientTests() - { - TestUtil.ClearClient(); - } - LdClient Client() { var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); @@ -252,19 +242,19 @@ public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerMan } [Fact] - public void FlagsAreLoadedFromPersistentStorageByDefault() + public void FlagsAreLoadedFromPersistentStorageByDefault() { var storage = new MockPersistentStorage(); var flagsJson = "{\"flag\": {\"value\": 100}}"; - storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); + storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .WithOffline(true) .WithPersistentStorage(storage) .WithFlagCacheManager(null); // use actual cache logic, not mock component (even though persistence layer is a mock) - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - Assert.Equal(100, client.IntVariation("flag", 99)); - } + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.Equal(100, client.IntVariation("flag", 99)); + } } [Fact] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs new file mode 100644 index 00000000..b74229db --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Common.Logging; +using Common.Logging.Simple; + +namespace LaunchDarkly.Xamarin.Tests +{ + // This mechanism allows unit tests to capture and inspect log output. In order for it to work properly, all + // tests must derive from BaseTest so that the global Common.Logging configuration is modified before any + // test code runs (i.e. before any SDK code has a change to create a logger instance). + // + // For debugging purposes, this also allows you to mirror all log output to the console (which Common.Logging + // does not do by default) by setting the environment variable LD_TEST_LOGS to any value. + + public class LogSink : AbstractSimpleLogger + { + public static LogSink Instance = new LogSink(); + + private static readonly bool _showLogs = Environment.GetEnvironmentVariable("LD_TEST_LOGS") != null; + + public LogSink() : base("", LogLevel.All, false, false, false, "") {} + + protected override void WriteInternal(LogLevel level, object message, Exception exception) + { + if (_showLogs) + { + Console.WriteLine("*** LOG: [" + level + "] " + message); + } + LogSinkScope.WithCurrent(s => s.Messages.Add(new LogItem { Level = level, Text = message.ToString() })); + } + } + + public struct LogItem + { + public LogLevel Level { get; set; } + public string Text { get; set; } + } + + public class LogSinkFactoryAdapter : AbstractSimpleLoggerFactoryAdapter + { + public LogSinkFactoryAdapter() : base(null) {} + + protected override ILog CreateLogger(string name, LogLevel level, bool showLevel, bool showDateTime, bool showLogName, string dateTimeFormat) + { + return LogSink.Instance; + } + } + + public class LogSinkScope : IDisposable + { + private static Stack _scopes = new Stack(); + + public List Messages = new List(); + + public LogSinkScope() + { + _scopes.Push(this); + } + + public void Dispose() + { + _scopes.Pop(); + } + + public static void WithCurrent(Action a) + { + if (_scopes.TryPeek(out var s)) + { + a(s); + } + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs index 1728fa34..1aeca942 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class MobilePollingProcessorTests + public class MobilePollingProcessorTests : BaseTest { private const string flagsJson = "{" + "\"int-flag\":{\"value\":15}," + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 03d4db6d..a7f8b1d0 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using LaunchDarkly.Client; using LaunchDarkly.Common; using LaunchDarkly.EventSource; -using Newtonsoft.Json; using Xunit; namespace LaunchDarkly.Xamarin.Tests { - public class MobileStreamingProcessorTests + public class MobileStreamingProcessorTests : BaseTest { private const string initialFlagsJson = "{" + "\"int-flag\":{\"value\":15,\"version\":100}," + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 71d8cb13..be5d187b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -1,30 +1,46 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using LaunchDarkly.Client; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin.Tests { - public class TestUtil + public static class TestUtil { // Any tests that are going to access the static LdClient.Instance must hold this lock, // to avoid interfering with tests that use CreateClient. public static readonly object ClientInstanceLock = new object(); - + // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can // instantiate their own independent clients. Application code cannot do this because // the LdClient.Instance setter has internal scope. - public static LdClient CreateClient(Configuration config, User user) + public static LdClient CreateClient(Configuration config, User user, TimeSpan? timeout = null) { + ClearClient(); lock (ClientInstanceLock) { - LdClient client = LdClient.Init(config, user, TimeSpan.FromSeconds(1)); + LdClient client = LdClient.Init(config, user, timeout ?? TimeSpan.FromSeconds(1)); LdClient.Instance = null; return client; } } + // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can + // instantiate their own independent clients. Application code cannot do this because + // the LdClient.Instance setter has internal scope. + public static async Task CreateClientAsync(Configuration config, User user) + { + ClearClient(); + LdClient client = await LdClient.InitAsync(config, user); + lock (ClientInstanceLock) + { + LdClient.Instance = null; + } + return client; + } + public static void ClearClient() { lock (ClientInstanceLock) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs index 4bfffcaa..b8b8e0f6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class UserFlagCacheTests + public class UserFlagCacheTests : BaseTest { IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); User user1 = User.WithKey("user1Key"); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs b/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs new file mode 100644 index 00000000..7e91eb7d --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs @@ -0,0 +1,43 @@ +using System; +using WireMock; +using WireMock.Logging; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace LaunchDarkly.Xamarin.Tests +{ + // Convenience methods to streamline the test code's usage of WireMock. + public static class WireMockExtensions + { + public static string GetUrl(this FluentMockServer server) + { + return server.Urls[0]; + } + + public static FluentMockServer ForAllRequests(this FluentMockServer server, Func builderFn) + { + server.Given(Request.Create()).RespondWith(builderFn(Response.Create().WithStatusCode(200))); + return server; + } + + public static IResponseBuilder WithJsonBody(this IResponseBuilder resp, string body) + { + return resp.WithBody(body).WithHeader("Content-Type", "application/json"); + } + + public static IResponseBuilder WithEventsBody(this IResponseBuilder resp, string body) + { + return resp.WithBody(body).WithHeader("Content-Type", "text/event-stream"); + } + + public static RequestMessage GetLastRequest(this FluentMockServer server) + { + foreach (LogEntry le in server.LogEntries) + { + return le.RequestMessage; + } + throw new InvalidOperationException("Did not receive a request"); + } + } +} From eb781c35ec24d07368d8908ff459255c0c77c028 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sun, 23 Jun 2019 16:39:45 -0700 Subject: [PATCH 108/499] fix malformed log message --- src/LaunchDarkly.XamarinSdk/Configuration.shared.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs b/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs index f65d032e..1d31887e 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs @@ -388,7 +388,7 @@ public static Configuration WithPollingInterval(this Configuration configuration { if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) { - Log.WarnFormat("PollingInterval cannot be less than the default of {0}."); + Log.WarnFormat("PollingInterval cannot be less than the default of {0}.", Configuration.MinimumPollingInterval); pollingInterval = Configuration.MinimumPollingInterval; } configuration.PollingInterval = pollingInterval; From fd93cfe35af5c7f233d3ce21204f4b0d27f7286c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sun, 23 Jun 2019 16:40:43 -0700 Subject: [PATCH 109/499] add tests for cached flags in offline mode --- .../LDClientEndToEndTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 4034c769..610f9f31 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -258,6 +258,58 @@ await WithServerAsync(async server => }); } + [Fact] + public void OfflineClientUsesCachedFlagsSync() + { + WithServer(server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + VerifyFlagValues(client, _flagData1); + } + }); + } + + [Fact] + public async Task OfflineClientUsesCachedFlagsAsync() + { + await WithServerAsync(async server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + VerifyFlagValues(client, _flagData1); + } + }); + } + private Configuration BaseConfig(FluentMockServer server) { return Configuration.Default(_mobileKey) From feed22d2898e018b12f92b259553ab8c9d753def Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sun, 23 Jun 2019 16:41:13 -0700 Subject: [PATCH 110/499] tiny fix to avoid doing ToString twice --- tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs index b74229db..5cee4c8d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs @@ -22,11 +22,12 @@ public LogSink() : base("", LogLevel.All, false, false, false, "") {} protected override void WriteInternal(LogLevel level, object message, Exception exception) { + var str = message?.ToString(); if (_showLogs) { - Console.WriteLine("*** LOG: [" + level + "] " + message); + Console.WriteLine("*** LOG: [" + level + "] " + str); } - LogSinkScope.WithCurrent(s => s.Messages.Add(new LogItem { Level = level, Text = message.ToString() })); + LogSinkScope.WithCurrent(s => s.Messages.Add(new LogItem { Level = level, Text = str })); } } From 1a417f3774411b4b167ad0813be2d5b65dd66e44 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sun, 23 Jun 2019 19:45:54 -0700 Subject: [PATCH 111/499] syntax error --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4de92fef..2dd03e7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,8 +12,8 @@ jobs: test-netstandard2.0: docker: - image: microsoft/dotnet:2.0-sdk-jessie - env: - - ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests + environment: + ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests steps: - checkout - run: dotnet restore From bdcc5f1735d084f74f52727243594fcb098d9690 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 10:07:15 -0700 Subject: [PATCH 112/499] comment --- .../FeatureFlagRequestorTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index fd07d09e..5c0dd584 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -91,10 +91,8 @@ await WithServerAsync(async server => Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); //Assert.Equal("{\"key\":\"foo\"}", req.Body); - // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, for unknown - // reasons the current version of WireMock.Net does not seem to be capturing that information; Content-Type and - // Content-Length are correct, and directing the request at a different listener (e.g. "nc -l") shows that the body - // is definitely being sent, but req.Body and req.BodyAsJson are both null no matter what. + // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, WireMock.Net + // is not currently able to detect the body for REPORT requests: https://github.com/WireMock-Net/WireMock.Net/issues/290 } }); } From 4b276c344f3fe65f8da30e7b6eb0f66d6e55259c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 10:16:53 -0700 Subject: [PATCH 113/499] simplify test methods by parameterizing --- .../LDClientEndToEndTests.cs | 108 +++++------------- 1 file changed, 27 insertions(+), 81 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 610f9f31..8fb985a2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -33,19 +33,15 @@ public class LdClientEndToEndTests : BaseTest { "flag1", "value2" } }; - [Fact] - public void InitGetsFlagsInPollingModeSync() - { - InitGetsFlagsSync(UpdateMode.Polling); - } - - [Fact] - public void InitGetsFlagsInStreamingModeSync() + public static readonly IEnumerable PollingAndStreaming = new List { - InitGetsFlagsSync(UpdateMode.Streaming); - } + { new object[] { UpdateMode.Polling } }, + { new object[] { UpdateMode.Streaming } } + }; - private void InitGetsFlagsSync(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public void InitGetsFlagsSync(UpdateMode mode) { WithServer(server => { @@ -60,23 +56,9 @@ private void InitGetsFlagsSync(UpdateMode mode) }); } - [Fact] - public async Task InitGetsFlagsInPollingModeAsync() - { - await InitGetsFlagsAsync(UpdateMode.Polling); - } - - [Fact] - public async Task InitGetsFlagsInStreamingModeAsync() - { - // Note: since WireMock.Net doesn't seem to support streaming responses, the response will close after the end of the - // data, the SDK will enter retry mode and we may get another identical streaming request. For the purposes of these tests, - // that doesn't matter. The correct processing of a chunked stream is tested in the LaunchDarkly.EventSource tests, and - // the retry logic is tested in LaunchDarkly.CommonSdk. - await InitGetsFlagsAsync(UpdateMode.Streaming); - } - - private async Task InitGetsFlagsAsync(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public async Task InitGetsFlagsAsync(UpdateMode mode) { await WithServerAsync(async server => { @@ -111,19 +93,9 @@ public void InitCanTimeOutSync() }); } - [Fact] - public void InitFailsOn401InPollingModeSync() - { - InitFailsOn401Sync(UpdateMode.Polling); - } - - [Fact] - public void InitFailsOn401InStreamingModeSync() - { - InitFailsOn401Sync(UpdateMode.Streaming); - } - - private void InitFailsOn401Sync(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public void InitFailsOn401Sync(UpdateMode mode) { WithServer(server => { @@ -149,19 +121,9 @@ private void InitFailsOn401Sync(UpdateMode mode) }); } - [Fact] - public async Task InitFailsOn401InPollingModeAsync() - { - await InitFailsOn401Async(UpdateMode.Polling); - } - - [Fact] - public async Task InitFailsOn401InStreamingModeAsync() - { - await InitFailsOn401Async(UpdateMode.Streaming); - } - - private async Task InitFailsOn401Async(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public async Task InitFailsOn401Async(UpdateMode mode) { await WithServerAsync(async server => { @@ -182,19 +144,9 @@ await WithServerAsync(async server => }); } - [Fact] - public void IdentifySwitchesUserAndGetsFlagsInPollingModeSync() - { - IdentifySwitchesUserAndGetsFlagsSync(UpdateMode.Polling); - } - - [Fact] - public void IdentifySwitchesUserAndGetsFlagsInStreamingModeSync() - { - IdentifySwitchesUserAndGetsFlagsSync(UpdateMode.Streaming); - } - - private void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { WithServer(server => { @@ -220,19 +172,9 @@ private void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) }); } - [Fact] - public async Task IdentifySwitchesUserAndGetsFlagsInPollingModeAsync() - { - await IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode.Polling); - } - - [Fact] - public async Task IdentifySwitchesUserAndGetsFlagsInStreamingModeAsync() - { - await IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode.Streaming); - } - - private async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) { await WithServerAsync(async server => { @@ -322,6 +264,10 @@ private void SetupResponse(FluentMockServer server, IDictionary { server.ForAllRequests(r => mode.IsStreaming ? r.WithEventsBody(StreamingData(data)) : r.WithJsonBody(PollingData(data))); + // Note: in streaming mode, since WireMock.Net doesn't seem to support streaming responses, the fake response will close + // after the end of the data-- so the SDK will enter retry mode and we may get another identical streaming request. For + // the purposes of these tests, that doesn't matter. The correct processing of a chunked stream is tested in the + // LaunchDarkly.EventSource tests, and the retry logic is tested in LaunchDarkly.CommonSdk. } private void VerifyRequest(FluentMockServer server, UpdateMode mode) @@ -373,7 +319,7 @@ private string StreamingData(IDictionary flags) } } - class UpdateMode + public class UpdateMode { public bool IsStreaming { get; private set; } public string FlagsPathRegex { get; private set; } From 1205e52a9bf2afe1b6b1bf5084e98b933f56ac3b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 14:04:40 -0700 Subject: [PATCH 114/499] fix test cleanup concurrency problem --- tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 4 ++-- .../LdClientEvaluationTests.cs | 10 ---------- .../LdClientEventTests.cs | 10 ---------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index 09b1acc5..36ee2c06 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -7,7 +7,7 @@ namespace LaunchDarkly.Xamarin.Tests { [Collection("serialize all tests")] - public class BaseTest + public class BaseTest : IDisposable { public BaseTest() { @@ -15,7 +15,7 @@ public BaseTest() TestUtil.ClearClient(); } - ~BaseTest() + public void Dispose() { TestUtil.ClearClient(); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 9d324328..25a3cb10 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -9,16 +9,6 @@ public class LdClientEvaluationTests : BaseTest static readonly string appKey = "some app key"; static readonly string nonexistentFlagKey = "some flag key"; static readonly User user = User.WithKey("userkey"); - - public LdClientEvaluationTests() - { - TestUtil.ClearClient(); - } - - ~LdClientEvaluationTests() - { - TestUtil.ClearClient(); - } private static LdClient ClientWithFlagsJson(string flagsJson) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 240e9efc..daa4cc6f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -8,16 +8,6 @@ public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); private MockEventProcessor eventProcessor = new MockEventProcessor(); - - public LdClientEventTests() - { - TestUtil.ClearClient(); - } - - ~LdClientEventTests() - { - TestUtil.ClearClient(); - } public LdClient MakeClient(User user, string flagsJson) { From 810179e28690419e23f1e685ff5595e26b66f483 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 15:15:07 -0700 Subject: [PATCH 115/499] make standard test suite run in iOS test project --- .../AppDelegate.cs | 51 +++ .../LDiOSTests.cs | 61 ---- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 300 +++++++++++++++++- LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs | 4 +- .../UnitTestAppDelegate.cs | 45 --- .../packages.config | 84 ++++- .../LDClientEndToEndTests.cs | 6 +- .../LdClientTests.cs | 2 +- 8 files changed, 437 insertions(+), 116 deletions(-) create mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs new file mode 100644 index 00000000..3cb59b4a --- /dev/null +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Foundation; +using UIKit; + +using Xunit.Runner; +using Xunit.Sdk; + + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + // The UIApplicationDelegate for the application. This class is responsible for launching the + // User Interface of the application, as well as listening (and optionally responding) to + // application events from iOS. + [Register("AppDelegate")] + public partial class AppDelegate : RunnerAppDelegate + { + + // + // This method is invoked when the application has loaded and is ready to run. In this + // method you should instantiate the window, load the UI into it and then make the window + // visible. + // + // You have 17 seconds to return from this method, or iOS will terminate your application. + // + public override bool FinishedLaunching(UIApplication app, NSDictionary options) + { + // We need this to ensure the execution assembly is part of the app bundle + AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); + + + // tests can be inside the main assembly + AddTestAssembly(Assembly.GetExecutingAssembly()); + // otherwise you need to ensure that the test assemblies will + // become part of the app bundle + //AddTestAssembly(typeof(PortableTests).Assembly); + +#if false + // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) + Writer = new TcpTextWriter ("10.0.1.2", 16384); + // start running the test suites as soon as the application is loaded + AutoStart = true; + // crash the application (to ensure it's ended) and return to springboard + TerminateAfterExecution = true; +#endif + return base.FinishedLaunching(app, options); + } + } +} \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs deleted file mode 100644 index 8deef83b..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LDiOSTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using NUnit.Framework; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin.iOS.Tests -{ - [TestFixture] - public class LDiOSTests - { - private ILdMobileClient client; - - [SetUp] - public void Setup() { - var user = LaunchDarkly.Client.User.WithKey("test-user"); - var timeSpan = TimeSpan.FromSeconds(10); - client = LdClient.Init("MOBILE_KEY", user, timeSpan); - } - - [TearDown] - public void Tear() { LdClient.Instance = null;} - - [Test] - public void BooleanFeatureFlag() - { - Console.WriteLine("Test Boolean Variation"); - Assert.True(client.BoolVariation("boolean-feature-flag")); - } - - [Test] - public void IntFeatureFlag() - { - Console.WriteLine("Test Integer Variation"); - Assert.True(client.IntVariation("int-feature-flag") == 2); - } - - [Test] - public void StringFeatureFlag() - { - Console.WriteLine("Test String Variation"); - Assert.True(client.StringVariation("string-feature-flag", "false").Equals("bravo")); - } - - [Test] - public void JsonFeatureFlag() - { - string json = @"{ - ""test2"": ""testing2"" - }"; - Console.WriteLine("Test JSON Variation"); - JToken jsonToken = JToken.FromObject(JObject.Parse(json)); - Assert.True(JToken.DeepEquals(jsonToken, client.JsonVariation("json-feature-flag", "false"))); - } - - [Test] - public void FloatFeatureFlag() - { - Console.WriteLine("Test Float Variation"); - Assert.True(client.FloatVariation("float-feature-flag") == 1.5); - } - } -} diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 04398e77..d8f5c899 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -1,5 +1,8 @@ + + + Debug iPhoneSimulator @@ -94,7 +97,7 @@ ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll - ..\packages\Newtonsoft.Json.9.0.1\lib\netstandard1.0\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.11.0.2\lib\netstandard2.0\Newtonsoft.Json.dll @@ -110,6 +113,243 @@ ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll + + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll + + + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll + + + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll + + + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll + + + ..\packages\xunit.runner.devices.2.5.25\lib\xamarinios10\xunit.runner.devices.dll + + + ..\packages\xunit.runner.devices.2.5.25\lib\xamarinios10\xunit.runner.utility.netstandard15.dll + + + ..\packages\xunit.abstractions.2.0.3\lib\netstandard2.0\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\netstandard1.1\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\netstandard1.1\xunit.execution.dotnet.dll + + + ..\packages\Microsoft.AspNetCore.Diagnostics.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Diagnostics.Abstractions.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.FileSystemGlobbing.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileSystemGlobbing.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.ObjectPool.2.1.1\lib\netstandard2.0\Microsoft.Extensions.ObjectPool.dll + + + ..\packages\MimeKitLite.2.0.7\lib\xamarinios\MimeKitLite.dll + + + ..\packages\Fare.2.1.1\lib\netstandard1.1\Fare.dll + + + ..\packages\JmesPath.Net.1.0.125\lib\netstandard2.0\JmesPath.Net.dll + + + ..\packages\NLipsum.1.1.0\lib\netstandard1.0\NLipsum.Core.dll + + + ..\packages\RandomDataGenerator.Net.1.0.8\lib\netstandard2.0\RandomDataGenerator.dll + + + ..\packages\SimMetrics.Net.1.0.5\lib\netstandard2.0\SimMetrics.Net.dll + + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + ..\packages\System.Diagnostics.DiagnosticSource.4.5.0\lib\netstandard1.3\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\RestEase.1.4.7\lib\netstandard2.0\RestEase.dll + + + ..\packages\System.Linq.Dynamic.Core.1.0.12\lib\netstandard2.0\System.Linq.Dynamic.Core.dll + + + ..\packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll + + + ..\packages\Handlebars.Net.1.9.5\lib\netstandard2.0\Handlebars.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll + + + ..\packages\Microsoft.Extensions.Primitives.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + + ..\packages\Microsoft.AspNetCore.Http.Features.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Hosting.Server.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + + ..\packages\Microsoft.Extensions.Configuration.Binder.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll + + + ..\packages\Microsoft.Extensions.Configuration.CommandLine.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.CommandLine.dll + + + ..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.EnvironmentVariables.dll + + + ..\packages\Microsoft.Extensions.FileProviders.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Abstractions.dll + + + ..\packages\Microsoft.Extensions.FileProviders.Physical.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Physical.dll + + + ..\packages\Microsoft.Extensions.Configuration.FileExtensions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.FileExtensions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Json.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Json.dll + + + ..\packages\Microsoft.Extensions.Configuration.UserSecrets.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.dll + + + ..\packages\Microsoft.Extensions.Hosting.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Logging.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Debug.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Debug.dll + + + ..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.ConfigurationExtensions.dll + + + ..\packages\Microsoft.Extensions.Logging.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Configuration.dll + + + ..\packages\Microsoft.Extensions.Logging.Console.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Console.dll + + + ..\packages\Microsoft.Net.Http.Headers.2.1.1\lib\netstandard2.0\Microsoft.Net.Http.Headers.dll + + + ..\packages\System.IO.Pipelines.4.5.2\lib\netstandard2.0\System.IO.Pipelines.dll + + + ..\packages\Microsoft.AspNetCore.Connections.Abstractions.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Connections.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.dll + + + ..\packages\System.Security.Principal.Windows.4.5.1\lib\netstandard2.0\System.Security.Principal.Windows.dll + + + ..\packages\System.Text.Encodings.Web.4.5.0\lib\netstandard2.0\System.Text.Encodings.Web.dll + + + ..\packages\Microsoft.AspNetCore.Http.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Authentication.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Hosting.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Http.Extensions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll + + + ..\packages\Microsoft.AspNetCore.HttpOverrides.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.HttpOverrides.dll + + + ..\packages\Microsoft.AspNetCore.Routing.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Routing.Abstractions.dll + + + ..\packages\Microsoft.AspNetCore.Routing.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Routing.dll + + + ..\packages\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll + + + ..\packages\Microsoft.AspNetCore.WebUtilities.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.WebUtilities.dll + + + ..\packages\Microsoft.AspNetCore.Diagnostics.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Diagnostics.dll + + + ..\packages\Microsoft.AspNetCore.Http.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.dll + + + ..\packages\Microsoft.AspNetCore.Authentication.Core.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Core.dll + + + ..\packages\Microsoft.AspNetCore.HostFiltering.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.HostFiltering.dll + + + ..\packages\Microsoft.AspNetCore.Hosting.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.dll + + + ..\packages\Microsoft.AspNetCore.Server.IISIntegration.2.1.2\lib\netstandard2.0\Microsoft.AspNetCore.Server.IISIntegration.dll + + + ..\packages\Microsoft.AspNetCore.Server.Kestrel.Core.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Core.dll + + + ..\packages\Microsoft.AspNetCore.Server.Kestrel.Https.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Https.dll + + + ..\packages\Microsoft.AspNetCore.Server.Kestrel.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.dll + + + ..\packages\Microsoft.AspNetCore.2.1.4\lib\netstandard2.0\Microsoft.AspNetCore.dll + + + ..\packages\XPath2.1.0.6.1\lib\netstandard2.0\XPath2.dll + + + ..\packages\XPath2.Extensions.1.0.6.1\lib\netstandard2.0\XPath2.Extensions.dll + + + ..\packages\WireMock.Net.1.0.20\lib\netstandard2.0\WireMock.Net.dll + @@ -121,9 +361,63 @@ - - + + + SharedTestCode\BaseTest.cs + + + SharedTestCode\ConfigurationTest.cs + + + SharedTestCode\FeatureFlagListenerTests.cs + + + SharedTestCode\FeatureFlagRequestorTests.cs + + + SharedTestCode\FeatureFlagTests.cs + + + SharedTestCode\FlagCacheManagerTests.cs + + + SharedTestCode\LdClientEndToEndTests.cs + + + SharedTestCode\LdClientEvaluationTests.cs + + + SharedTestCode\LdClientEventTests.cs + + + SharedTestCode\LdClientTests.cs + + + SharedTestCode\LogSink.cs + + + SharedTestCode\MobilePollingProcessorTests.cs + + + SharedTestCode\MockComponents.cs + + + SharedTestCode\TestUtil.cs + + + SharedTestCode\UserFlagCacheTests.cs + + + SharedTestCode\WireMockExtensions.cs + + + + + + + + \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs index c040831c..96b290eb 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs @@ -12,9 +12,9 @@ public class Application // This is the main entry point of the application. static void Main(string[] args) { - // if you want to use a different Application Delegate class from "UnitTestAppDelegate" + // if you want to use a different Application Delegate class from "AppDelegate" // you can specify it here. - UIApplication.Main(args, null, "UnitTestAppDelegate"); + UIApplication.Main(args, null, "AppDelegate"); } } } diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs deleted file mode 100644 index d5e666aa..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/UnitTestAppDelegate.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; - -using Foundation; -using UIKit; -using MonoTouch.NUnit.UI; - -namespace LaunchDarkly.Xamarin.iOS.Tests -{ - // The UIApplicationDelegate for the application. This class is responsible for launching the - // User Interface of the application, as well as listening (and optionally responding) to - // application events from iOS. - [Register("UnitTestAppDelegate")] - public partial class UnitTestAppDelegate : UIApplicationDelegate - { - // class-level declarations - UIWindow window; - TouchRunner runner; - - // - // This method is invoked when the application has loaded and is ready to run. In this - // method you should instantiate the window, load the UI into it and then make the window - // visible. - // - // You have 17 seconds to return from this method, or iOS will terminate your application. - // - public override bool FinishedLaunching(UIApplication app, NSDictionary options) - { - // create a new window instance based on the screen size - window = new UIWindow(UIScreen.MainScreen.Bounds); - runner = new TouchRunner(window); - - // register every tests included in the main application/assembly - runner.Add(System.Reflection.Assembly.GetExecutingAssembly()); - - window.RootViewController = new UINavigationController(runner.GetViewController()); - - // make the window visible - window.MakeKeyAndVisible(); - - return true; - } - } -} diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config b/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config index 6aedd5ad..8a78858e 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config @@ -2,18 +2,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + @@ -23,34 +82,57 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 8fb985a2..37dd9052 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -163,7 +163,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) SetupResponse(server, _flagData2, mode); client.Identify(_otherUser); - Assert.Equal(_otherUser, client.User); + Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes VerifyRequest(server, mode); Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); @@ -191,7 +191,7 @@ await WithServerAsync(async server => SetupResponse(server, _flagData2, mode); await client.IdentifyAsync(_otherUser); - Assert.Equal(_otherUser, client.User); + Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes VerifyRequest(server, mode); Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); @@ -257,7 +257,7 @@ private Configuration BaseConfig(FluentMockServer server) return Configuration.Default(_mobileKey) .WithBaseUri(server.GetUrl()) .WithStreamUri(server.GetUrl()) - .WithEventProcessor(new NullEventProcessor()); + .WithEventProcessor(new MockEventProcessor()); } private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index ae66e813..45afe92e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -53,7 +53,7 @@ public void IdentifyUpdatesTheUser() { var updatedUser = User.WithKey("some new key"); client.Identify(updatedUser); - Assert.Equal(client.User, updatedUser); + Assert.Equal(client.User.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes } } From d55c90a09f29e4c086581512d0e553cf43166aa0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 16:39:00 -0700 Subject: [PATCH 116/499] run iOS tests in CI --- .circleci/config.yml | 46 +++++++++++++++++-- .../AppDelegate.cs | 8 +--- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 5 ++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2dd03e7c..b4cce793 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,17 +57,55 @@ jobs: xcode: "10.2.1" steps: - - run: - name: Set up package source for Xamarin tools - command: brew install caskroom/cask/brew-cask + - restore_cache: + key: homebrew - run: name: Install Xamarin tools - command: brew cask install xamarin xamarin-ios && brew install mono + command: | + brew install caskroom/cask/brew-cask + brew cask install xamarin xamarin-ios && brew install mono # Note, "mono" provides the msbuild CLI tool + - save_cache: + key: homebrew + paths: + - /usr/local/Homebrew + - checkout - run: name: Build SDK command: msbuild /restore /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + + - run: + name: Build test project + command: msbuild LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + + - run: + name: Start simulator + command: | + xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2 + xcrun simctl boot xm-ios + + - run: + name: Load test app into simulator + command: xcrun simctl install "xm-ios" LaunchDarkly.XamarinSdk.iOS.Tests/bin/iPhoneSimulator/Debug/LaunchDarkly.XamarinSdk.iOS.Tests.app + + - run: + name: Start capturing log output + command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.XamarinSdk.iOS.Tests"' >test-run.log + background: true + + - run: + name: Launch test app in simulator + command: xcrun simctl launch "xm-ios" com.launchdarkly.LaunchDarkly-Xamarin-iOS-Tests + + - run: + name: Wait for tests to finish running + # https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found + command: "( tail -f -n0 test-run.log & ) | grep -q 'Tests run:'" + + - run: + name: Show test output + command: cat test-run.log diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs index 3cb59b4a..aa7f8ce1 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs @@ -8,7 +8,6 @@ using Xunit.Runner; using Xunit.Sdk; - namespace LaunchDarkly.Xamarin.iOS.Tests { // The UIApplicationDelegate for the application. This class is responsible for launching the @@ -17,7 +16,6 @@ namespace LaunchDarkly.Xamarin.iOS.Tests [Register("AppDelegate")] public partial class AppDelegate : RunnerAppDelegate { - // // This method is invoked when the application has loaded and is ready to run. In this // method you should instantiate the window, load the UI into it and then make the window @@ -29,7 +27,6 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options) { // We need this to ensure the execution assembly is part of the app bundle AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - // tests can be inside the main assembly AddTestAssembly(Assembly.GetExecutingAssembly()); @@ -37,14 +34,13 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options) // become part of the app bundle //AddTestAssembly(typeof(PortableTests).Assembly); -#if false // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - Writer = new TcpTextWriter ("10.0.1.2", 16384); + // Writer = new TcpTextWriter ("10.0.1.2", 16384); // start running the test suites as soon as the application is loaded AutoStart = true; // crash the application (to ensure it's ended) and return to springboard TerminateAfterExecution = true; -#endif + return base.FinishedLaunching(app, options); } } diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index d8f5c899..cc14f2f4 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -83,6 +83,11 @@ NSUrlSessionHandler + + + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\xamarin.ios10\LaunchDarkly.XamarinSdk.dll From 732f31f43ef3115c77499febefe8badecda20faf Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 16:40:41 -0700 Subject: [PATCH 117/499] syntax error --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b4cce793..8e35c19e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,7 +58,7 @@ jobs: steps: - restore_cache: - key: homebrew + key: homebrew - run: name: Install Xamarin tools @@ -104,7 +104,7 @@ jobs: - run: name: Wait for tests to finish running # https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found - command: "( tail -f -n0 test-run.log & ) | grep -q 'Tests run:'" + command: "( tail -f -c+0 test-run.log & ) | grep -q 'Tests run:'" - run: name: Show test output From 3bd1f8f21c11b2e97b8eb972661a0e336a88a6a4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 16:49:27 -0700 Subject: [PATCH 118/499] misc build fixes --- .circleci/config.yml | 2 +- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e35c19e..1441af98 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,7 +80,7 @@ jobs: - run: name: Build test project - command: msbuild LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - run: name: Start simulator diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index cc14f2f4..ec344391 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -89,9 +89,6 @@ - - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\xamarin.ios10\LaunchDarkly.XamarinSdk.dll - @@ -419,6 +416,12 @@ + + + {7717A2B2-9905-40A7-989F-790139D69543} + LaunchDarkly.XamarinSdk + + From 39d124bf116125ac630608524778348294d98cfa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Jun 2019 16:50:09 -0700 Subject: [PATCH 119/499] cleanup boilerplate code --- .../AppDelegate.cs | 32 ++++--------------- LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs | 10 +----- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs index aa7f8ce1..4beefc8f 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Foundation; using UIKit; @@ -10,35 +7,20 @@ namespace LaunchDarkly.Xamarin.iOS.Tests { - // The UIApplicationDelegate for the application. This class is responsible for launching the - // User Interface of the application, as well as listening (and optionally responding) to - // application events from iOS. + // This is based on code that was generated automatically by the xunit.runner.devices package. + // It configures the test-runner app that is implemented by that package, telling it where to + // find the tests, and also configuring it to run them immediately and then quit rather than + // waiting for user input. Output from the test run goes to the system log. + [Register("AppDelegate")] public partial class AppDelegate : RunnerAppDelegate { - // - // This method is invoked when the application has loaded and is ready to run. In this - // method you should instantiate the window, load the UI into it and then make the window - // visible. - // - // You have 17 seconds to return from this method, or iOS will terminate your application. - // public override bool FinishedLaunching(UIApplication app, NSDictionary options) { - // We need this to ensure the execution assembly is part of the app bundle AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - - // tests can be inside the main assembly AddTestAssembly(Assembly.GetExecutingAssembly()); - // otherwise you need to ensure that the test assemblies will - // become part of the app bundle - //AddTestAssembly(typeof(PortableTests).Assembly); - - // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - // Writer = new TcpTextWriter ("10.0.1.2", 16384); - // start running the test suites as soon as the application is loaded - AutoStart = true; - // crash the application (to ensure it's ended) and return to springboard + + AutoStart = true; TerminateAfterExecution = true; return base.FinishedLaunching(app, options); diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs index 96b290eb..fa24461b 100644 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs +++ b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs @@ -1,19 +1,11 @@ -using System; -using System.Linq; -using System.Collections.Generic; - -using Foundation; -using UIKit; +using UIKit; namespace LaunchDarkly.Xamarin.iOS.Tests { public class Application { - // This is the main entry point of the application. static void Main(string[] args) { - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. UIApplication.Main(args, null, "AppDelegate"); } } From e166d731bd6c066238d0b838e77c153f1627c6e2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 25 Jun 2019 13:38:10 -0700 Subject: [PATCH 120/499] add a new iOS test project made from scratch --- .circleci/config.yml | 6 +- LaunchDarkly.XamarinSdk.sln | 28 +-- .../AppDelegate.cs | 29 +++ .../AppIcon.appiconset/Contents.json | 117 ++++++++++ .../AppIcon.appiconset/Icon1024.png | Bin 0 -> 70429 bytes .../AppIcon.appiconset/Icon120.png | Bin 0 -> 3773 bytes .../AppIcon.appiconset/Icon152.png | Bin 0 -> 4750 bytes .../AppIcon.appiconset/Icon167.png | Bin 0 -> 4692 bytes .../AppIcon.appiconset/Icon180.png | Bin 0 -> 5192 bytes .../AppIcon.appiconset/Icon20.png | Bin 0 -> 1313 bytes .../AppIcon.appiconset/Icon29.png | Bin 0 -> 845 bytes .../AppIcon.appiconset/Icon40.png | Bin 0 -> 1101 bytes .../AppIcon.appiconset/Icon58.png | Bin 0 -> 1761 bytes .../AppIcon.appiconset/Icon60.png | Bin 0 -> 2537 bytes .../AppIcon.appiconset/Icon76.png | Bin 0 -> 2332 bytes .../AppIcon.appiconset/Icon80.png | Bin 0 -> 2454 bytes .../AppIcon.appiconset/Icon87.png | Bin 0 -> 2758 bytes .../Entitlements.plist | 6 + .../Info.plist | 48 +++++ .../LaunchDarkly.XamarinSdk.iOS.Toasts.csproj | 204 ++++++++++++++++++ .../LaunchScreen.storyboard | 27 +++ .../LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs | 12 ++ .../Main.storyboard | 27 +++ .../Properties/AssemblyInfo.cs | 36 ++++ .../Resources/LaunchScreen.xib | 43 ++++ 25 files changed, 566 insertions(+), 17 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon20.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon29.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon40.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon80.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.storyboard create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/Resources/LaunchScreen.xib diff --git a/.circleci/config.yml b/.circleci/config.yml index 1441af98..7d47f923 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,7 +80,7 @@ jobs: - run: name: Build test project - command: msbuild /restore LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - run: name: Start simulator @@ -90,7 +90,7 @@ jobs: - run: name: Load test app into simulator - command: xcrun simctl install "xm-ios" LaunchDarkly.XamarinSdk.iOS.Tests/bin/iPhoneSimulator/Debug/LaunchDarkly.XamarinSdk.iOS.Tests.app + command: xcrun simctl install "xm-ios" tests/LaunchDarkly.XamarinSdk.iOS.Tests/bin/iPhoneSimulator/Debug/LaunchDarkly.XamarinSdk.iOS.Tests.app - run: name: Start capturing log output @@ -99,7 +99,7 @@ jobs: - run: name: Launch test app in simulator - command: xcrun simctl launch "xm-ios" com.launchdarkly.LaunchDarkly-Xamarin-iOS-Tests + command: xcrun simctl launch "xm-ios" com.launchdarkly.XamarinSdkTests - run: name: Wait for tests to finish running diff --git a/LaunchDarkly.XamarinSdk.sln b/LaunchDarkly.XamarinSdk.sln index 348c10fb..9611317e 100644 --- a/LaunchDarkly.XamarinSdk.sln +++ b/LaunchDarkly.XamarinSdk.sln @@ -6,10 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Tests", "tests\LaunchDarkly.XamarinSdk.Tests\LaunchDarkly.XamarinSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.iOS.Tests", "LaunchDarkly.XamarinSdk.iOS.Tests\LaunchDarkly.XamarinSdk.iOS.Tests.csproj", "{066AA0F9-449A-48F5-9492-D698F0EFD923}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Android.Tests", "LaunchDarkly.XamarinSdk.Android.Tests\LaunchDarkly.XamarinSdk.Android.Tests.csproj", "{0B18C336-C770-42C1-B77A-E4A49F789677}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.iOS.Tests", "tests\LaunchDarkly.XamarinSdk.iOS.Tests\LaunchDarkly.XamarinSdk.iOS.Tests.csproj", "{5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,18 +44,6 @@ Global {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.ActiveCfg = Debug|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.Build.0 = Debug|Any CPU - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|Any CPU.ActiveCfg = Release|iPhone - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|Any CPU.Build.0 = Release|iPhone - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhone.ActiveCfg = Release|iPhone - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhone.Build.0 = Release|iPhone - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhone.ActiveCfg = Debug|iPhone - {066AA0F9-449A-48F5-9492-D698F0EFD923}.Debug|iPhone.Build.0 = Debug|iPhone {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -68,6 +56,18 @@ Global {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.ActiveCfg = Debug|Any CPU {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.Build.0 = Debug|Any CPU + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|Any CPU.ActiveCfg = Release|iPhone + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|Any CPU.Build.0 = Release|iPhone + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|iPhone.ActiveCfg = Release|iPhone + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|iPhone.Build.0 = Release|iPhone + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhone.ActiveCfg = Debug|iPhone + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhone.Build.0 = Debug|iPhone EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs new file mode 100644 index 00000000..fa931387 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using Foundation; +using UIKit; + +using Xunit.Runner; +using Xunit.Sdk; + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + // This is based on code that was generated automatically by the xunit.runner.devices package. + // It configures the test-runner app that is implemented by that package, telling it where to + // find the tests, and also configuring it to run them immediately and then quit rather than + // waiting for user input. Output from the test run goes to the system log. + + [Register("AppDelegate")] + public partial class AppDelegate : RunnerAppDelegate + { + public override bool FinishedLaunching(UIApplication app, NSDictionary options) + { + AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); + AddTestAssembly(Assembly.GetExecutingAssembly()); + + AutoStart = true; + TerminateAfterExecution = true; + + return base.FinishedLaunching(app, options); + } + } +} \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..98f4d035 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,117 @@ +{ + "images": [ + { + "scale": "2x", + "size": "20x20", + "idiom": "iphone", + "filename": "Icon40.png" + }, + { + "scale": "3x", + "size": "20x20", + "idiom": "iphone", + "filename": "Icon60.png" + }, + { + "scale": "2x", + "size": "29x29", + "idiom": "iphone", + "filename": "Icon58.png" + }, + { + "scale": "3x", + "size": "29x29", + "idiom": "iphone", + "filename": "Icon87.png" + }, + { + "scale": "2x", + "size": "40x40", + "idiom": "iphone", + "filename": "Icon80.png" + }, + { + "scale": "3x", + "size": "40x40", + "idiom": "iphone", + "filename": "Icon120.png" + }, + { + "scale": "2x", + "size": "60x60", + "idiom": "iphone", + "filename": "Icon120.png" + }, + { + "scale": "3x", + "size": "60x60", + "idiom": "iphone", + "filename": "Icon180.png" + }, + { + "scale": "1x", + "size": "20x20", + "idiom": "ipad", + "filename": "Icon20.png" + }, + { + "scale": "2x", + "size": "20x20", + "idiom": "ipad", + "filename": "Icon40.png" + }, + { + "scale": "1x", + "size": "29x29", + "idiom": "ipad", + "filename": "Icon29.png" + }, + { + "scale": "2x", + "size": "29x29", + "idiom": "ipad", + "filename": "Icon58.png" + }, + { + "scale": "1x", + "size": "40x40", + "idiom": "ipad", + "filename": "Icon40.png" + }, + { + "scale": "2x", + "size": "40x40", + "idiom": "ipad", + "filename": "Icon80.png" + }, + { + "scale": "1x", + "size": "76x76", + "idiom": "ipad", + "filename": "Icon76.png" + }, + { + "scale": "2x", + "size": "76x76", + "idiom": "ipad", + "filename": "Icon152.png" + }, + { + "scale": "2x", + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon167.png" + }, + { + "scale": "1x", + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon1024.png" + } + ], + "properties": {}, + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png new file mode 100644 index 0000000000000000000000000000000000000000..9174c989a9c8b8a5ca133228f4ed7c173fffd2ee GIT binary patch literal 70429 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 z zCn)b8&=oMu6ygwwJfdasJX|@L6?m1Dv0X9t>JAWO^UIj0#&(3UrHx;vP^3g= zL{(XT!?`D*pP8)WoGHYEZZc$!odTzb8n)q0|88*>6P z`?6&CSv_W7r2yF0beQ2*?V^_%pKktVAo`)T^26X@NpK_*-ni{D7{Sp{C0A<|16l(; zOL*xGW|*sKsiwHvE!h3QXe@^a#6W3}8!DQu-h?A_4gkeRYkt4NC~GR5P8eyp;9kVQ8$QG$5ad7Fo23Z~ak1jY~RXG{v?3G$RarFe`XePu3X{R+=mBOw&X zks)|Sc$RcG-jhn!`~-x|vg!&DA&@}QH^RNdyy9nq56yrU$^qAaS+F_NOaeFb)CVaH z?!UvPajgrK&zqdAs>&Def#wkcG_UhmYOVw^M`VZz@+4IWAVzK%`+za9rm2SD9={u@ zlx5D6UDL;lc7#9`+%vnlP3PescU=N`DHQPt_N55GNBMkVCRMR4?fvp zAFsvcHN4c9rb>J@{*IH>RTr9de%9i4Gd(cbFa9SP4anhoP;TA0!oZyB8?lNMDHPHK zCaOaFU9?x2A!o>p>mCF9r+hKs9Czu_P1l$LWU%}q#)=T3p`ZnYyeHmsewqw`}L^4LuHqfo+CG6<2n7#l^3;H^^!1 zsaieYFnN)Kc7Mv}^xE)4kXUw8<9I+jMB@QV9T9I8haLDt1Ne#exWUfGYG$4uMoEu& zo81#2up18Y40h%tIsOZglp(ltVsE*j1~$lVd|;rN)&${~o~-%KZnJp&3|OFR{^8E9 zJ;fCu53Ysw%}@VYWE*z7r)&4P=^B-SF%a@>*9g84<4aFUZT7x)qdsS+#2tu5NbpU@ zg;EwV)l-#sK>#r9>(0Figx{9lKm>KvRj;y<8 zc8SxMW4<11(s@QMV_}n9MRzA*62->vzxmHh1)GVASEJY7LVtRw`Rv{v`(Fuc00(&o z%m>gS2aJekmdNQ4p<{pD3HqZ-%4hdU1__xYhLi9mTJXD|E zE`t6SX)}l_DY5vO0Xrs#O6_DKtPKn0f+e~SprDYmJL_`<053iA5P`zn z4<5etc%aF58sHFr#M;U-9|=;l)J#Q2vS!Q9(d(EX6fubL%uA_lqa2%!cpNIv78QZ}Ayo(>C(ZpsRtKhzD--fpuoCch87cX-Bna9_{z%$b*dHM0?+T&Hk!+^UM`r|vq z2Id$??bX^|tfYaE+h#Nik(ZcN+wt)28q^gWe!y8jDCXrD<2qV#49x@5$8&Zrd5NTs zNYcix;9fe#PQQ;T?!6hG>9K{K+RCPqiGc9z%t{=`QaX>7O{l(+#7mJ1>Rae^J?82e z6cLqLypskTCyu>uc~$0-XZ^1Qvhwr+pKQ#CKImhGu*MGM*ZrROuAHWuT*yM$ieEy8*KLFMMdLZL|D+yDmy@3_PELTEVMI6nwfcYA3ZQ9wwKdtkT z;`;z7fU{U6>CS7kr3=A-()_G*G(Mjf2wXKe

Fpy)y!S(AQHSG#udd_8#b4sQu!R zu5}IzX*$;Hxs1sgr9+QLeUpi2f*mS@gu1o7j$4a#3eTy87Cy1W(bOxj9-8ZRrIM4o z(cA}65RvU5I{R>voiE4hq?IR|Ex_{-*@Npqt( zIDp!L(vSJ6d4kt3bs?%QG|WN<_=G`~ybhL&9_Y*G$dd&gzIVx_>J;7D4C2nuwc4#) z5oJX$8=Md9e*Hi8-uf-dt_vH6aex6-NQT&YzDk9kkAV%_iab>#OS+YuEn$;$M;c(Sd)J0rIbX z{EH0#cbb8K`3uC+X#dwI2Izf^0iyroYQl1He~3Sp z9Fx@l`8(iZoPRI=N3{P+<9~JRUupa+jel|Df6(z?6#ZZK@vk)gl?IxW{OczE-*gi+ zb8qh85`ndqgV%nJ>guX{$n)M6qHnj_T$b`tR34FDa`$1_^U?ItSlFw7d=L5&1Cl^` zzpFQD=#B9D^F*$kw;n?UG)96ooiUh<(xCDxFm&rVoixfLVV1D$51WNGgTyb4hxoep zCkq#MwtDymBypp3DCNYLDZkdfjO{|In?8-NU#Mn=$kbsx4g1<{dG1OsOM z^S(GH0vscF2!TPh=BouYuW&YxI~I4S;wDeL#7504see`vK7baJIpAFjE;|jybj?Ma z4DlkjJ_ZDL!-{brXo3m*fPv-j&+x{K#^1jM!aVx;bWXQPf2BwTCGFF=BX2&$R%NH69*WD((3g^WLA>z!2{l#;#hj53RrdA*6k@ z>)frxQ$dTm%&tDoNad2N!Xf?80s~Br8`5}Z{yEctC?Atp>LVRH<6aCCqyi0$1~e4H z1Doqa98wsV*Pu7G$2)Q2?W1PQ=~EW$#YJ&Jl)^*uRFsW|nIg(BdB6zd*<{TqmuLPA zo^UGz!$FSD5FMyg8)gU+$Eg&1s~c*jpE%q4ZQk`@hQJb8BA>%7*oaVDkH6_MBYHGQ zZUcEsfdz{bOFRTmQ8<9w?k7Egoe+b7hez-{|L9yZ6$udpR!<-4Z7Dv-OBZ6tp0M!7 z+l{wR>yO)}z`Bp|NNx89(5?A!1i334oHD^iEAMQaS@h6+VJDnTUjhSjAB*@chR>?M zMa%hWT%f5I+-?O&DF{s3|2^auhVXYYZ5WMlDsileyDcV|8K&a&-!2A+Q*b$9T;oXj--c-MIcGjH`Q)ok9@te@%IVBB6 z+@-WYrOWHLw^o)XiG)?@fM9fij3T9<^M+wSj$qzRve6M>d8 z9##had3h00gQ*I|!Kvaz!2IffZ0b3>j(}V#FnvPc1^9d&my1ed+&Y>aN76hiO@%Eqs96VKz?GBY$o^^fpwH#q8W1)rgpdg1#+iCr0EF;rVtjf zw7>iF8Cn`THv+bpruM@+k~jHa%z*3Q&R72J`rx;f1GEXWJ8`t^Omh5$hE+zu?6bt3 zh~`%ebCC0+-+XLtG|2#~`N}MgN#iY$^#lDqVo~U3r-=3O1Jp4$&Tk4JO40#ojEfIN zLQsuZ(k_yRZ4);n55SWXrvOiG2(gd@#8Zdx0k+CjM{Snl*VKeLNbfI>0gVp95W$nu z>MMz?MR{OakSn3_=nV%L>nG~7E6{Ypfd1xsCDOZsKbFL3P#NTCbs&BEoR}~~PCvM# zQg<=iO-|qVkCN#TW?&3JZmQ6NC#d?kJSEF+zb>aRzEf-l9k#i#(`-dn zwj0K5mc-zx5ne}|QpdFtAQcQejZx`WUCxx*JjCYUEa%F5CiHKGi<=P9kjCvq?9fwF zPY!#0Ec;_fa$srE4^Ggk<^X-Iks6nzP%EE$Dxs|Wt>iLg^#o`N_<7*~DWdslTgVQ@ zh!S}3u<ENlKq+q-TSn`EL$8o-Xx?;mt>y*II$^sY?%|IpsMJKM2n@|(e zUP?K$u<1dJR%s=m(zfKxhEPHa)%glroTVwsW5*W15P4xVIzqL@$uJL|%rQAgIi?8< zSvM~&uVB}GslE#pNM=jTi<_FDso4 z-@4rIQCatChJo82=z`liCm4C5gim$n!Qr>%w_OhvFV#sG-=go@#Y&+Q2S*9;&BLR< z~}{!Wy}U z8_`F+?$^*Z^by#QI*%L`FzZ@zQze51UO&x>cbb=nR##w`9QA;QIfO_P={R9`2Rp(rfLDMQ(B{q(EVQo7`mcTJf(*G=@5L}P&(>C;=_BoOWV_6t*|bbun_f>NP>k-{^n=vBY1a^Y>kwf@=K*Dmg7TX2CEpC{RlA$f_q( zpl-r8;B0Is%E(Tv=cS)z_a642Pyjv9nkr4Dlil0u-u`PiXhoI;Ya+5_fI$fdAZ{g- zO!x$8FOe#4Xq_VL(4ldZ6K7|#Wu)si3yYa$w0$_sM2Y_DoHPSt-VOVw7zFl;olx^y zwZI0ipMIfL(z$tYC-xtWq(X!94&ej8uBS;FS{uSYw80!`VgWixCh<}L7ZNV6X&{v= zTqnUw?`XHbnz4Hu3@MIy9MDE5&dz#_h84=m;Tqd2TJ)c-fr1!@REYspQ8Jbt)lF)6 zRRn4mSC$@4Z}iuM{kaHCtPhvrBgU#7iTiklozu?pwF=-sp^`wHG_jUhgD{%%1%zXR z$>>z^fr|E@HSS8ydlS*H4tGy>nv91Xy@LOSf@wIY1Fa$};7+I*hx z>;&@)p??q{n+uV+*nurX_SR}=RaKjUpn4&zk+@MQh%fxj9-ht$}7%YlR;J{<8|HEBndHrI7}l zR)eq*+Hwf%pV6p7pSgWz0VUzTdboM&t53cr?AoKH>Hwq)Ng$>O!hn$BjYymD`0U9{ zxaOvHUcKc%rv&$kBlysd!=B_Mx$}vsUKwX=&D*>5KxqnK&oG`!2af}tw}-?%b`737 z*Ze`Dg&Hrq*#9FRahm&(Flh|eWQ3ROVQiDkA+G$e1rC4~hbRCoAx}>*f#OLU4_6HH z?4bAjf@yd0La_6g`G%73r=34S0>i=-SuJ2_A+i5-*0}UN zRQ?aRU_b(Hqa7!3OZB-5QRjA(iHO>`cR(&JV4YZN`9&i{Z54)2pkXfPMgUGG zqZNFca5v|LDe{Nog}N^n7kWC=Xp5}9KlKD7AQsC|4W584*?Y6Ti9arf;umlHnc@plG{)a2)D@_coLpIbE5I-rjsc-10Vr4* zH256)mD6Fo(6(}So`vb^g$l!#ELyuQYQz_!{dE*4<)z|#DVSRnCTl(Qo6+IjQ&|9O zf_lw>QRs01tV#V3*3{`m@u2}0mPm&d=3U5&3OP$mm%I{g7(cKI1llT6s6M^(XMt$C z5+xz0u?s@KH}s~n$T8jo+JX@fibWBEQIS6_)*9h$W6>X4_XoQI;sFh;&i>{M{AUFs zys(PLxl^k^UB9c-vVPR4jnee*o#{Jtk@q1fO-l1~H#V4z;NBUuQAF6l8dSl239BTN z2_Hxq{|H!gYA>wQT^E~2jRf|Wi@K-=<3J4YcZC*f>QQGxM$ivkq4$0u*J`qQ2tL8U zLm>kXrM0|D9oj?oU*MrG*AOB}00sJc+Igv@%PUX&U%$n)Ue8HZrwr+C-S{9{!!X!? zAao!0d@@Wsn`_|2D{G90=+MtnqSVDQCjIiAx9|JQ)CnU4uDz7$cXO0|T}T1?of#h! zG4(oJD%BL`{yqvWj|NulKWu^wO}#&K<^qF~n4f*zv{cs=&VwS*c|Q=??fw!%5=U^n z#c*uI{qw_@<$$Uhn;h1wOuCis;rQmFI1HLv<}4bt#^x0Xfu3;nqg3-VJLd2VVbVk&aedMql$jg9H#R zl_E=m14E@w5c~Q|ql=@(gT&>(nW(GCda2j~6+*6!Nq!0hR&g6B~vChl<_^~MzR@^s^~laKwwK2h9wo)QU!C?+R+ zCD*D{sQ)G!a@F=m^d?G5+xuP+t~c7}Qrb{S6>eVS9c;v|uRty?y+^OoQbhH@IR$RV zhQRPt9LPG1AI|k-FTXic8P4m0EHgkihzT}0aS=!rT^O}?m5{koife|TrV?i3WqEfF zt;T-?SAQJ0XAo<4RW;b%N@@NbfoLrzxsO>W&v`9sB@z(Rrl?&wPsC|vylV;#9 z3Hd=pHpd^NwvuvT;7lmze47&&LvRoq(Ph>;Y-?B{-FtaW@lJIv42hT0>e zS>>f~I;vf1TR+ zpF>bH_iDyE_bF#4A58(bjE|*r?XMCNJJ`9+eiPDOpTMaLPMLVm<{QdIJ(DPDsw(jO zB@#w(L=kGx?7kc1eg0!BI%fRA=d5S9Lz|&Fw^#l6GI{dMy*z$q&V?n6d`+VE_{HJN z_hyKkYcPCCQu9Bq<%E5Q*hUzz7&73=>M?@+==>2mSgqBb@6YFE7ZoqLly2v1GZ5Tk zlgD2UCSQb%w%YKTp=nFStB`z3tjb9b-p@~JxAtSMK6u+X@S&`JAR?s)PFF<#VtXop zgQ*rDhWj=@9JC*OleaZ0c$;lCPGLM#QtZ36;>H;olmavb_82|TV_@C{st8u0+ zaa`!aXwGn}F6MnflZgv2Q>_9PGJ?xKf%zyk`^pH9723~yy!gKK4g=qfwR>;0hQWUD zu0C+5bL7TXupn{cs|mfNMu#kob^#wj=(>DEa3A}CP47Rw0OnQw-W>FVa`RNih@{i_ zxS+I9MS3TpbmCnzKN86VFIs7|xbV$G<7n@0HDJx2CD_J~hdW~@aHr~GMX&XrzRt7q zN#MG{7Q+?3X|Lw~{SC-?fY><_OM^GqUk*}suQZyVP&zXx?ovtK+JwFD)l)y>)AWro zkE&BwYwx}T`%6?!D)P)NTow92WKeET;G!i^aDVxS$L*m4C5fS`6<__VzWK%%gEFAi z(eic9Yr`pk_}2j9f3WcjTSo3F$RWf7%2CHKaT9`#cP_y}d>H2(BDKC-Z#IcwD2hyx zi)tl3omCZCE>-pExn3Qde{Zr<#kYJJL)xFoM)5Rid-3$M{l59s#yY?i9_bPv)!U2L zIb%)_-c~Ri?|r<9h5X*LN7fqTho*kGz|pusf#+45KLZ(vZ&L&hxvkXNo2i+A6gz!W zmyMWjaC3E-(Rm_MMv8i+&;2P zQ9Fw{J2l-{iofm5mR-Ua(wqzZ4&r(m5KH=pgE7f)r)#W|*@fLdc3w&Im|bO5z%uN( zT%R1jMiy<#B;9pTb19=Ph(0b`QcGYUrf;?VJ-l(cMsTowU(9#z0sVqJ;|IpG@XkEL zNr@M1x7oiL#;hH0@Xu<|Roaa4uNw@FmBVlW*6{$o7a4qSM;h@UAfMtvk7^xiN=>tS zX|8-amqHoI{FFXqe*XsC8fH2{97!sBcBqfD zluUU9EMl85-e>0)rjy3rW`w#-M|P!JHxxz&;#t6I22!qz$s%|H3LBp;2o82VO_9!I z@Agsu#e^NP;kj<-;pid35wG<;a&DwM^bGVXc{dmM?eP3wkU&GdAa4F zTmVr<3U&`9{n(Gp&C-U$y2yn|e^4sZZw=3e4?h(kVVX%JE(V6DrECo*Kfy|9QWrOla zv`d|!qPbd*nCeBaVCpHaa-LlcqkT`BcxHNGw^k$gr&T!GKSeg_IMFq6ny;u^FL871 z2Y(=&*9_Z%{urDZn88~C$&wvpvGitBkf;aL-@h9S^c>sN85SfxPig(S({D?#%Qf?b zTw?#0@D;9_RI6~YV(Wuacs~cy?)X+$bCq3hS~N57x5?$Muaw;-y7frJgx+J+-2G8U z(o!3xxr#jz<d~omhGW zGwr7mhIS7q5|*{7O10p!8?l?i+}nv!rW>SubNn8vb$n;1`Q1x9GKaQ%hfE?U(8_8l zfCK0L!lzh`^p|p%BByQnX~-m1A8#w%CTR>zE)c5!Dpa!XSvLoak3IdNp=Y)A@B#jf zGs_meCexK@CFIe%;Azr}h8t@9!0lgd43*}9f07EETX<^sEbt+QfS~M3Ci&3J2O<)* zb|SB)yizc;2#$EShmul)8=1~p9eqG0wMfZ}lP~XDkOTVfPYk-%+G3cQ5({;W|~nfx>CSmzkxa+t;rB?E3v^eedr3rL-C^;)F>DA>QNA z80IU`@o~9;vU_IA9Z^?ZRH!#oV_G$`T<&0vP&a+P4|1w$f<>Eu<@1OaUoy}@ZP29F z`6zgXAG3vjqWUf6Dwj`U8u)Cz_<-+T+DTgm= z_6y!dAG=c0ww7OnoDM0xSMHKEbPs7=QZst_a2=mt8pFqO{E7DU=wxgc6+imsYnV_7 zI*Al%`B@c~%~gJ7Q=+I@$6OS?Z>f6f+58QhF>)9oXvxr!w)`y2ZIY6lOmI+(PJWXs z)IM$5^-7e>>{Gdef7zEu2pd?Us z+DACx+h)&tdC`1Pj_B!kUKP%GPS&?PB9b9!a<@S|wCv29-4VT6t1>dyUDIzHNupXh zWv7%buPFGE+fqj*vrbPXQ`?%Cr0e+HVbRdTfpSwWh{xj{uV4er!MaQhwLbdoMp$e) zVL1q|{Y4s>)Sh5nW|WFtb~RW>xPDN)yrJas^qUitGh-%c>>oy_`%J!US`?jG@{mcz z&%kp&H_ROiNWIKd5xU%3dcl*EAmSE#(yl!l&paz4;U`y8w986>b+D7;%h0#6@r*{L zhid27>;%{|s|Onypod58d$5CbTsB7!|LL2Ene~3(e(xmq4stX>9bEQRUd8#QW! zR?SP#gX{1t78lCA^jqIIOn-$|@MN1f;_dJ(GS+cSv6oSo<)$Y_Dbr_xti#XOd^hPy zph?R%<}Hp2xAApnVh*dOhYiE&zc2sZNE?&#>$etK-g67zYnqg7 zOT8SFNM%7TG~=z0$jt2YzmW~ot3N~oi;7b0TYZj>I$T;oc+r2;p-QljJA~!(K!BR- zOgbdc%#F#P|70hgB}P6AdFg;1emc4Rcj~Ny>GXZ=?-*(u0mRO4Eb?(5NwI^19~U7` zuhxI#vGiE$bT7S0pg8XD`Xrc?IrcICV})_HW9MUGnpK@NlCTcyywFHF)0}s*itgZe zAP`}(H*wf6klKj)?q9(}ygE$x&DqwMerXQiZ>d~c8H6rT*TrDT;$Tm2f4Yx7@uZ@uMF345%fLz6@=nW-5>tCMj ztq*HJ3SXvxNJ!;EIqlJO$u@^?o%Av3)j#BoFF9SlDT+&ghJSXtPq`_Est;=j4aNa zl)vxXehOU34AXA6RX{zLPPO=SS?%3Od)r6m0sN^77X#y11l9>``1b5|W}n-ynOl!8 z`GGKT>-pMV3w-pvTjsA_g)5%MJa5le)Rg2O$OFXD!R#wNi`Xzoa(HUIN58X?1*Nk} z4;YFQ;+?M|i>fR7-5@

>5(9LhcY!ztnkkI5XJ=TATRVN8h*{Z|qWc>i%d;x4Lch zg+u>_GVkY_`;p%4@D?NH2K%7_QltJD5YmN#kj_430w2PG_uowScqXyRBtxWdfVWW| zFY-NQn;yxfndbEr%7vH4$c(FS5Pmhqywizk$~)HR5-XnlQ8E1~lw>Uj8Xd75BUBeP zXJp@BajwQI90B70TNvlj=5SaFiMs>HdVDLv32Qz|V4#l-7Dpl+ja9?hlMHCFJQ|X0 zcpmMDfJRc|B6X_DMM5j_6c(1E|1@iBX;NuC7P`xDMXUS z>z?2)d@G`ca9cd#iLyDNg8xvoN4KOgm{mN9+W(U1%}B#CM}=ryQ5R{iCpJvAgH#9rBq51{>M|L#QOkJklJn zF!-0_uBja67U)0ODhhkMpx#oB8ZvaoN(soi3_|&pk~57T{kE|yCzCotCDeAuoT*?} z8m}$o==c=YdH1=m50>^~mGJGSA8y2#JWQ~Ou2UJu;^8tYB#Fa?^l?9-?*2RB--P>u z=o;iR2vi9_X$(FifgTm+N!Wr9>Acq7e`A};sN~MtSFLXq!Jv;; zwAa|STrtni>R12Hn5#WT6mleN`4@)E=eVZALetZQUNtv6M;WpGc}ad72<9AGL+TD$ zcnN$58LSO5Yu_ z+!gbYvAde)rftz2YFsz!UgIh(bsw+kI~oKk-xZW28Eza zx^BxU6C*c}NtXt=-8T#*nSKzzx(yDGZDUR2(uqTrHzM?rcLSCJOt@TS3;t$i188RE zY9%qac+cQ{TH<;8OFFBIw>x2*9O-w)*o5S6%xn48JC+I8ZxOa5F;?C&CDG4Qnf!PE z;}Z#06b&H_P zgZ(c0UujIMBwh_CaeQP|t*^92I74vd5(-P*+I!|To zlu3kIj3weJC>|NUsCo#s0XZ+@C-K39>H(}Zf(NmyyiZLT`fM+Ss=Vj-R4+x4m*Esk1l+l2w6kzgx3I6+{RODZu*%v#Hvf_Zx|UOt+Qy1J zRt;Y!T}mblWRB!N0y{Jrwwqy4F3ReTM?jjnrm^nc|E(=~f5dU^_niQ2O!J|_GEKOf zhDw9A2DP=ty=hRp=CJuCGz4Svnb^W${(q_#xW@6?0Yz(2PW|D^y0Qfc(V8b_fLOv( z(CH1c2x5<=$tcU_2?e6Yunk?(WvnBK+wO&FcVoFlz)OneHt>7?da=%)Aj9_4HA~GS znW#+HIa}UN9~J-f6Ul_kL)WdXfA|^Ey#iWF#Ro(CtI4Zwhe0hdF;a)KRjfOT1U_QCsC&5SoPUG(mbID!ON!D7dPu!q^0d#4Z| z%TYF_==Q6*q(6Dvk*8L9mCP!aJNLAh+#)u2Og39)4$I~QY38QOxfT_en{6L^JwenyFqij%B?$unY4%>UB1GrPIJQk{izWNd;s5_E(-0ZWUcB*QXt z(seg!L;P3h=HdQ#6;4lg6!EW#3iTa;u{6>y~EU zlkCo4yluH$9yikaF*>*W567C|LNgO@eOF@f>oB?4B7_ z)h$1BsHaf2CW85cOc4e{08@~H@xm5 z{q{>fULt$BTNp~2E>Ew1Z6TL5AEUS`UMU3u3YW96&bRc;pxc@*ahBi%LBIq2N2FU$ zPRsqbFutb9Pj1Hmca}asVe5`{+1#WyMSFHF3$VaI<_^T*}*oQQSBP9 zpUO@_Y;ppeB~uwSP5|l@U0flLHE8yPio@p|53k;3kVE>Uo*Ai}4(;hBArCgys5R@pRn1qdYJ_2_emsBKEsA=t?Z!7-IojE9`wOVa z!Uj1Cf1kW9{&?~fdC7epU?uJIzd5J>Y$%$vPt!~XXCA_`X)|!5g0D1*(9M-O{T3bz zsOqP;k$y!D;gU{o@1z^~=x@(C`B&9`oshPC3}M0jE%XiNA&bNw9Vn!EhLOQW6ZuZu z(btfoY$XR2?U4q2|9G-j-^gXCD9T;lv7@u2Lm};bZQQKo!Z>UV+yY+i6;x~uw zNHj+n$~KPF9=&ITe~auvDz1~Sh5jz|Bn;P3*+SJHYqAg`KK}I_qb@R+)?d6465RPh zF?A9|xdh#3U1D(rPu)m9dtR6uPBZ!a^pXs3+kdqXQ`U_cC?>M0uf|}#5|+S;+R%HU zIX`%XE>x+4dXkYP-6kBQi8KWmuZwNAzk+*UrEOvOOe)Af2eQh1b%;E(e%JN>{d2SG z@9)#*=t6F$TJbX)a(vJRFE!W!O=o6>ecOK43*~2SSr@_{WY>$Q&J$(9jBRppdzb>+ zOzH%!i|SYp_CVdh2vCe{yB};sPhpB%g8>o@KA523I0f?+sQF_9l~SDvt~@k_xlcY6 z2Jak5Q!d4sm{XR$S1I<&_=qvs3`weR4{Y|{bsx6pozFwD_L)|-b&c3DQo(O@Fw@e^ z!(6nnS{)69_^+1OfZ+nlL4(C$_(r@EfirseWrIF@iFywdi3KSllFr?_g1bKHGj$+_ zUekuq9Ij}|W;z{KZJJK4pSE?v_wDjvG{EWpinH&EX}T$(LXJ9wmr`=NU0;(VLUjBl zj&+5A@`tg!D%Tez5B^iwq0c;!S0ARhq1JPlJ!YEbuSED1Yy#KJ59VKI?TUI_ryQ#m zIDRsG&WR}Lrs+51g zi_4Go{7`|Nnrix6?m+NuOl{!&>%Vr$*pQrr>)ls8$b5lDlt&QHfqZIDq@Qg9ov!Gy z(_r4axVKl{lrk;_xbN8JSDTTW>_$W3$EgIFVFipVv5Wz~)j zaa=rL%_)oZ`6^dpgYcs3>pC_%LBOJquzK#IUd@Gz=&(P?R1fyHvmj5W(VfPDi9Cn& zF>u{0NQgLOiMvYTUAeN~F`O?d+?I;C`i4szDX#bo#&DbPgWRL2@2n-lQum>dYS+S& zmP5z9qla;xQ|oyfd0W-=TcJlJZ-<b>fF$GZOX zj^pSTc*CXx5u#oak#Jv3*hQwbwrb2WX3vvz#+KdQ)?xa8nLW{WSJijfjAo5iM*+TE}~ofmdH{p401I=UaR+ z&FLsBQ+fV6YF1mZ`Zd;4X{Eg7e3LNp!Tb)4Uj|`4w;UdjN3RD$`T?F2sA-TdO0geEAGZG#?mUs02;DD1gVZa?5Xz^2s+AB&lHE)e+~Me5DJgc?8OFUU?7qjD+_krnW)3>-;4Z zalkcbsA(-}p;5#}>4V|gYmIVbgu9{s9$)$O&MRFf*ppJCi(P;BYo*1mTC23bny0AM z(1yHX42~_kAFnD>N4kdBXeC?HJUKu`qF~Vb0Y5+oH*s#c&GbqvO~fExW%P`-R7arF z2ucT#N6Ko~4f>A7;dteua@CuXIsBW;(iZHxEP|?B`RT2RTOG=h5po@j4=D8DNuxq4NLxlJ%9Ae#Rks%tz(rl;TPu#2%2(At=;BOpLn!bQ z=lD#XU>4};GK@17DMY%11h@tO1*x}DsWiKW0o0n~oo+>xHUcbu_TA_BnD%%Ii!oo`7m9FL zxN}?S^&9akh))xWN8b|3FmoqX5?ww#^<%ZQ>l zzZ-BL|F_UM9a|k$>^w}FDoe3@n-GS7a8o7_6Qv~)@6F4lZ%uCURn?@S@swYMe{!?* zBZi@l;hv3G0y}u^CgiIXm!98O7^flR*G}xN=5t>U3;B2Fi@%fhHt@FSGFkvv+EQ2a z-y^lK*@?xXWX%ysL%I_a#?tS z+=1L^a$4L{Is-xY{2g%wc0ix_ zGgABF=&Xds1*gu^8?-}ENG)C>b7))3+GfJ7r-qHQRZqqZHlD%HwS)CO&#DIYJKXd1 ztvMU&!9}_cKGKf}bbs`^?334vgkEQ^@AS%(Qiz+F`%TmJITl;fRJw^dZnWb!)hEU0 zt$=oxGJW5NNvY0%`535nX@@WEfB#b@+YT-m&M@h2V`G7|yph;_+V`{aw{)Q=J+UsR zxhm&svHR3ht~z6>HVaM9&lE|F*|By{=jC#cS3Faqv@r$dDY`^jE@ z+TZ@FH%dmC%Jy}hz%l+E=8(2;iFXC{OJFso9u*ACr_nq*R2jaU?#lmdp5VcC>u1w%KXwqlo@#cW+ z%G$T}tfl3MTUO~=SNxtbt;jLQ@ujC{L4+BLSafh^WZTKBi%Q*v&4$L#z0|xwm<7m1 zi^oV2zdM6=QGQzbvQ^Br)!H*?(uDODe(P}B{Bp1O5o%nZ;kDbBqoYd|r9khZE+6Ty z9WiI;x6)$^9yh!C{TB9-5N#+K+tapOOT$@kZWW(%^AL|OWP9to62SI0CH=PNnluof zVUHlUkj%V;b5X2~AWdqaY}@1z7%g#RfapE!AoaoNa)?!Q1GDgh>Vq=c^5x+(tuPXft;)3;pP^snauj!w+_{vnpt1!#GT1d=)Ynj4WX;P9 z=Ie}GFn&$Igw-EDfeX_QOs^Agk^EdEP=)R)j^Vn{Kz>QQdT>88KFOuONu73N5ytKYJDM}ny zOX4lw=6)j57|G3+OqY4HBCZQKcU$ zisdIIXB41LjN;7?CC>&)KM21#rykGib`y&w*?QbHhWkA6DuzcFxG;hiWtAuofA_6M*%?u{M&f@V}Wz zN)~MXQo~aa0+f+EUW{5dC4hw6aQ#NlEdVIAg#&7wX*QvY5R}>f6Mvlh0dYf^$j?9Z?!XncbVk(Cg zd<_f6S7d9)yH3@6smMHYMo9*xe9iKhm^;i4la!EQyHdZQ!TvYF!dyD&xZRcgb#p`# zoH!si2`)#uT1becYYSXIw}&2B5{<{bIq@BpLydnLzK$#n8re_r@P1W5P1HTQ$K3^9 z3AT4IyNDcin_WJ+gV_6`kU~o+aFQ-2s(WesnXh!x@ttUkIE^3}Cv&mGkg(jqc&>p| zw_!O0Dk7ScxV8I;-y)!cr0p*r$LIU&qNy&t3w~Fqd)|&vR zE7Z$IE&h0tx0LLq;^g&V#bb=Nd3UwSJPD4I_o_p^%6x;6l$&N=09bRHf&k_w!@$o{ zvp6m5U_KmOre}H08@Cj8aI+{zIFk5fe7UcfQonBU#rGF=H}N@mxWPrWJ--4vRTum> zDwZBNaimTC#pK@O($#e2rb#5GE8t=;>0{|4Cz@?Ut}(Jgh0%%mYoLKp3zpzD3Ek!j z`EbLHg#?)v^{vJ=Pn;{OO8EP{u!PaNUI&BgPu6$yYBjpk`)ZILRCtE{UOivlaaWzZ zV|*K(oW>K{(y&;9lguf%9)FGa=~`>Tr%ue7C_b)(p>tu1?(>q=WP1FnHZNM6mr~;T zV2zWx@rY(72R{_%#F%@q#QSmLodI_*l{+E_)~ezL%jJr_+rGOmnbW`Br%GsPkEC#E z{);Dy;CZBQYF#uxUin1)Y}(Nh-hnij4GH{mUfk22aljj<(gA` zUXH67gEx5nmW!tv=BcxTby1a*C)kx4snK|>7uc;Gm7lt|W+|)`H1MB{zSar7Xq^zw z4fTq+s(7G(ud8?CxJFRKv8*xf6au@Ud-hj!U&%`hEG|&OU;q)WFk-FFU!Td$p z28kaY#uwbA))%RY@Dni4^fnzE(cxW9^zkzte}RgKQF=bQoEW+`7uDLYCmydXAD7}iFK)aj|amP=*Q6IMO&QF`loZN8eP z>zg=?-_=rTshoVSk-&vB;%U4l=@lYH>UOe)-F6>IW9WzT*z*yi$n_sD3ZgvsZp>oS zdO{z8g9RCuFLCiPpJX%)QyVgs4^n5s5VS-j0UckiaE4{Nb`p9%$_Xh?ttaPkb@)tK z>DKnHd_TDH3{vTh)Ise{3nzOKrmu|M6`+sU61XQidw->k8K<$1;?kxdSW&uU64|oA zf-FdiaUn=LA>1tn{JuXo4behp+@sj)z3*FKw>?kBP{uHm%YTM#;)~MJ`gHD~T&l#; zUYB$wID7xf`dAg)#8LjqtHJf~16AZy#I3dh<|E#w%^s|Vx;L3IEgQ2ICHA{>jGLrO z-M@n^XJIM#iFWhBI1!+{tA)AzuXt6F2;o%_A@@Bh@QSN1g+}F;QplfGQ9SNkv0|DC z_0ppY!9cNyvaoT4d!_$gLj=da);*XMRb75wx>0#;`kHNG;ML$l!g#_g-9$l_2;pN| zy7_;U-Ai#ksfz?DxkdgF<6gH{^|!|=cfxch(m783r}>ZvK&E#O?ViW>bKaLiib>=D{f*r>+3Z~Y&wYu$=#iP!SQ4KuiYK>byA7lgP+&A;j zPk-uuG~YwO<&#&qlhXc#)EmJyT^Ac;r8$(xa<&T0?Ms^B1Zlo-@uhjV`}9+kNig4v zod+DMPq>>4L}xnGz!tUIwg;dWm@hKHiboet;fZ^rSx zl=}!A(G3UsKL2xMfE)UfYhZ;SpH4hy#0{GCjP+=`)aDESi%0ipUDFQ_2I*g^TShS) z6?^S;`D6dIG&u}4O+M=U67u||h&YdIa}PV@11qFhKQhnkA-s*~;-f3GuTzF$YZW|A zQnUE8LGx8de_Mo^O2Tb*=6ZgLMsTj|8=%4us6=K%{FxwbMW&GB6#K;`)fjIXwoqVc z&7rEuWa(KQyONf?2U@Z(*!A8=mz?cwh_8;un46CF(N9mBZVs-5xApV~IlA_+h2Sr( zglf3Cq!@zTJ4KL+_#PCON;fYW(C$VZKbLBe37c;n`H|iJi=-{>OiIkhr%`zSb4p_M zVs&a7;E5=!cZd4{+qgVQ48xY>6gqxAm=P6fKh)n&AdT(D5VLDY?G~S-Om7?OuprOU zpWQ1lu+4ppS-s-muCptXX^h?S`O7Zesu)!b?~9 zxD2f7PofTDKh12_6H;OPB(A!ut&z0dd&%*9IVBhwr>kD`;s0yz%KxG6qCaC~jj>cj zw(MjH*_)9qd)n+v(Sj^lLc+*X5hGhW&*s#Q&dPl#Xwz>+CT-G!1xsfq>1xYAMoLzf+mJ~U2kz0*M=Vt+YuTitx; zLqGn2$J*dwT^WI+R*2_lryN?ke%5C^e|&+oqx=w$-ErqbgH`ty{L9dO0g_DPD~*$5 ziq5hZw>rkyKz$%1T>{i4Rq&oY2lKk`l9(AS&vTaL&$mVaWZ%*|jX%6NyFUir*ZUV( z3ylS(wlt~T*yli)90}n|*MFC^-!$SIm#_I*k0)Kx&9Lb(FlF5G4npfNKGj-QM%IG@L@dvT))O}WADJg zzGo>ng9Jyy#qBruRMOnWy+Kx0HbPdPbrrB-GfoLX1fFs2Rhw$xAC8BY;GWyikss2L z@r@NR=;&J)=d;J{X4yKPX$#Nj`z(t0zpd!jW+;d((~`m7)9g`x@K#wj(XacmO< zgX6Y(BTBtB`Qn#e8477U<$Z}$%`R6F>vZ#SQnJU2WJS>o5`nvP;X?>z;IoROa6gX0_OCm~oxO<>vr!l4*0mln zyLN|JTic6gxEaj}`uLvv<-a*|CzQ}%^l)6>F}bQH)Ly?m^g~+Ove}u~@-q|8HjD?( zK+WX^P|3$B{UWIO?b5zylbNc&vL!n_I?Hh}-lU+hP%?I{^cuXpPV=Pn*B+0c1b=K1 zh>pn0W6G=Zt)ro1yFj{Sp4URMc9-n*0bFYd!y$A`OUt%4r>i$ca^-4F_a2#^`TEsu zXeUA%G6qXGn`S+s2Z6G-o(G33=kS5|AII|;TMBvOZXDntxf;KAokh}yUJ-;U69Ubnv~=*33X^^uwCCLq!( z%{-rTsbO`?nG31TP)+Fu%c!wWEs&l544Z23T3Z)--Ktov$f}8@f(>m#z#+=6{b(cBHUVm1$dY8P1bM#i>`KpDj-Y-gg zyIi#D%F!VUD(hV`sD_Rr*~1SU63feungo~*&H`lCnCFLW;Y$z7oEO96RnYM)*Pg#F z?<~xi6#w%&eLD*)=QUQoT5E=^5vh>LMG|rd-)MSwn!M24))za*`tF6hc>&2R)wh0Q zN9g=#H_K0_&DV~mCUH}#ByAnZ`nK_6%~f^D(n|pp`7N7m+fOkuhai<{)nBcam<xY37=rdqG%XS%K5$7k0R?r7+UlSS#+ z1lUs}21Tm_!)-To?np9zB81z9P4x8fGIOpU`!aBA4y&Rp9)^v`axY3qIL-43AtL|= zk>+RxmOB2J6hRMQUOOs9# z1NR3zj;~xXk(zEjr4}Eowzwrl~n1l zm1-=t7RZ^k3hV(}=t*=H^cj*Ea&q&$N{_#lAwhh#&gv*1UUT=i2Ez=z3-#h~2<0Dd zmF&KU(#x&31D_^6_dara@FCq?ZFQz5j=QuYn1?!gZzPL5+G()_azpi<{PwY*u!Ca4 zBlf$Ubg%5)um0ko@6^h&RAv8Et+szopYlbxYGMz8lU^|bItu>?9sjL-LAE6M@u3y& zsO+O!yRi*T(#7-yt8J)8KexL&$$5L+Qn+*aT11x$Nb3@zfiowHz2}@KUJfyKj9^4U z&6-dR(aOuUCSR{i6&J`XKJ?)E7TzF`8`98d(%7(4BYTU|#;9`VQ8hPz9yZrM=i8{R$+pXxxyq)V!l{Gk@7YBpYA9-_^*lsKFo@xBt{XRQ1*($@{d^Tn>QjM| zso6pW5@Q8J_C7?E=n2_gu%s#*<=>C+9e-g}!GRoyd(Fh2dLA`Y0#{AbI{utjy#ZIV zGXL$}G&Y02?Mh6WCVEY&H+i4MSEZg+Z=u4EMbRVCLky21TyU^u)v2AiRjNCDkV_=( z*(Vy`hKK^{eM?j3T;zI8qCKhHwjDg|LSa>e{D=?6slfr8iVNlCDc~M-YL_9Mv3s9CUJ@B{cN3pVOOMTryw&kWsG2q)EjpWiRSJYBgu0gHQ0U%IKX;TM zB_3GLjkyugD~Tuo9ZH{TR$%?xsWVei5!n26R~`}EUVLRCq7O$W~6v~=d~k3DghzYWMWLeY8?Lg65MI zSpfbn7*Z`>b5(dxL}>Wjp5z=YEt2;uke!GUFYG0K>q%YU?kH(ixhXYuWmtum{@k%5 zWf&$h96ko{e!G|Q;23xkyfDipr_bIx2;0#Uppe3i&U*0CqqpoPFDCUbKFQ@N#VGNq zI>#YDjZuMfd*qjzq{WV8J7upp8hJ_|LhQ50sr@Uh*{I1=6@x9y3m;jHRh_+#B zl5wlao8-Nb5tI^0r?g~`$~9!qAez{L+*UQ0odx<6c7y!Jzx3yS!dm;`C>jL=D6yG{ zKY266=kBFqzw1sG{T#>y5u@5B=?>3KaIMB=Vc|N-&BO1w+%s^0fAQdiF3AjB``PzMF@a~qfiVvj!<=Z|-x=QZE8PvHeQY3r`M15~^bmtOxJV+f&H zs)#NcfPKSN8NL+;jv`s|P+1$0AMilD;|*;Klhcqj5MVv{^to8=KFv-CIs=f@Fap<- zH_?~z!Hl`(WhrM$1f)?RD;N>Dn1vIOM71}+3*qZpxA=)L0$eGNhh$)CuGauBv{OBx z((0rF`9tTUO3xXt*Hx0x&&}0UvH$xt7N(4I1aIic&AfIf zq#s5RKIc@r>KCd)bW&;f@h6%bFXKa-n$Qf3Q#~ou0Tf~KfuSeB)g*+ZU6KrVBvH=X zv+U?oU)p6gFBZpz($}YkoJ92Ys?7gF7|!qEc#I!X)_rC=dK|juWt>;Uij3@3v_{7i z$ozc(S_N+VFU0UV=}RaeO9^c_rSf{Y$d{W_!V)@%?+|$D>k}dDh;$^45n3cUJbUnouX^OBO$+YA#i{$_H3tnDpb)n&gHG~rW54v!!_tYcwYkmk1 zSGr_^&u)A(ka+O+d7!>i(PPkF9qd!wS&r*?tJA;|q-uXI5u@c+ib5}gTXrz)fH@WN4)4{HA6)8?~PCa5l{FvVPEsX!lt z>;^~9ZDu8$qVrV{Q;o%U1i!o4og}tR?)s^~f|uSWdR-9xmxb9Dv_7+>4MDYkj(NfJ+L%>?~xzJJD^*m5nyv#Z2dG> z>Hh&;9heh=z`61cK*WHP2>z+Fjy_0Z84YM8K=}NY&Kd-XauGfMg)_^gj8O;?!EumE z>CquXc+ycF7j~ruNo7f+{8W*BuYiFj|6UQ1s?8q^q*1=wfTlK#S3w&Ep4Io%re`Z0 z*$|tltnYJzt&@OTb&lLt23MM1PodvB+RK|Xa*abO6VWIP^tL7HsTmymvQSXNReI^f zAOH>d^ja+N@(Ic#d6O(nw2eQ8uyBBi#asmyJ1bqu38VT0=N48ZKXgc@2SmAK(8>av zM^Mgnd_l1VuM_&!o#IGk5*)G7LIj~<0uxNB)N=t<`GU%c%9kIvl`vKeQdY!IJ~Cv2 z4vnM6X$}VMK7^Wchf*vH(%&y*-Rnf8E;3X;_`+>SH^yUe3C?8F^a12N-X$|Tlc?^4 zX6KJ?&a%UD69d7jSiZz;fNkwyUEhGh=aM^TWKbIW^x?RO4&-KrY?S%tud52yMdTP@ zxB5Yd*R2Xf8Fj2bGGM&)ffh>V;Y#NyV|)}mLpqJpW%i|Wt?<#4K^0N2QEg);1ZdL1 zG4Qi%orMPhX;|gg-!>5%rE-9er>M3B^oouO80s6WPJbls$HCc2h-(blAQN(s*KDL# zf!CJFEa>BNGueXa&m&LWXh4%Go~8tg<@5cv&fX0z%HIyYF%B+g!KTCh26-xEhy(9a z%-{cUVnpWO<^!D*@$|ngOh&=S>3+?WF+mR6o)p}Tqi#LHJG@ro&Fi54#!*)?`AgqIf=AXPtC{#26E| zAN(HJl46P!7gbTi0r{G@gcH4I5RB>rH51Gc(gLvc*?#sj>$C7_CijIa|B&C%8Oe8V zC6eM_1;g;K;HJu{cMe2Qo@NWy2KXDvXPyMa2F^IJX(3b$9mwhAoWnaT#DwWUK4!|EU9!+QAqr}W?MO8lM2NAARi?3 z_WZ$doiG!FZ7AD)O0uO0pPryXZ`h5dOrm1Xj^r7vYP8wLu3VKmnCBYkL9b$w0T9^MiYZS!2-*C(Y+qUJ5r_zcMBW_I?XR=q>TM zKp4BhU`hkx&qW0~A7%|4@j-Cbw98TD{0Z|%br#KoVL|vlHC7bIL5vej zxB~wMA9=XaYCl%x$o>=+6C=u{gR~#fI0zaRovderm<}qMnwN>%@IaHukIF><9E`xwDJ27p5f>0{gGJmm^jls9!EzR&;V@?d8}zmzG&Whr~9G@d!X ze6Vy(l2;UvZWy?fBzZ>t7E3=xtQ6FTQ{dzeo(M+(nFHHGPI*V*_ z&}rnQ0;$pe+zRZMzurUss{$jFe@u&ocBuRJ+wciO^*Eq#4jIsYo8aW&{oftXWb#`? zKb7OR7X6gO?~V2^Ui^lNUzDt>N|-%#;W62GD1H&py~y#9ZE)78y*{+(YMo}Xo< PgFh2POM|ilu9yD@)d*=D literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png new file mode 100644 index 0000000000000000000000000000000000000000..9c60a1761dbf62cc2a45ff98b9fdb63ade16e4d9 GIT binary patch literal 3773 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 Dh-f%Y literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png new file mode 100644 index 0000000000000000000000000000000000000000..448d6efb577d07e227a5c62545173ddf6bd86b55 GIT binary patch literal 4750 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png new file mode 100644 index 0000000000000000000000000000000000000000..8524768f8d764da7e9c452a444208708f2d18ff1 GIT binary patch literal 4692 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png new file mode 100644 index 0000000000000000000000000000000000000000..60a64703c0f11d08705cd607f7751338707f5919 GIT binary patch literal 5192 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 nB>N_)Q7iwV%v7MwAoJ}E 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 T3-0p800000NkvXXu0mjfSlal| literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png new file mode 100644 index 0000000000000000000000000000000000000000..1ad04f004b638bf781012290d78e4138f97bbe5e GIT binary patch literal 1761 zcmV<71|Io|P)4P%ubY|S^%$zf~ zmwTOa@12BA$oV-Y9!V&U%c=j==#_}M2ylE}1m>yyDoGsZ#Yy zNX}RO*f(MzmKS&u`qiajIyW{Y_LC%m2NqT@Ic|QpvYqwNgBK7n5X%c(3k^?2>EOA` zqGaXjE7H9BiJ55fh0iJRW}@=&(@R^E1hLB>kE%PS6eP@VZVdtn(fh;5DPKg!j;fJZ%)wH{Wn#~V&#n(o1URS zsyS`0Tu2m;-H}z9O^h`!UZAFr@?0a7Z;pYOi0uZhgzh=rOEDi`FIkKtVu*gEcSM!h zmb#_XR$akjlg$JI75MXgWkG7IUnYJ+X=1J!qJ%jXVj{L1I2QU%?=?DgV^U?)92DZm zV?>``xT1#kZdgUt!2n?|0>*6ae4tikA9FAlJ}kjmMQm_z3LB5sZYHBKdbex9_Hv@K z%Y?q@9-)b7vJ6X$3h0B4tH__=#*`9^efY@IQfghn*=E2Nb8sR8lrQlu`Ca_Rmm6>Z z7bkEe^w8M>x;hoNUvWu_GZJPVpI;bMTsBpf(@U$Ch(-gk0T#WpsaB1{7ISQ~Y48mW z;Nk?@LjM_?q{BV-D=veoOmJoncVDC1GwGGz(O5@o7ZkGCIJMPO(7K9b6M_wF?Xqd< zo4J6KF0_U2<1=T3x0qc6G6#g+^=N{QTpChC!GZuCY*|eU{Rw)LMN7a2wwbaCdn_dE zzy+Lip(XU4-+SoTFyEcnH3?HRV^%-;Ylx;|>8v&^Dy459ZJ_1zio`68!6s8SO(6sq zaN75WUKiF9+8MruR3=w5)hzA^Z1clVBuXc)+8@e);xX7bfygR&FsIIt-gQ+==(c;S z#J*CO1qZBF&M&6TRmskZXaMOU6&?jn_(BqY5 z>Y|^?uOh;yp6w0QR1`>tiEz_-{Zu!N#(nhRndJV$7;LOgPyZQ*J2yCin+~*u!qKj# zItsG2IDi{ZH+E6j=D|ht=qWxKNxaA6E>3Wna>=z1gy?*#>|g_1^BGspGro&OWRc(k zPP)(*y0WZ7Z-kICr3#g7($P*LCjE>7S`Xh~s!b~bPTo0XSkRm2T(M`QZbv-`Imx*Ulqk*aI* zaqa*=>61?nFb8w3wuiA&zyVyGz>!cm-pY3xEsMEiY)Th4FVrtqWp%V~gxW82)>4^N z*H{>GR?m$^s6BN^M=4^iEjT7(gM(8z7K#9Sn(M_`)oSH2zHDCla&cmK)bb|4nAMIO ze4S3gI9L8AD+T9c#C8K8JF)I68NBWK`5pE`q^OR#h~-u!e7P2i$UDq7^*uZr< zbp8sR<$nYxb8EKH|BltapZ^j3+PZt_u^PR*lT6;TCNxu^yFH$j(!JXvbmHfQ>a0>O z+2k{tOWD%ln$M`tD&>+*KBKvmEgi1;jOwgXF4_DG_&r&PcxYTT00000NkvXXu0mjf DptxI> literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd52620a8f15577e56ec7fe8e671988dd17ab0f GIT binary patch literal 2537 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png new file mode 100644 index 0000000000000000000000000000000000000000..b058cae2f440e5a5875e45c036c99f1fb6356046 GIT binary patch literal 2332 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 Kc&(xHv;P1B$EH01 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png new file mode 100644 index 0000000000000000000000000000000000000000..4954a4bd33f613d45f74dc0b12beb516e3b38661 GIT binary patch literal 2758 zcmbW3`9Bkm1IFjp%zcKjbSPx1iJT2#jw$8XupGH_6Pa_%ghXz$B+5DFthu6mLe4ot z&P8+P_HpF;_W2jSKRnO#)BDHg^?JwMMH+Ae#eo0-fE!_`Xa0As{tGAj-}dYL@%Zns zy24H206Tby@Oa7NhkA}!dczK$r?iEZ$Vhk-~@_+0zcnhHN1L<7SAz`^F^nt`pwmv zI;#7fNKRBqbi6#R=nWp3-t74^oio)O;EmZe%xSE-ft@G$^pS1_xV#<%J(m%H+rQ!* zeO`jU&03LnPLHln2g*P?)v6~sZQ-n}D1!`%X!+++kd;pV^S*5Se2>5=Z`KM3Gmd<| zJF!(*?{;#~qk4WSj+3+crGgdT6Ejft?G(>s%rr;yx#obfA_zOw!F@HHO!JVZp zf$<-eL=R(cgna67o3&QbQ_Rv*Q3p@(;J(R=%OVA1GC$(xNcNjoL@EYV2i{_r-2)EH zuPBIa^h!{Vodg4CW|9W&yI7UkliwR^OOdj33md-r{pnaxx#u8hxDfrw)Zji{*2~q+ z7s#&eS`I3`P&rvQ&9R3K4UCVN@WZ4U?cRjaKLs$vHD_)tQkkvXQFSJ39(>pGT5kO? z4$r!Ckk=G-IQ&Y{=&Q&r%QB(f*eAJKW1+G4^)wQ;;Is5kVTDO(4*m4+^SUL0;l*&a zR*i&l3aH4_<=^bf)VUI&RnPTvXd#uOHx}H?N&(>;FqeU(mz_40%hZ07s+ns=(XfmN zfa6EuMsqpK`5mhsIfMX9rY_}S%S_p1G%+J(e4oCGhW1~|wa{pMX9%*zz(O{Cb)i?- zzHB+y_c>Z32re>o|HXeNxpkmC8#Q(j@b31u^6f428bei>AXBC;6ayPmOOwHH-KPWQ_;$cG1QWdMZmpVBz4>j2M>~_Jmn`f3U{Sc`+6wF7O^SA9Txq7z6%gi&%=Xw% z#e7x|hba_?Yu}$U_?@kA>3mc4bY9&a%lK|Pg0XGE5unnOc`#(_w%fVdHcXxLp8j0Q z*qWsYKz4{YZ?Nup!t@>mgADqL=qOE$H(>+Rz9-WF895)?l$n}Md~Wrhwf_{7p&9f} z-E%@I-SYD>cz3nQa3Awe-dO*5|5<<0i?hRFdus8$thon(4#!b*Ue&2wgwMe~=|~EcV-FCW^eVMd?2* z!RTvDWs{aXYqR9@PPod9mI^vYmjn6mlS%GBU6bur7&I~?Yl_w*PSxfX3tci=)sD!$ zbid|y14KETnjx36kq`iA>^~T-LTf;u?U+5r6j%+=_Ah8+<>(MR3$I@Pe=v|Lw}Xo^ z0g)a$zHcy)U8+X{^6#M>Qix)zCRhgZT?$!DaqiXl7F!WlOIT5C1v2NBQ=-?n%|+<1 z5828!%oV_92uT1|EKEN!*fTYVUy)my7PkJZxfWesufbp7qe8Ttz=q>^ zUZ3ThC&FHZ(L=ty~-bcQytnTxM6SsuPt zx4MsrKD)N6{UoC@_s>>cuJ?Q*b9Iw%A96%N))!B}U}C6bvM4@aquDr+TfQ0T$;YA{ z(P6a9(KYIQyLk8CiP9aH;qagxLZi-H42&%!25R#bg`~6dG!I_>rRBH+ZUshGwt;%7 zClZx|gp^-oY!vVGl(p%Z+R>#2&ZSFyBiE&s?L+a9JwTRjO=d$tH!)j)osWL~$c9dn zXNhEEPYc}*l;(E)IvN-K_y^j+4{%r#@7T~%s6#0X=AaBDh!RLs8Ta_}>1axha^o6` z16K*+URzT!L-mK&b9FJ1_c62QH^D*j#Y+`vAK{xanlRIv`)KZAoaJY!N(D(`U2PBt z_MRtLeDZYH0ei;Ssrqg5EK_de^6vuUf;nPV&Bw-dv_Y_ae572`i410XSh0qh`bdh~eju;=kTI2--?I;!N6U8+kDt!vDkUU2suB3% z8v)2l$ZyA1J2W%uQv&a5h-^_veL7R*_rokWR%MhuY~rz$xUI|f_lERZ{(==GA~mR0 zK!H(Xad9WxqLbhrxH~QeZk@-8nqk~Rgte8gBVv)W+4>VJrNt5M(O{I4AunWN_spXO z|F@)8#>+kLlHPBjVB_fP2-f?L>o6XnWvTiO??9z8QB5s#%yzG{W_qjY))A?T_ty8R ze$H2PtgwU6!nCZ#Okr_}3!k{8DRKo+$F!+m@#~@k$?1NaExb2d0knV{`Vf}Z&5922cL0(H%cf|9Zp zF^~f7>{S|WGrQx-QQbI=mjgWF#Hyh3uN>dh*Q}ivx84}*?r01~V1n&ov&@riGnMMt z?JbJ}kJ0(M2e==tN8y6(^>1sVq^6@lq>I(;-o-Q!@ECB$=h)Z>nRU9cs!05~E~ToL z6~KWBw*XJ-2iRoZv%{pl^O;`bz3^cSRo1JybN$)v&*Idczu#*&S77BE^Vz9s^*fvlW%}$lz5B2&e7W$MS z%%bwZZ9W~Dr{Pn_*{lkcF?6I?_rP^;z%@-rd^wI1&q6 zYu38JL*FT;Mp>Tbrr0;;GGpJ$50brQ)6@u1r~N2D_HQDWrcotJ%XovVOGuX&PH50? zd|9`iE|d~B62LXh)5H*Mgbs1pg$IT$s&Siiotm8!j`3@dkWLBn(!Dr^PmK>VpZ?ri z + + + + + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist new file mode 100644 index 00000000..e17844fd --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDisplayName + LaunchDarkly.XamarinSdk.iOS.Tests + CFBundleIdentifier + com.launchdarkly.XamarinSdkTests + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIMainStoryboardFile~ipad + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj new file mode 100644 index 00000000..627ffabe --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj @@ -0,0 +1,204 @@ + + + + Debug + iPhoneSimulator + {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {edc1b0fa-90cd-4038-8fad-98fe74adb368} + Exe + LaunchDarkly.XamarinSdk.iOS.Toasts + LaunchDarkly.XamarinSdk.iOS.Toasts + Resources + true + NSUrlSessionHandler + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG + prompt + 4 + false + x86_64 + None + true + + + none + true + bin\iPhoneSimulator\Release + prompt + 4 + None + x86_64 + false + + + true + full + false + bin\iPhone\Debug + DEBUG + prompt + 4 + false + ARM64 + Entitlements.plist + iPhone Developer + true + + + none + true + bin\iPhone\Release + prompt + 4 + Entitlements.plist + ARM64 + false + iPhone Developer + + + + + + + + + + + + 3.4.1 + + + 2.5.25 + + + 2.4.1 + + + 4.0.0.497661 + + + 1.0.20 + + + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + + + + + + + + + + + + + + + + + SharedTestCode\BaseTest.cs + + + SharedTestCode\ConfigurationTest.cs + + + SharedTestCode\FeatureFlagListenerTests.cs + + + SharedTestCode\FeatureFlagRequestorTests.cs + + + SharedTestCode\FeatureFlagTests.cs + + + SharedTestCode\FlagCacheManagerTests.cs + + + SharedTestCode\LdClientEndToEndTests.cs + + + SharedTestCode\LdClientEvaluationTests.cs + + + SharedTestCode\LdClientEventTests.cs + + + SharedTestCode\LdClientTests.cs + + + SharedTestCode\LogSink.cs + + + SharedTestCode\MobilePollingProcessorTests.cs + + + SharedTestCode\MockComponents.cs + + + SharedTestCode\TestUtil.cs + + + SharedTestCode\UserFlagCacheTests.cs + + + SharedTestCode\WireMockExtensions.cs + + + + + {7717A2B2-9905-40A7-989F-790139D69543} + LaunchDarkly.XamarinSdk + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard new file mode 100644 index 00000000..f18534b4 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs new file mode 100644 index 00000000..fa24461b --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs @@ -0,0 +1,12 @@ +using UIKit; + +namespace LaunchDarkly.Xamarin.iOS.Tests +{ + public class Application + { + static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.storyboard b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.storyboard new file mode 100644 index 00000000..a3f40d68 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b2eb5309 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.iOS.Toasts")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.iOS.Toasts")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Resources/LaunchScreen.xib b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Resources/LaunchScreen.xib new file mode 100644 index 00000000..1f1c1cb7 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Resources/LaunchScreen.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2022ad94ac745342dc0e9e1135d3460922be8eeb Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 25 Jun 2019 13:52:02 -0700 Subject: [PATCH 121/499] revert accidental rename --- ...Toasts.csproj => LaunchDarkly.XamarinSdk.iOS.Tests.csproj} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/LaunchDarkly.XamarinSdk.iOS.Tests/{LaunchDarkly.XamarinSdk.iOS.Toasts.csproj => LaunchDarkly.XamarinSdk.iOS.Tests.csproj} (98%) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj similarity index 98% rename from tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj rename to tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 627ffabe..a2988ae1 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Toasts.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -7,8 +7,8 @@ {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {edc1b0fa-90cd-4038-8fad-98fe74adb368} Exe - LaunchDarkly.XamarinSdk.iOS.Toasts - LaunchDarkly.XamarinSdk.iOS.Toasts + LaunchDarkly.XamarinSdk.iOS.Tests + LaunchDarkly.XamarinSdk.iOS.Tests Resources true NSUrlSessionHandler From e223aa3905c6308fe89575b2af4acef684dc6ea3 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 25 Jun 2019 15:24:09 -0700 Subject: [PATCH 122/499] use mono-mdk --- .circleci/config.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d47f923..504e452b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,6 +56,9 @@ jobs: macos: xcode: "10.2.1" + environment: + PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands + steps: - restore_cache: key: homebrew @@ -64,8 +67,8 @@ jobs: name: Install Xamarin tools command: | brew install caskroom/cask/brew-cask - brew cask install xamarin xamarin-ios && brew install mono - # Note, "mono" provides the msbuild CLI tool + brew cask install xamarin xamarin-ios mono-mdk + # Note, "mono-mdk" provides the msbuild CLI tool - save_cache: key: homebrew From 486ab22d8359a6f9296897a072bd27407b72762b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 11:04:39 -0700 Subject: [PATCH 123/499] rm obsolete project --- .../AppDelegate.cs | 29 -- .../Entitlements.plist | 6 - LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist | 36 -- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 431 ------------------ .../LaunchScreen.storyboard | 27 -- LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs | 12 - .../packages.config | 138 ------ 7 files changed, 679 deletions(-) delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs delete mode 100644 LaunchDarkly.XamarinSdk.iOS.Tests/packages.config diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs deleted file mode 100644 index 4beefc8f..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Reflection; -using Foundation; -using UIKit; - -using Xunit.Runner; -using Xunit.Sdk; - -namespace LaunchDarkly.Xamarin.iOS.Tests -{ - // This is based on code that was generated automatically by the xunit.runner.devices package. - // It configures the test-runner app that is implemented by that package, telling it where to - // find the tests, and also configuring it to run them immediately and then quit rather than - // waiting for user input. Output from the test run goes to the system log. - - [Register("AppDelegate")] - public partial class AppDelegate : RunnerAppDelegate - { - public override bool FinishedLaunching(UIApplication app, NSDictionary options) - { - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - AddTestAssembly(Assembly.GetExecutingAssembly()); - - AutoStart = true; - TerminateAfterExecution = true; - - return base.FinishedLaunching(app, options); - } - } -} \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist b/LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist deleted file mode 100644 index 9ae59937..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/Entitlements.plist +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist b/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist deleted file mode 100644 index 1cbfd464..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleName - LaunchDarkly.Xamarin.iOS.Tests - CFBundleIdentifier - com.launchdarkly.LaunchDarkly-Xamarin-iOS-Tests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - MinimumOSVersion - 12.1 - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UILaunchStoryboardName - LaunchScreen - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj deleted file mode 100644 index ec344391..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ /dev/null @@ -1,431 +0,0 @@ - - - - - - - Debug - iPhoneSimulator - {066AA0F9-449A-48F5-9492-D698F0EFD923} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - LaunchDarkly.Xamarin.iOS.Tests - LaunchDarkly.XamarinSdk.iOS.Tests - Resources - - - true - full - false - bin\iPhoneSimulator\Debug - DEBUG; - prompt - 4 - iPhone Developer - true - true - true - 46596 - None - x86_64 - NSUrlSessionHandler - false - - - - pdbonly - true - bin\iPhone\Release - - prompt - 4 - iPhone Developer - true - true - Entitlements.plist - SdkOnly - ARM64 - NSUrlSessionHandler - - - - pdbonly - true - bin\iPhoneSimulator\Release - - prompt - 4 - iPhone Developer - true - None - x86_64 - NSUrlSessionHandler - - - - true - full - false - bin\iPhone\Debug - DEBUG; - prompt - 4 - iPhone Developer - true - true - true - true - true - Entitlements.plist - 42164 - SdkOnly - ARM64 - NSUrlSessionHandler - - - - - - - - - - - - - - - - ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll - - - ..\packages\Newtonsoft.Json.11.0.2\lib\netstandard2.0\Newtonsoft.Json.dll - - - - - ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll - - - ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - - - ..\packages\LaunchDarkly.CommonSdk.2.1.2\lib\netstandard2.0\LaunchDarkly.CommonSdk.dll - - - ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\xamarinios10\Plugin.DeviceInfo.dll - - - ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - - - ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - - - ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - - - ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll - - - ..\packages\xunit.runner.devices.2.5.25\lib\xamarinios10\xunit.runner.devices.dll - - - ..\packages\xunit.runner.devices.2.5.25\lib\xamarinios10\xunit.runner.utility.netstandard15.dll - - - ..\packages\xunit.abstractions.2.0.3\lib\netstandard2.0\xunit.abstractions.dll - - - ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll - - - ..\packages\xunit.extensibility.core.2.4.1\lib\netstandard1.1\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.4.1\lib\netstandard1.1\xunit.execution.dotnet.dll - - - ..\packages\Microsoft.AspNetCore.Diagnostics.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Diagnostics.Abstractions.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.FileSystemGlobbing.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileSystemGlobbing.dll - - - ..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll - - - ..\packages\Microsoft.Extensions.ObjectPool.2.1.1\lib\netstandard2.0\Microsoft.Extensions.ObjectPool.dll - - - ..\packages\MimeKitLite.2.0.7\lib\xamarinios\MimeKitLite.dll - - - ..\packages\Fare.2.1.1\lib\netstandard1.1\Fare.dll - - - ..\packages\JmesPath.Net.1.0.125\lib\netstandard2.0\JmesPath.Net.dll - - - ..\packages\NLipsum.1.1.0\lib\netstandard1.0\NLipsum.Core.dll - - - ..\packages\RandomDataGenerator.Net.1.0.8\lib\netstandard2.0\RandomDataGenerator.dll - - - ..\packages\SimMetrics.Net.1.0.5\lib\netstandard2.0\SimMetrics.Net.dll - - - ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll - - - ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll - - - ..\packages\System.Diagnostics.DiagnosticSource.4.5.0\lib\netstandard1.3\System.Diagnostics.DiagnosticSource.dll - - - ..\packages\RestEase.1.4.7\lib\netstandard2.0\RestEase.dll - - - ..\packages\System.Linq.Dynamic.Core.1.0.12\lib\netstandard2.0\System.Linq.Dynamic.Core.dll - - - ..\packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll - - - ..\packages\Handlebars.Net.1.9.5\lib\netstandard2.0\Handlebars.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll - - - ..\packages\Microsoft.Extensions.Primitives.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll - - - ..\packages\Microsoft.AspNetCore.Http.Features.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll - - - ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Hosting.Server.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll - - - ..\packages\Microsoft.Extensions.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll - - - ..\packages\Microsoft.Extensions.Configuration.Binder.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll - - - ..\packages\Microsoft.Extensions.Configuration.CommandLine.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.CommandLine.dll - - - ..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.EnvironmentVariables.dll - - - ..\packages\Microsoft.Extensions.FileProviders.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Abstractions.dll - - - ..\packages\Microsoft.Extensions.FileProviders.Physical.2.1.1\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Physical.dll - - - ..\packages\Microsoft.Extensions.Configuration.FileExtensions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.FileExtensions.dll - - - ..\packages\Microsoft.Extensions.Configuration.Json.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Json.dll - - - ..\packages\Microsoft.Extensions.Configuration.UserSecrets.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.dll - - - ..\packages\Microsoft.Extensions.Hosting.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll - - - ..\packages\Microsoft.Extensions.Options.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll - - - ..\packages\Microsoft.Extensions.Logging.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll - - - ..\packages\Microsoft.Extensions.Logging.Debug.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Debug.dll - - - ..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.ConfigurationExtensions.dll - - - ..\packages\Microsoft.Extensions.Logging.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Configuration.dll - - - ..\packages\Microsoft.Extensions.Logging.Console.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Console.dll - - - ..\packages\Microsoft.Net.Http.Headers.2.1.1\lib\netstandard2.0\Microsoft.Net.Http.Headers.dll - - - ..\packages\System.IO.Pipelines.4.5.2\lib\netstandard2.0\System.IO.Pipelines.dll - - - ..\packages\Microsoft.AspNetCore.Connections.Abstractions.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Connections.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.dll - - - ..\packages\System.Security.Principal.Windows.4.5.1\lib\netstandard2.0\System.Security.Principal.Windows.dll - - - ..\packages\System.Text.Encodings.Web.4.5.0\lib\netstandard2.0\System.Text.Encodings.Web.dll - - - ..\packages\Microsoft.AspNetCore.Http.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Authentication.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Hosting.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Http.Extensions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll - - - ..\packages\Microsoft.AspNetCore.HttpOverrides.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.HttpOverrides.dll - - - ..\packages\Microsoft.AspNetCore.Routing.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Routing.Abstractions.dll - - - ..\packages\Microsoft.AspNetCore.Routing.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Routing.dll - - - ..\packages\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll - - - ..\packages\Microsoft.AspNetCore.WebUtilities.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.WebUtilities.dll - - - ..\packages\Microsoft.AspNetCore.Diagnostics.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Diagnostics.dll - - - ..\packages\Microsoft.AspNetCore.Http.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Http.dll - - - ..\packages\Microsoft.AspNetCore.Authentication.Core.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Core.dll - - - ..\packages\Microsoft.AspNetCore.HostFiltering.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.HostFiltering.dll - - - ..\packages\Microsoft.AspNetCore.Hosting.2.1.1\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.dll - - - ..\packages\Microsoft.AspNetCore.Server.IISIntegration.2.1.2\lib\netstandard2.0\Microsoft.AspNetCore.Server.IISIntegration.dll - - - ..\packages\Microsoft.AspNetCore.Server.Kestrel.Core.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Core.dll - - - ..\packages\Microsoft.AspNetCore.Server.Kestrel.Https.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.Https.dll - - - ..\packages\Microsoft.AspNetCore.Server.Kestrel.2.1.3\lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.dll - - - ..\packages\Microsoft.AspNetCore.2.1.4\lib\netstandard2.0\Microsoft.AspNetCore.dll - - - ..\packages\XPath2.1.0.6.1\lib\netstandard2.0\XPath2.dll - - - ..\packages\XPath2.Extensions.1.0.6.1\lib\netstandard2.0\XPath2.Extensions.dll - - - ..\packages\WireMock.Net.1.0.20\lib\netstandard2.0\WireMock.Net.dll - - - - - - - - - - - - - - - SharedTestCode\BaseTest.cs - - - SharedTestCode\ConfigurationTest.cs - - - SharedTestCode\FeatureFlagListenerTests.cs - - - SharedTestCode\FeatureFlagRequestorTests.cs - - - SharedTestCode\FeatureFlagTests.cs - - - SharedTestCode\FlagCacheManagerTests.cs - - - SharedTestCode\LdClientEndToEndTests.cs - - - SharedTestCode\LdClientEvaluationTests.cs - - - SharedTestCode\LdClientEventTests.cs - - - SharedTestCode\LdClientTests.cs - - - SharedTestCode\LogSink.cs - - - SharedTestCode\MobilePollingProcessorTests.cs - - - SharedTestCode\MockComponents.cs - - - SharedTestCode\TestUtil.cs - - - SharedTestCode\UserFlagCacheTests.cs - - - SharedTestCode\WireMockExtensions.cs - - - - - - - - {7717A2B2-9905-40A7-989F-790139D69543} - LaunchDarkly.XamarinSdk - - - - - - - - - \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard b/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard deleted file mode 100644 index 5d2e905a..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs b/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs deleted file mode 100644 index fa24461b..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UIKit; - -namespace LaunchDarkly.Xamarin.iOS.Tests -{ - public class Application - { - static void Main(string[] args) - { - UIApplication.Main(args, null, "AppDelegate"); - } - } -} diff --git a/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config b/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config deleted file mode 100644 index 8a78858e..00000000 --- a/LaunchDarkly.XamarinSdk.iOS.Tests/packages.config +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 291d9d33283ffce6f50ef4e7724015e58ec7f232 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 11:31:02 -0700 Subject: [PATCH 124/499] better test output processing --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 504e452b..af05f0e8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -110,5 +110,9 @@ jobs: command: "( tail -f -c+0 test-run.log & ) | grep -q 'Tests run:'" - run: - name: Show test output - command: cat test-run.log + name: Show all test output + command: | + cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,9- + grep '\[FAIL\]' test-run.log >/dev/null && exit 1 + # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a + # JUnit-compatible test results file; you'll just have to look at the output. From 0a387d82fdd26b682b5b024bfc722be11bbdbe64 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 11:54:26 -0700 Subject: [PATCH 125/499] fix script exit logic --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index af05f0e8..7f2235d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -113,6 +113,6 @@ jobs: name: Show all test output command: | cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,9- - grep '\[FAIL\]' test-run.log >/dev/null && exit 1 + if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a # JUnit-compatible test results file; you'll just have to look at the output. From 0020505dacfb48199f8f42973e517551a8d9a35c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 18:02:57 -0700 Subject: [PATCH 126/499] add a new Android test project made from scratch --- .../LDAndroidTests.cs | 62 - ...unchDarkly.XamarinSdk.Android.Tests.csproj | 214 - .../MainActivity.cs | 23 - .../Properties/AndroidManifest.xml | 6 - .../Properties/AssemblyInfo.cs | 27 - .../Resources/Resource.designer.cs | 886 -- .../Resources/mipmap-hdpi/Icon.png | Bin 2201 -> 0 bytes .../Resources/mipmap-mdpi/Icon.png | Bin 1410 -> 0 bytes .../Resources/mipmap-xhdpi/Icon.png | Bin 3237 -> 0 bytes .../Resources/mipmap-xxhdpi/Icon.png | Bin 5414 -> 0 bytes .../Resources/mipmap-xxxhdpi/Icon.png | Bin 7825 -> 0 bytes .../designtime/build.props | 24 - .../packages.config | 86 - LaunchDarkly.XamarinSdk.sln | 28 +- .../Assets/AboutAssets.txt | 19 + ...unchDarkly.XamarinSdk.Android.Tests.csproj | 180 + .../MainActivity.cs | 24 + .../Properties/AndroidManifest.xml | 5 + .../Properties/AssemblyInfo.cs | 30 + .../Resources/AboutResources.txt | 2 +- .../Resources/Resource.designer.cs | 9543 +++++++++++++++++ .../Resources/layout/activity_main.axml | 3 + .../mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../Resources/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1634 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1441 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3552 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1362 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 958 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2413 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2307 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 2056 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 4858 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3871 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 3403 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8001 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 5016 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 4889 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 10893 bytes .../Resources/values/colors.xml | 6 + .../values/ic_launcher_background.xml | 4 + .../Resources/values/strings.xml | 5 + .../Resources/values/styles.xml | 10 + 43 files changed, 9854 insertions(+), 1343 deletions(-) delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/Icon.png delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/Icon.png delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/Icon.png delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/Icon.png delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/Icon.png delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/designtime/build.props delete mode 100644 LaunchDarkly.XamarinSdk.Android.Tests/packages.config create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs rename {LaunchDarkly.XamarinSdk.Android.Tests => tests/LaunchDarkly.XamarinSdk.Android.Tests}/Resources/AboutResources.txt (97%) create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/layout/activity_main.axml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/colors.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/ic_launcher_background.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/strings.xml create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs b/LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs deleted file mode 100644 index 931b7c19..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/LDAndroidTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using NUnit.Framework; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin.Android.Tests -{ - [TestFixture] - public class LDAndroidTests - { - private ILdMobileClient client; - - [SetUp] - public void Setup() - { - var user = LaunchDarkly.Client.User.WithKey("test-user"); - var timeSpan = TimeSpan.FromSeconds(10); - client = LdClient.Init("MOBILE_KEY", user, timeSpan); - } - - [TearDown] - public void Tear() { LdClient.Instance = null; } - - [Test] - public void BooleanFeatureFlag() - { - Console.WriteLine("Test Boolean Variation"); - Assert.True(client.BoolVariation("boolean-feature-flag")); - } - - [Test] - public void IntFeatureFlag() - { - Console.WriteLine("Test Integer Variation"); - Assert.True(client.IntVariation("int-feature-flag") == 2); - } - - [Test] - public void StringFeatureFlag() - { - Console.WriteLine("Test String Variation"); - Assert.True(client.StringVariation("string-feature-flag", "false").Equals("bravo")); - } - - [Test] - public void JsonFeatureFlag() - { - string json = @"{ - ""test2"": ""testing2"" - }"; - Console.WriteLine("Test JSON Variation"); - JToken jsonToken = JToken.FromObject(JObject.Parse(json)); - Assert.True(JToken.DeepEquals(jsonToken, client.JsonVariation("json-feature-flag", "false"))); - } - - [Test] - public void FloatFeatureFlag() - { - Console.WriteLine("Test Float Variation"); - Assert.True(client.FloatVariation("float-feature-flag") == 1.5); - } - } -} diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj deleted file mode 100644 index fc46d530..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ /dev/null @@ -1,214 +0,0 @@ - - - - Debug - AnyCPU - {0B18C336-C770-42C1-B77A-E4A49F789677} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - LaunchDarkly.Xamarin.Android.Tests - LaunchDarkly.XamarinSdk.Android.Tests - v9.0 - MonoAndroid90 - True - Resources\Resource.designer.cs - Resource - Resources - Assets - Properties\AndroidManifest.xml - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - None - - arm64-v8a;armeabi-v7a;x86 - - - true - pdbonly - true - bin\Release - prompt - 4 - true - false - - - - - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\monoandroid81\LaunchDarkly.XamarinSdk.dll - - - - - - - - - ..\packages\Common.Logging.Core.3.4.1\lib\netstandard1.0\Common.Logging.Core.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\netstandard1.0\Newtonsoft.Json.dll - - - - - ..\packages\Common.Logging.3.4.1\lib\netstandard1.3\Common.Logging.dll - - - ..\packages\LaunchDarkly.EventSource.3.3.0\lib\netstandard1.4\LaunchDarkly.EventSource.dll - - - ..\packages\LaunchDarkly.CommonSdk.2.1.2\lib\netstandard2.0\LaunchDarkly.CommonSdk.dll - - - ..\packages\Plugin.CurrentActivity.2.1.0.4\lib\monoandroid44\Plugin.CurrentActivity.dll - - - - ..\packages\Xam.Plugin.DeviceInfo.4.1.1\lib\monoandroid71\Plugin.DeviceInfo.dll - - - ..\packages\Xamarin.Android.Support.Annotations.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Annotations.dll - - - ..\packages\Xamarin.Android.Arch.Core.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Common.dll - - - ..\packages\Xamarin.Android.Arch.Core.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Runtime.dll - - - ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Common.dll - - - ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.Core.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.Core.dll - - - ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.dll - - - ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Runtime.dll - - - ..\packages\Xamarin.Android.Arch.Lifecycle.ViewModel.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.ViewModel.dll - - - ..\packages\Xamarin.Android.Support.Collections.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Collections.dll - - - ..\packages\Xamarin.Android.Support.CursorAdapter.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CursorAdapter.dll - - - ..\packages\Xamarin.Android.Support.DocumentFile.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DocumentFile.dll - - - ..\packages\Xamarin.Android.Support.Interpolator.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Interpolator.dll - - - ..\packages\Xamarin.Android.Support.LocalBroadcastManager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.LocalBroadcastManager.dll - - - ..\packages\Xamarin.Android.Support.Print.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Print.dll - - - ..\packages\Xamarin.Android.Support.VersionedParcelable.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.VersionedParcelable.dll - - - ..\packages\Xamarin.Android.Support.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Compat.dll - - - ..\packages\Xamarin.Android.Support.AsyncLayoutInflater.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.AsyncLayoutInflater.dll - - - ..\packages\Xamarin.Android.Support.CustomView.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CustomView.dll - - - ..\packages\Xamarin.Android.Support.CoordinaterLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CoordinaterLayout.dll - - - ..\packages\Xamarin.Android.Support.DrawerLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DrawerLayout.dll - - - ..\packages\Xamarin.Android.Support.Loader.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Loader.dll - - - ..\packages\Xamarin.Android.Support.Core.Utils.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.Utils.dll - - - ..\packages\Xamarin.Android.Support.Media.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Media.Compat.dll - - - ..\packages\Xamarin.Android.Support.SlidingPaneLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SlidingPaneLayout.dll - - - ..\packages\Xamarin.Android.Support.SwipeRefreshLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SwipeRefreshLayout.dll - - - ..\packages\Xamarin.Android.Support.ViewPager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.ViewPager.dll - - - ..\packages\Xamarin.Android.Support.Core.UI.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.UI.dll - - - ..\packages\Xamarin.Android.Support.Fragment.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Fragment.dll - - - ..\packages\Xamarin.Android.Support.v4.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v4.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs b/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs deleted file mode 100644 index 22d1b416..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; - -using Android.App; -using Android.OS; -using Xamarin.Android.NUnitLite; - -namespace LaunchDarkly.Xamarin.Android.Tests -{ - [Activity(Label = "LaunchDarkly.Xamarin.Android.Tests", MainLauncher = true)] - public class MainActivity : TestSuiteActivity - { - protected override void OnCreate(Bundle bundle) - { - // tests can be inside the main assembly - AddTest(Assembly.GetExecutingAssembly()); - // or in any reference assemblies - // AddTest (typeof (Your.Library.TestClass).Assembly); - - // Once you called base.OnCreate(), you cannot add more assemblies. - base.OnCreate(bundle); - } - } -} diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml b/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml deleted file mode 100644 index 458b1f79..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs b/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 80ab1048..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using Android.App; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("LaunchDarkly.Xamarin.Android.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.0")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs deleted file mode 100644 index 8144be89..00000000 --- a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ /dev/null @@ -1,886 +0,0 @@ -#pragma warning disable 1591 -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.Xamarin.Android.Tests.Resource", IsApplication=true)] - -namespace LaunchDarkly.Xamarin.Android.Tests -{ - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] - public partial class Resource - { - - static Resource() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - public static void UpdateIdValues() - { - global::Xamarin.Android.NUnitLite.Resource.Id.OptionHostName = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionHostName; - global::Xamarin.Android.NUnitLite.Resource.Id.OptionPort = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionPort; - global::Xamarin.Android.NUnitLite.Resource.Id.OptionRemoteServer = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionRemoteServer; - global::Xamarin.Android.NUnitLite.Resource.Id.OptionsButton = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.OptionsButton; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultFullName = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultFullName; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultMessage = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultMessage; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultResultState = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultResultState; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultRunSingleMethodTest = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultRunSingleMethodTest; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultStackTrace = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultStackTrace; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsFailed = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsFailed; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsId = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsId; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsIgnored = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsIgnored; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsInconclusive = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsInconclusive; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsMessage = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsMessage; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsPassed = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsPassed; - global::Xamarin.Android.NUnitLite.Resource.Id.ResultsResult = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.ResultsResult; - global::Xamarin.Android.NUnitLite.Resource.Id.RunTestsButton = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.RunTestsButton; - global::Xamarin.Android.NUnitLite.Resource.Id.TestSuiteListView = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Id.TestSuiteListView; - global::Xamarin.Android.NUnitLite.Resource.Layout.options = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.options; - global::Xamarin.Android.NUnitLite.Resource.Layout.results = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.results; - global::Xamarin.Android.NUnitLite.Resource.Layout.test_result = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.test_result; - global::Xamarin.Android.NUnitLite.Resource.Layout.test_suite = global::LaunchDarkly.Xamarin.Android.Tests.Resource.Layout.test_suite; - } - - public partial class Attribute - { - - // aapt resource value: 0x7f010009 - public const int alpha = 2130771977; - - // aapt resource value: 0x7f010000 - public const int coordinatorLayoutStyle = 2130771968; - - // aapt resource value: 0x7f010011 - public const int font = 2130771985; - - // aapt resource value: 0x7f01000a - public const int fontProviderAuthority = 2130771978; - - // aapt resource value: 0x7f01000d - public const int fontProviderCerts = 2130771981; - - // aapt resource value: 0x7f01000e - public const int fontProviderFetchStrategy = 2130771982; - - // aapt resource value: 0x7f01000f - public const int fontProviderFetchTimeout = 2130771983; - - // aapt resource value: 0x7f01000b - public const int fontProviderPackage = 2130771979; - - // aapt resource value: 0x7f01000c - public const int fontProviderQuery = 2130771980; - - // aapt resource value: 0x7f010010 - public const int fontStyle = 2130771984; - - // aapt resource value: 0x7f010013 - public const int fontVariationSettings = 2130771987; - - // aapt resource value: 0x7f010012 - public const int fontWeight = 2130771986; - - // aapt resource value: 0x7f010001 - public const int keylines = 2130771969; - - // aapt resource value: 0x7f010004 - public const int layout_anchor = 2130771972; - - // aapt resource value: 0x7f010006 - public const int layout_anchorGravity = 2130771974; - - // aapt resource value: 0x7f010003 - public const int layout_behavior = 2130771971; - - // aapt resource value: 0x7f010008 - public const int layout_dodgeInsetEdges = 2130771976; - - // aapt resource value: 0x7f010007 - public const int layout_insetEdge = 2130771975; - - // aapt resource value: 0x7f010005 - public const int layout_keyline = 2130771973; - - // aapt resource value: 0x7f010002 - public const int statusBarBackground = 2130771970; - - // aapt resource value: 0x7f010014 - public const int ttcIndex = 2130771988; - - static Attribute() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Attribute() - { - } - } - - public partial class Color - { - - // aapt resource value: 0x7f060003 - public const int notification_action_color_filter = 2131099651; - - // aapt resource value: 0x7f060004 - public const int notification_icon_bg_color = 2131099652; - - // aapt resource value: 0x7f060000 - public const int notification_material_background_media_default_color = 2131099648; - - // aapt resource value: 0x7f060001 - public const int primary_text_default_material_dark = 2131099649; - - // aapt resource value: 0x7f060005 - public const int ripple_material_light = 2131099653; - - // aapt resource value: 0x7f060002 - public const int secondary_text_default_material_dark = 2131099650; - - // aapt resource value: 0x7f060006 - public const int secondary_text_default_material_light = 2131099654; - - static Color() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Color() - { - } - } - - public partial class Dimension - { - - // aapt resource value: 0x7f070008 - public const int compat_button_inset_horizontal_material = 2131165192; - - // aapt resource value: 0x7f070009 - public const int compat_button_inset_vertical_material = 2131165193; - - // aapt resource value: 0x7f07000a - public const int compat_button_padding_horizontal_material = 2131165194; - - // aapt resource value: 0x7f07000b - public const int compat_button_padding_vertical_material = 2131165195; - - // aapt resource value: 0x7f07000c - public const int compat_control_corner_material = 2131165196; - - // aapt resource value: 0x7f07000d - public const int compat_notification_large_icon_max_height = 2131165197; - - // aapt resource value: 0x7f07000e - public const int compat_notification_large_icon_max_width = 2131165198; - - // aapt resource value: 0x7f07000f - public const int notification_action_icon_size = 2131165199; - - // aapt resource value: 0x7f070010 - public const int notification_action_text_size = 2131165200; - - // aapt resource value: 0x7f070011 - public const int notification_big_circle_margin = 2131165201; - - // aapt resource value: 0x7f070005 - public const int notification_content_margin_start = 2131165189; - - // aapt resource value: 0x7f070012 - public const int notification_large_icon_height = 2131165202; - - // aapt resource value: 0x7f070013 - public const int notification_large_icon_width = 2131165203; - - // aapt resource value: 0x7f070006 - public const int notification_main_column_padding_top = 2131165190; - - // aapt resource value: 0x7f070007 - public const int notification_media_narrow_margin = 2131165191; - - // aapt resource value: 0x7f070014 - public const int notification_right_icon_size = 2131165204; - - // aapt resource value: 0x7f070004 - public const int notification_right_side_padding_top = 2131165188; - - // aapt resource value: 0x7f070015 - public const int notification_small_icon_background_padding = 2131165205; - - // aapt resource value: 0x7f070016 - public const int notification_small_icon_size_as_large = 2131165206; - - // aapt resource value: 0x7f070017 - public const int notification_subtext_size = 2131165207; - - // aapt resource value: 0x7f070018 - public const int notification_top_pad = 2131165208; - - // aapt resource value: 0x7f070019 - public const int notification_top_pad_large_text = 2131165209; - - // aapt resource value: 0x7f070000 - public const int subtitle_corner_radius = 2131165184; - - // aapt resource value: 0x7f070001 - public const int subtitle_outline_width = 2131165185; - - // aapt resource value: 0x7f070002 - public const int subtitle_shadow_offset = 2131165186; - - // aapt resource value: 0x7f070003 - public const int subtitle_shadow_radius = 2131165187; - - static Dimension() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Dimension() - { - } - } - - public partial class Drawable - { - - // aapt resource value: 0x7f020000 - public const int notification_action_background = 2130837504; - - // aapt resource value: 0x7f020001 - public const int notification_bg = 2130837505; - - // aapt resource value: 0x7f020002 - public const int notification_bg_low = 2130837506; - - // aapt resource value: 0x7f020003 - public const int notification_bg_low_normal = 2130837507; - - // aapt resource value: 0x7f020004 - public const int notification_bg_low_pressed = 2130837508; - - // aapt resource value: 0x7f020005 - public const int notification_bg_normal = 2130837509; - - // aapt resource value: 0x7f020006 - public const int notification_bg_normal_pressed = 2130837510; - - // aapt resource value: 0x7f020007 - public const int notification_icon_background = 2130837511; - - // aapt resource value: 0x7f02000a - public const int notification_template_icon_bg = 2130837514; - - // aapt resource value: 0x7f02000b - public const int notification_template_icon_low_bg = 2130837515; - - // aapt resource value: 0x7f020008 - public const int notification_tile_bg = 2130837512; - - // aapt resource value: 0x7f020009 - public const int notify_panel_notification_icon_bg = 2130837513; - - static Drawable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Drawable() - { - } - } - - public partial class Id - { - - // aapt resource value: 0x7f0a0032 - public const int OptionHostName = 2131361842; - - // aapt resource value: 0x7f0a0033 - public const int OptionPort = 2131361843; - - // aapt resource value: 0x7f0a0031 - public const int OptionRemoteServer = 2131361841; - - // aapt resource value: 0x7f0a0041 - public const int OptionsButton = 2131361857; - - // aapt resource value: 0x7f0a003c - public const int ResultFullName = 2131361852; - - // aapt resource value: 0x7f0a003e - public const int ResultMessage = 2131361854; - - // aapt resource value: 0x7f0a003d - public const int ResultResultState = 2131361853; - - // aapt resource value: 0x7f0a003b - public const int ResultRunSingleMethodTest = 2131361851; - - // aapt resource value: 0x7f0a003f - public const int ResultStackTrace = 2131361855; - - // aapt resource value: 0x7f0a0037 - public const int ResultsFailed = 2131361847; - - // aapt resource value: 0x7f0a0034 - public const int ResultsId = 2131361844; - - // aapt resource value: 0x7f0a0038 - public const int ResultsIgnored = 2131361848; - - // aapt resource value: 0x7f0a0039 - public const int ResultsInconclusive = 2131361849; - - // aapt resource value: 0x7f0a003a - public const int ResultsMessage = 2131361850; - - // aapt resource value: 0x7f0a0036 - public const int ResultsPassed = 2131361846; - - // aapt resource value: 0x7f0a0035 - public const int ResultsResult = 2131361845; - - // aapt resource value: 0x7f0a0040 - public const int RunTestsButton = 2131361856; - - // aapt resource value: 0x7f0a0042 - public const int TestSuiteListView = 2131361858; - - // aapt resource value: 0x7f0a0020 - public const int action0 = 2131361824; - - // aapt resource value: 0x7f0a001d - public const int action_container = 2131361821; - - // aapt resource value: 0x7f0a0024 - public const int action_divider = 2131361828; - - // aapt resource value: 0x7f0a001e - public const int action_image = 2131361822; - - // aapt resource value: 0x7f0a001f - public const int action_text = 2131361823; - - // aapt resource value: 0x7f0a002e - public const int actions = 2131361838; - - // aapt resource value: 0x7f0a0017 - public const int all = 2131361815; - - // aapt resource value: 0x7f0a0018 - public const int async = 2131361816; - - // aapt resource value: 0x7f0a0019 - public const int blocking = 2131361817; - - // aapt resource value: 0x7f0a0008 - public const int bottom = 2131361800; - - // aapt resource value: 0x7f0a0021 - public const int cancel_action = 2131361825; - - // aapt resource value: 0x7f0a0009 - public const int center = 2131361801; - - // aapt resource value: 0x7f0a000a - public const int center_horizontal = 2131361802; - - // aapt resource value: 0x7f0a000b - public const int center_vertical = 2131361803; - - // aapt resource value: 0x7f0a0029 - public const int chronometer = 2131361833; - - // aapt resource value: 0x7f0a000c - public const int clip_horizontal = 2131361804; - - // aapt resource value: 0x7f0a000d - public const int clip_vertical = 2131361805; - - // aapt resource value: 0x7f0a000e - public const int end = 2131361806; - - // aapt resource value: 0x7f0a0030 - public const int end_padder = 2131361840; - - // aapt resource value: 0x7f0a000f - public const int fill = 2131361807; - - // aapt resource value: 0x7f0a0010 - public const int fill_horizontal = 2131361808; - - // aapt resource value: 0x7f0a0011 - public const int fill_vertical = 2131361809; - - // aapt resource value: 0x7f0a001a - public const int forever = 2131361818; - - // aapt resource value: 0x7f0a002b - public const int icon = 2131361835; - - // aapt resource value: 0x7f0a002f - public const int icon_group = 2131361839; - - // aapt resource value: 0x7f0a002a - public const int info = 2131361834; - - // aapt resource value: 0x7f0a001b - public const int italic = 2131361819; - - // aapt resource value: 0x7f0a0012 - public const int left = 2131361810; - - // aapt resource value: 0x7f0a0000 - public const int line1 = 2131361792; - - // aapt resource value: 0x7f0a0001 - public const int line3 = 2131361793; - - // aapt resource value: 0x7f0a0023 - public const int media_actions = 2131361827; - - // aapt resource value: 0x7f0a0016 - public const int none = 2131361814; - - // aapt resource value: 0x7f0a001c - public const int normal = 2131361820; - - // aapt resource value: 0x7f0a002d - public const int notification_background = 2131361837; - - // aapt resource value: 0x7f0a0026 - public const int notification_main_column = 2131361830; - - // aapt resource value: 0x7f0a0025 - public const int notification_main_column_container = 2131361829; - - // aapt resource value: 0x7f0a0013 - public const int right = 2131361811; - - // aapt resource value: 0x7f0a002c - public const int right_icon = 2131361836; - - // aapt resource value: 0x7f0a0027 - public const int right_side = 2131361831; - - // aapt resource value: 0x7f0a0014 - public const int start = 2131361812; - - // aapt resource value: 0x7f0a0022 - public const int status_bar_latest_event_content = 2131361826; - - // aapt resource value: 0x7f0a0002 - public const int tag_transition_group = 2131361794; - - // aapt resource value: 0x7f0a0003 - public const int tag_unhandled_key_event_manager = 2131361795; - - // aapt resource value: 0x7f0a0004 - public const int tag_unhandled_key_listeners = 2131361796; - - // aapt resource value: 0x7f0a0005 - public const int text = 2131361797; - - // aapt resource value: 0x7f0a0006 - public const int text2 = 2131361798; - - // aapt resource value: 0x7f0a0028 - public const int time = 2131361832; - - // aapt resource value: 0x7f0a0007 - public const int title = 2131361799; - - // aapt resource value: 0x7f0a0015 - public const int top = 2131361813; - - static Id() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Id() - { - } - } - - public partial class Integer - { - - // aapt resource value: 0x7f080000 - public const int cancel_button_image_alpha = 2131230720; - - // aapt resource value: 0x7f080001 - public const int status_bar_notification_info_maxnum = 2131230721; - - static Integer() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Integer() - { - } - } - - public partial class Layout - { - - // aapt resource value: 0x7f040000 - public const int notification_action = 2130968576; - - // aapt resource value: 0x7f040001 - public const int notification_action_tombstone = 2130968577; - - // aapt resource value: 0x7f040002 - public const int notification_media_action = 2130968578; - - // aapt resource value: 0x7f040003 - public const int notification_media_cancel_action = 2130968579; - - // aapt resource value: 0x7f040004 - public const int notification_template_big_media = 2130968580; - - // aapt resource value: 0x7f040005 - public const int notification_template_big_media_custom = 2130968581; - - // aapt resource value: 0x7f040006 - public const int notification_template_big_media_narrow = 2130968582; - - // aapt resource value: 0x7f040007 - public const int notification_template_big_media_narrow_custom = 2130968583; - - // aapt resource value: 0x7f040008 - public const int notification_template_custom_big = 2130968584; - - // aapt resource value: 0x7f040009 - public const int notification_template_icon_group = 2130968585; - - // aapt resource value: 0x7f04000a - public const int notification_template_lines_media = 2130968586; - - // aapt resource value: 0x7f04000b - public const int notification_template_media = 2130968587; - - // aapt resource value: 0x7f04000c - public const int notification_template_media_custom = 2130968588; - - // aapt resource value: 0x7f04000d - public const int notification_template_part_chronometer = 2130968589; - - // aapt resource value: 0x7f04000e - public const int notification_template_part_time = 2130968590; - - // aapt resource value: 0x7f04000f - public const int options = 2130968591; - - // aapt resource value: 0x7f040010 - public const int results = 2130968592; - - // aapt resource value: 0x7f040011 - public const int test_result = 2130968593; - - // aapt resource value: 0x7f040012 - public const int test_suite = 2130968594; - - static Layout() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Layout() - { - } - } - - public partial class Mipmap - { - - // aapt resource value: 0x7f030000 - public const int Icon = 2130903040; - - static Mipmap() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Mipmap() - { - } - } - - public partial class String - { - - // aapt resource value: 0x7f090000 - public const int status_bar_notification_info_overflow = 2131296256; - - static String() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private String() - { - } - } - - public partial class Style - { - - // aapt resource value: 0x7f050006 - public const int TextAppearance_Compat_Notification = 2131034118; - - // aapt resource value: 0x7f050007 - public const int TextAppearance_Compat_Notification_Info = 2131034119; - - // aapt resource value: 0x7f050000 - public const int TextAppearance_Compat_Notification_Info_Media = 2131034112; - - // aapt resource value: 0x7f05000c - public const int TextAppearance_Compat_Notification_Line2 = 2131034124; - - // aapt resource value: 0x7f050004 - public const int TextAppearance_Compat_Notification_Line2_Media = 2131034116; - - // aapt resource value: 0x7f050001 - public const int TextAppearance_Compat_Notification_Media = 2131034113; - - // aapt resource value: 0x7f050008 - public const int TextAppearance_Compat_Notification_Time = 2131034120; - - // aapt resource value: 0x7f050002 - public const int TextAppearance_Compat_Notification_Time_Media = 2131034114; - - // aapt resource value: 0x7f050009 - public const int TextAppearance_Compat_Notification_Title = 2131034121; - - // aapt resource value: 0x7f050003 - public const int TextAppearance_Compat_Notification_Title_Media = 2131034115; - - // aapt resource value: 0x7f05000a - public const int Widget_Compat_NotificationActionContainer = 2131034122; - - // aapt resource value: 0x7f05000b - public const int Widget_Compat_NotificationActionText = 2131034123; - - // aapt resource value: 0x7f050005 - public const int Widget_Support_CoordinatorLayout = 2131034117; - - static Style() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Style() - { - } - } - - public partial class Styleable - { - - public static int[] ColorStateListItem = new int[] { - 16843173, - 16843551, - 2130771977}; - - // aapt resource value: 2 - public const int ColorStateListItem_alpha = 2; - - // aapt resource value: 1 - public const int ColorStateListItem_android_alpha = 1; - - // aapt resource value: 0 - public const int ColorStateListItem_android_color = 0; - - public static int[] CoordinatorLayout = new int[] { - 2130771969, - 2130771970}; - - // aapt resource value: 0 - public const int CoordinatorLayout_keylines = 0; - - // aapt resource value: 1 - public const int CoordinatorLayout_statusBarBackground = 1; - - public static int[] CoordinatorLayout_Layout = new int[] { - 16842931, - 2130771971, - 2130771972, - 2130771973, - 2130771974, - 2130771975, - 2130771976}; - - // aapt resource value: 0 - public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - - // aapt resource value: 2 - public const int CoordinatorLayout_Layout_layout_anchor = 2; - - // aapt resource value: 4 - public const int CoordinatorLayout_Layout_layout_anchorGravity = 4; - - // aapt resource value: 1 - public const int CoordinatorLayout_Layout_layout_behavior = 1; - - // aapt resource value: 6 - public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 6; - - // aapt resource value: 5 - public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - - // aapt resource value: 3 - public const int CoordinatorLayout_Layout_layout_keyline = 3; - - public static int[] FontFamily = new int[] { - 2130771978, - 2130771979, - 2130771980, - 2130771981, - 2130771982, - 2130771983}; - - // aapt resource value: 0 - public const int FontFamily_fontProviderAuthority = 0; - - // aapt resource value: 3 - public const int FontFamily_fontProviderCerts = 3; - - // aapt resource value: 4 - public const int FontFamily_fontProviderFetchStrategy = 4; - - // aapt resource value: 5 - public const int FontFamily_fontProviderFetchTimeout = 5; - - // aapt resource value: 1 - public const int FontFamily_fontProviderPackage = 1; - - // aapt resource value: 2 - public const int FontFamily_fontProviderQuery = 2; - - public static int[] FontFamilyFont = new int[] { - 16844082, - 16844083, - 16844095, - 16844143, - 16844144, - 2130771984, - 2130771985, - 2130771986, - 2130771987, - 2130771988}; - - // aapt resource value: 0 - public const int FontFamilyFont_android_font = 0; - - // aapt resource value: 2 - public const int FontFamilyFont_android_fontStyle = 2; - - // aapt resource value: 4 - public const int FontFamilyFont_android_fontVariationSettings = 4; - - // aapt resource value: 1 - public const int FontFamilyFont_android_fontWeight = 1; - - // aapt resource value: 3 - public const int FontFamilyFont_android_ttcIndex = 3; - - // aapt resource value: 6 - public const int FontFamilyFont_font = 6; - - // aapt resource value: 5 - public const int FontFamilyFont_fontStyle = 5; - - // aapt resource value: 8 - public const int FontFamilyFont_fontVariationSettings = 8; - - // aapt resource value: 7 - public const int FontFamilyFont_fontWeight = 7; - - // aapt resource value: 9 - public const int FontFamilyFont_ttcIndex = 9; - - public static int[] GradientColor = new int[] { - 16843165, - 16843166, - 16843169, - 16843170, - 16843171, - 16843172, - 16843265, - 16843275, - 16844048, - 16844049, - 16844050, - 16844051}; - - // aapt resource value: 7 - public const int GradientColor_android_centerColor = 7; - - // aapt resource value: 3 - public const int GradientColor_android_centerX = 3; - - // aapt resource value: 4 - public const int GradientColor_android_centerY = 4; - - // aapt resource value: 1 - public const int GradientColor_android_endColor = 1; - - // aapt resource value: 10 - public const int GradientColor_android_endX = 10; - - // aapt resource value: 11 - public const int GradientColor_android_endY = 11; - - // aapt resource value: 5 - public const int GradientColor_android_gradientRadius = 5; - - // aapt resource value: 0 - public const int GradientColor_android_startColor = 0; - - // aapt resource value: 8 - public const int GradientColor_android_startX = 8; - - // aapt resource value: 9 - public const int GradientColor_android_startY = 9; - - // aapt resource value: 6 - public const int GradientColor_android_tileMode = 6; - - // aapt resource value: 2 - public const int GradientColor_android_type = 2; - - public static int[] GradientColorItem = new int[] { - 16843173, - 16844052}; - - // aapt resource value: 0 - public const int GradientColorItem_android_color = 0; - - // aapt resource value: 1 - public const int GradientColorItem_android_offset = 1; - - static Styleable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Styleable() - { - } - } - } -} -#pragma warning restore 1591 diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/Icon.png deleted file mode 100644 index f4c804644c5e47daffed8e8573b5da99d0939bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2201 zcmV;K2xj+*P){EiEnLgO@jbP<$a7N&ovF_Lrmiarpgv{=M+flYGZm(ImUDn zsAf#fZ>ai}o;M9vU2#3IZ=RBHP7XP&P4}yV4qu62^%e-0{`@gjdh#Yzg(r7h<(?d- z%$`Gn`QHC3yQd!$eb$YO{G6@7Kxhb9g2xAsTBj786aJc=4S|ZF1m$%&@*zPmRQIG$ z3W)-eK1*MdW1u8h@3-!p5^#=R%AM^B*dJQ^4HOQQTre0QBIq6}0LkeyKg<>P7IQ#i z9;vi@4(6AbQKwViSH7ywZysgB!2#iL zARxp+TB314L`S_vqqf_{tD^3n6aJ&^%0r7S3I_s$kSHJsDO+gpmA6OLMGbeYNu=Ki zAt}pt4()wpI*1Cwk!1B41V>MCQdHmwf@PX3VDmHJ$aN2^_X)FmsOo%Wev7#Ghy!X0 zR2!;%MR;gYFr1+U!9X}r5K#7*D#X8CKVUILwkhng%y0BtpQ0Tz6c-!_O2>2$gaaXo zY2m4*D|ddx0Ew4Fm(5!UpvVp@y!YX14%zu9dt4>%tg)YK@LOCFfn&dET<#n&kKnH1 zzm(#+hX|H;2#5)Zl>HI&&D`Z-FY8T#ste@41)v~34?i5da^ddLz5$1bJlFdwE`+u0 zH+ag|)rvRdkjgm~M`)q$fpFL%1|V6zhG2D{Xo4SXj`FId1MY!Bog+O%K;-w2dCTp5J&(`w z!7;!2`f^f!F?K8ft6VDp@Z9PzRw$hW2gcBDWY@CXYRQW>- zLL$%g{>n2UCG486k~NM2(U5D4mFYA7L2LlXG zuA$FhgaO>c(evPa4|ENLB;FD_WVxu(ZP_8lC53^85e`Pab97M5u;Qp(6b?{jf1Xh5 zHtm=c&UxYdbg`@ta>C(n1SuTo9<;3Uh8I+=zjTl=U|%WRmK>Y6fCmUZ^n%|`K;fYU za}I>V4an@#3pMVN9A@rudd=M^1RM}ntr+gn%N&F$au2F1Pf<537frYI7YuVd2JejV zYXq8enC{_(0%dj|8YV1&dqmAFyK`tbykL-0F8517`Qr@5po~44!-NHO5d}1>s6*2; z2@o8LCp0K{Na;ArxiSakUP(kQ`O@)IYf>#E-2}D9m#way>?&-}C zRINLWEI>1ttXQ(R@Tz*~H9!aqcCL{G@(f2vK`?aA6IKW1v>zBE3}A>!Nm|srl;-bL zre5uYKqw66|LriUlO6$tfPs(zAf=&oxLYmkZr-9I3}BL`E}AWBQQlwa*C7yQ;^j}m zNWa68khuZr{@iz+oSiUWlzHl+ZPtw&Ot-b82pm>^!5jl2$#)J$UK{(3X1Mgb6j@;Dmtyy1w|(D6&Lp?2%PITsd|mL>7e$?#kqMw61NE4Kf$r&4 zjgYgDGl82cXOc?E8A5Fe zLuwuq5) zskA8!`+_qg{iT!+1{f@yq7F%SX0U2udtV|>=5xwz+SB+9z1nbDW~SAS}_kv_~BLJ@aQ{*AO@vPZeI zp2(E_b2}YJ?r35F1)ue(&Luk(mUZQ2|D)B5m*lmlK8n~CfxoOjB75T~>)|i^dt0`u z3p!Q7*nojDbfyRQw0x_ML|NyZ87)$^CfDa(OAQuvXOQF=btvSWq~9TXNdI-mF18x&iFTuHsk2{JN}K1<4>E! zHd3TZLnB#~QacHu^v86fF{3j+C&ofTpmFOaL4@S?p3y6l)A647Uf-tRh0kYvk9WUw z?z!jPDEh|XI2?!L;(FltHygqEslP(omp4WjhTq%ezF;`X*ZT}5nM@McWh1bCX?>Hys{U7bU2YPYC$dl1 zKMrD8^=z=s4$)Bxb6RLmgKW*rVsQ>6_qVp*d zC+i-|1GsdO;OwIy2IHQmL(#UQ9OWPmz5&@!49=o{Ps(F(=CQm$hzoh3o&P?jcF!7G zg^AU^QzHrAb0KeZ897nkm_GDwCf{ z9%5P4aniL*SlY)nH(jR)PL-?&Eal1SQ#msluawyywU)K zGzOlBb5ggoltS~K*GCgwTOf(0Dfv{4$fts{F9@jKEkAeUC<5`zZuajFP5WbRJ>t{{ zi#YcnkUS0m?vLH;nNGV3p{4c{!8P9wA&ulUZ(2g$Ny-cOsbFU?;yJ-lfaDK|XhQA1 zBBW_3aUn0jEmK?O#T65b`>x-bg*q|- z43@?jr?Zd6X?dU}Vut{%F9hJeZkrLJdg@$UwRIe#_KDIF)Q3xEZ~&Il$&XLAE;x21 z*(Q2CE*mUwtaLEVk;@-{2fWcLR`b(m)8Lw} zm0I#UPQT&3RlS8u1c_sC*6|4wNX@$O0Gj^zdMh#+|^E$Q!c=JfXHM{RSi_$s&<`H#J& zF!=skoXHML|rvJS|S+y<5c`KYTilKU%t1 zANUe0K-Vq?b%7 diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/Icon.png deleted file mode 100644 index b7e2e57aa90f8687bdfa19d82ff6bda71535b8d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3237 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 Xx9ez7Rf!D(00000NkvXXu0mjfmS|-l diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/Icon.png b/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/Icon.png deleted file mode 100644 index 8d20a38d1782921964638e873ffc98d148ebda6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5414 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 c0$sN|hZ~f>0q(p1ua5js-$bwSo@3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LaunchDarkly.XamarinSdk.sln b/LaunchDarkly.XamarinSdk.sln index 9611317e..98809c03 100644 --- a/LaunchDarkly.XamarinSdk.sln +++ b/LaunchDarkly.XamarinSdk.sln @@ -6,10 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Tests", "tests\LaunchDarkly.XamarinSdk.Tests\LaunchDarkly.XamarinSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Android.Tests", "LaunchDarkly.XamarinSdk.Android.Tests\LaunchDarkly.XamarinSdk.Android.Tests.csproj", "{0B18C336-C770-42C1-B77A-E4A49F789677}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.iOS.Tests", "tests\LaunchDarkly.XamarinSdk.iOS.Tests\LaunchDarkly.XamarinSdk.iOS.Tests.csproj", "{5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Android.Tests", "tests\LaunchDarkly.XamarinSdk.Android.Tests\LaunchDarkly.XamarinSdk.Android.Tests.csproj", "{2E7720E4-01A0-403B-863C-C6C596DF5926}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,18 +44,6 @@ Global {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.ActiveCfg = Debug|Any CPU {F6B71DFE-314C-4F27-A219-A14569C8CF48}.Debug|iPhone.Build.0 = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|Any CPU.Build.0 = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhone.ActiveCfg = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhone.Build.0 = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {0B18C336-C770-42C1-B77A-E4A49F789677}.Debug|iPhone.Build.0 = Debug|Any CPU {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|Any CPU.ActiveCfg = Release|iPhone @@ -68,6 +56,18 @@ Global {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhone.ActiveCfg = Debug|iPhone {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}.Debug|iPhone.Build.0 = Debug|iPhone + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|Any CPU.Build.0 = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|iPhone.ActiveCfg = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|iPhone.Build.0 = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt new file mode 100644 index 00000000..b0633374 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with you package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj new file mode 100644 index 00000000..0202bfd4 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -0,0 +1,180 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2E7720E4-01A0-403B-863C-C6C596DF5926} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + Library + Properties + LaunchDarkly.XamarinSdk.Android.Tests + LaunchDarkly.XamarinSdk.Android.Tests + 512 + True + Resources\Resource.designer.cs + Resource + Off + v8.1 + Properties\AndroidManifest.xml + Resources + Assets + true + + + True + portable + False + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + None + False + + + True + pdbonly + True + bin\Release\ + TRACE + prompt + 4 + true + False + SdkOnly + True + + + + + + + + + + + + + + + SharedTestCode\BaseTest.cs + + + SharedTestCode\ConfigurationTest.cs + + + SharedTestCode\FeatureFlagListenerTests.cs + + + SharedTestCode\FeatureFlagRequestorTests.cs + + + SharedTestCode\FeatureFlagTests.cs + + + SharedTestCode\FlagCacheManagerTests.cs + + + SharedTestCode\LDClientEndToEndTests.cs + + + SharedTestCode\LdClientEvaluationTests.cs + + + SharedTestCode\LdClientEventTests.cs + + + SharedTestCode\LdClientTests.cs + + + SharedTestCode\LogSink.cs + + + SharedTestCode\MobilePollingProcessorTests.cs + + + SharedTestCode\MockComponents.cs + + + SharedTestCode\TestUtil.cs + + + SharedTestCode\UserFlagCacheTests.cs + + + SharedTestCode\WireMockExtensions.cs + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.4.1 + + + 1.0.20 + + + 2.4.1 + + + 4.0.0.497661 + + + 2.5.25 + + + + + + + + {7717A2B2-9905-40A7-989F-790139D69543} + LaunchDarkly.XamarinSdk + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs new file mode 100644 index 00000000..ea291a39 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs @@ -0,0 +1,24 @@ +using System.Reflection; + +using Android.App; +using Android.OS; +using Xunit.Runners.UI; +using Xunit.Sdk; + +namespace LaunchDarkly.Xamarin.Android.Tests +{ + [Activity(Label = "LaunchDarkly.Xamarin.Android.Tests", MainLauncher = true)] + public class MainActivity : RunnerActivity + { + protected override void OnCreate(Bundle bundle) + { + AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); + AddTestAssembly(Assembly.GetExecutingAssembly()); + + AutoStart = true; + //TerminateAfterExecution = true; + + base.OnCreate(bundle); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml new file mode 100644 index 00000000..4e00401d --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..bdf7f78e --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Android.App; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.Android.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.Android.Tests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt similarity index 97% rename from LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt rename to tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt index 10f52d46..c2bca974 100644 --- a/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt @@ -41,4 +41,4 @@ public class R { You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main to reference the layout/main.axml file, or R.strings.first_string to reference the first -string in the dictionary file values/strings.xml. +string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs new file mode 100644 index 00000000..a119fbb2 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -0,0 +1,9543 @@ +#pragma warning disable 1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.XamarinSdk.Android.Tests.Resource", IsApplication=true)] + +namespace LaunchDarkly.XamarinSdk.Android.Tests +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + public partial class Resource + { + + static Resource() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + public static void UpdateIdValues() + { + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_enter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_popup_enter; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_exit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_popup_exit; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_shrink_fade_out_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_shrink_fade_out_from_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_slide_in_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_slide_in_top; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_slide_out_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_slide_out_top; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.design_bottom_sheet_slide_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.design_bottom_sheet_slide_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.design_snackbar_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.design_snackbar_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.EnterFromLeft; + global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.EnterFromRight; + global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.ExitToLeft; + global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.ExitToRight; + global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_enter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.tooltip_enter; + global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_exit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.tooltip_exit; + global::Xamarin.Forms.Platform.Android.Resource.Animator.design_appbar_state_list_animator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animator.design_appbar_state_list_animator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarDivider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarDivider; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarItemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarPopupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarPopupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSplitStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarSplitStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarTabBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarTabStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarTabTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarWidgetTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionBarWidgetTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionDropDownStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionDropDownStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionMenuTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionMenuTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeCloseButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeCloseDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCopyDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeCopyDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCutDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeCutDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeFindDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeFindDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePasteDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModePasteDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePopupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModePopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSelectAllDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeSelectAllDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeShareDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeShareDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSplitBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeSplitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeWebSearchDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionModeWebSearchDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionOverflowButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowMenuStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionOverflowMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionProviderClass = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionProviderClass; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionViewClass = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.actionViewClass; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.activityChooserViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.activityChooserViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogButtonGroupStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alertDialogButtonGroupStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogCenterButtons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alertDialogCenterButtons; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alertDialogStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alertDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.allowStacking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.allowStacking; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alpha; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alphabeticModifiers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.alphabeticModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowHeadLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.arrowHeadLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowShaftLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.arrowShaftLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoCompleteTextViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoCompleteTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMaxTextSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoSizeMaxTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMinTextSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoSizeMinTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizePresetSizes = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoSizePresetSizes; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeStepGranularity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoSizeStepGranularity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeTextType = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.autoSizeTextType; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.background; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundSplit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundStacked = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.backgroundStacked; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.barLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.barLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_autoHide = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.behavior_autoHide; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_hideable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.behavior_hideable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_overlapTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.behavior_overlapTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_peekHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.behavior_peekHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_skipCollapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.behavior_skipCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.borderWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderlessButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.borderlessButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetDialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.bottomSheetDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.bottomSheetStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonBarButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNegativeButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonBarNegativeButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNeutralButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonBarNeutralButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarPositiveButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonBarPositiveButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonPanelSideLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonPanelSideLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyleSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.buttonTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardBackgroundColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardBackgroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardCornerRadius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardCornerRadius; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardElevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardElevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardMaxElevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardMaxElevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardPreventCornerOverlap = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardPreventCornerOverlap; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardUseCompatPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.cardUseCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkboxStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.checkboxStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkedTextViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.checkedTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.closeIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.closeItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.collapseContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.collapseIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.collapsedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.collapsedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.color; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorAccent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorAccent; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorBackgroundFloating = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorBackgroundFloating; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorButtonNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorButtonNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlActivated = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorControlActivated; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlHighlight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorControlHighlight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorControlNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorError = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorError; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimary = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorPrimary; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimaryDark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorPrimaryDark; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorSwitchThumbNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.colorSwitchThumbNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.commitIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.commitIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEndWithActions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStartWithNavigation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentScrim = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.contentScrim; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.controlBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.controlBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.counterEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterMaxLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.counterMaxLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterOverflowTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.counterOverflowTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.counterTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.customNavigationLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.customNavigationLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.defaultQueryHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.defaultQueryHint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogPreferredPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dialogPreferredPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.displayOptions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.displayOptions; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.divider; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerHorizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dividerHorizontal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dividerPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerVertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dividerVertical; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawableSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.drawableSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawerArrowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.drawerArrowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropDownListViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dropDownListViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropdownListPreferredItemHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.dropdownListPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.editTextBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.editTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.editTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.elevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.errorEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.errorTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandActivityOverflowButtonDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandActivityOverflowButtonDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expanded = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expanded; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMargin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.expandedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fabSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fabSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fastScrollEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalThumbDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fastScrollHorizontalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalTrackDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fastScrollHorizontalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalThumbDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fastScrollVerticalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalTrackDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fastScrollVerticalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.font; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderCerts; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderPackage; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderQuery; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.foregroundInsidePadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.foregroundInsidePadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.gapBetweenBars = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.gapBetweenBars; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.goIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.goIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.headerLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.headerLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.height; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hideOnContentScroll = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.hideOnContentScroll; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintAnimationEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.hintAnimationEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.hintEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.hintTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeAsUpIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.homeLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.icon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.iconTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.iconTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconifiedByDefault = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.iconifiedByDefault; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.imageButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.imageButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.indeterminateProgressStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.indeterminateProgressStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.initialActivityCount = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.initialActivityCount; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.insetForeground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.insetForeground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.isLightTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.isLightTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemIconTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.itemPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.keylines = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.keylines; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layoutManager = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layoutManager; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchorGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_anchorGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_behavior = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_behavior; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_collapseMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseParallaxMultiplier = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_collapseParallaxMultiplier; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_dodgeInsetEdges = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_dodgeInsetEdges; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_insetEdge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_insetEdge; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_keyline = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_keyline; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollFlags = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_scrollFlags; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollInterpolator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.layout_scrollInterpolator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listChoiceBackgroundIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listChoiceBackgroundIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listDividerAlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listDividerAlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listMenuViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listMenuViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPopupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPreferredItemHeightLarge; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPreferredItemHeightSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPreferredItemPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.listPreferredItemPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.logo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.logo; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.logoDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.logoDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxActionInlineWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.maxActionInlineWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxButtonHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.maxButtonHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.measureWithLargestChild = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.measureWithLargestChild; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.menu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.multiChoiceItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.multiChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.navigationContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.navigationIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.navigationMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.numericModifiers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.numericModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.overlapAnchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.overlapAnchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingBottomNoButtons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.paddingBottomNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.paddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.paddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingTopNoTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.paddingTopNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.panelBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.panelMenuListTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.panelMenuListWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.passwordToggleContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.passwordToggleDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.passwordToggleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.passwordToggleTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.passwordToggleTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupMenuStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.popupMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.popupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.preserveIconSpacing = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.preserveIconSpacing; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.pressedTranslationZ = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.pressedTranslationZ; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.progressBarPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.progressBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.queryBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.queryHint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.radioButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.radioButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.ratingBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.ratingBarStyleIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.ratingBarStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.reverseLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.reverseLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.rippleColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.rippleColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimAnimationDuration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.scrimAnimationDuration; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimVisibleHeightTrigger = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.scrimVisibleHeightTrigger; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchHintIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.searchHintIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.searchIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.searchViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.seekBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.seekBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.selectableItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackgroundBorderless = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.selectableItemBackgroundBorderless; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showAsAction = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.showAsAction; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showDividers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.showDividers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.showText; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.singleChoiceItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.singleChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spanCount = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.spanCount; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinBars = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.spinBars; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerDropDownItemStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.spinnerDropDownItemStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.spinnerStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.splitTrack = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.splitTrack; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.srcCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.srcCompat; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.stackFromEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.stackFromEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_above_anchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.state_above_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.state_collapsed; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.state_collapsible; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.statusBarBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarScrim = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.statusBarScrim; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subMenuArrow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.subMenuArrow; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.submitBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.submitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.subtitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.subtitleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.suggestionRowLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.suggestionRowLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchMinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.switchMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.switchPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.switchStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.switchTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabContentStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabContentStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabIndicatorColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabIndicatorHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMaxWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabMaxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabPaddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabPaddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabSelectedTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabSelectedTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tabTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAllCaps = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceLargePopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceLargePopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceListItem; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSecondary = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceListItemSecondary; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceListItemSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearancePopupMenuHeader = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearancePopupMenuHeader; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultSubtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceSearchResultSubtitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceSearchResultTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSmallPopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textAppearanceSmallPopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorAlertDialogListItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textColorAlertDialogListItem; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorError = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textColorError; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorSearchUrl = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.textColorSearchUrl; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.theme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.theme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thickness = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.thickness; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTextPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.thumbTextPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.thumbTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.thumbTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tickMark; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tickMarkTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tickMarkTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.title; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargins = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleMargins; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarId = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.toolbarId; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarNavigationButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.toolbarNavigationButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.toolbarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipForegroundColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tooltipForegroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipFrameBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tooltipFrameBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.tooltipText; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.track = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.track; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.trackTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.trackTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.useCompatPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.useCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.voiceIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.voiceIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBarOverlay = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowActionBarOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionModeOverlay = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowActionModeOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowFixedHeightMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowFixedHeightMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowFixedWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowFixedWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowMinWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowMinWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowNoTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.windowNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_allow_stacked_button_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_allow_stacked_button_bar; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_actionMenuItemAllCaps = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_config_actionMenuItemAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_closeDialogWhenTouchOutside = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_config_closeDialogWhenTouchOutside; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_borderless_text_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_btn_colored_borderless_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_text_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_btn_colored_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_color_highlight_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_color_highlight_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_hint_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_hint_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_input_method_navigation_guard = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_input_method_navigation_guard; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_primary_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_primary_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_search_url_text; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_search_url_text_normal; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_search_url_text_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_selected = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_search_url_text_selected; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_secondary_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_secondary_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_btn_checkable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_btn_checkable; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_default = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_default; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_edittext = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_edittext; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_seek_thumb = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_seek_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_spinner; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_switch_track = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.abc_tint_switch_track; + global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.accent_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.accent_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.background_floating_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.background_floating_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.background_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.background_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_inverse_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_inverse_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.bright_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.button_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.button_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_dark_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.cardview_dark_background; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_light_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.cardview_light_background; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_end_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.cardview_shadow_end_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_start_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.cardview_shadow_start_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_bottom_navigation_shadow_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_bottom_navigation_shadow_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_error = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_error; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_end_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_shadow_end_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_mid_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_shadow_mid_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_start_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_shadow_start_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_inner_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_stroke_end_inner_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_outer_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_stroke_end_outer_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_inner_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_stroke_top_inner_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_outer_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_fab_stroke_top_outer_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_snackbar_background_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_snackbar_background_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_tint_password_toggle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.design_tint_password_toggle; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.dim_foreground_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.dim_foreground_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.dim_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.dim_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.error_color_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.error_color_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.highlighted_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.highlighted_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_800 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_blue_grey_800; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_900 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_blue_grey_900; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_950 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_blue_grey_950; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_200 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_deep_teal_200; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_500 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_deep_teal_500; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_100 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_100; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_300 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_300; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_50 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_50; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_600 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_600; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_800 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_800; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_850 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_850; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_900 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.material_grey_900; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_action_color_filter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_action_color_filter; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_icon_bg_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_material_background_media_default_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_dark_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_dark_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_text_default_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_text_default_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_text_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.primary_text_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_normal_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.switch_thumb_normal_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.tooltip_background_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.tooltip_background_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_with_nav = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_with_nav; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_height_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_end_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_end_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_start_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_elevation_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_elevation_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_icon_vertical_padding_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_icon_vertical_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_end_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_end_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_start_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_progress_bar_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_progress_bar_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_max_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_stacked_max_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_tab_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_stacked_tab_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_top_margin_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_top_margin_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_height_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_button_min_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_button_min_width_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_overflow_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_action_button_min_width_overflow_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_alert_dialog_button_bar_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_alert_dialog_button_bar_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_button_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_button_inset_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_button_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_button_padding_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_cascading_menus_min_smallest_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_cascading_menus_min_smallest_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_config_prefDialogWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_config_prefDialogWidth; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_control_corner_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_inset_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_control_inset_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_padding_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_control_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_major = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_minor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_major = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_minor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_top_no_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_list_padding_top_no_title; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_major = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_min_width_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_minor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_min_width_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_top_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_padding_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_title_divider_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dialog_title_divider_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_icon_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_left = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_left; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_right = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_right; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_bottom_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_bottom_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_top_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_floating_window_z = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_floating_window_z; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_list_item_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_list_item_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_panel_menu_list_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_panel_menu_list_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_progress_bar_height_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_progress_bar_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_search_view_preferred_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_search_view_preferred_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_background_height_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_seekbar_track_background_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_progress_height_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_seekbar_track_progress_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_select_dialog_padding_start_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_select_dialog_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_switch_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_switch_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_1_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_body_1_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_2_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_body_2_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_button_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_button_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_caption_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_caption_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_1_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_display_1_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_2_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_display_2_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_3_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_display_3_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_4_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_display_4_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_headline_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_headline_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_large_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_large_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_medium_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_medium_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_header_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_menu_header_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_menu_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_small_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_small_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subhead_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_subhead_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subtitle_material_toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_subtitle_material_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material_toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.abc_text_size_title_material_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_compat_inset_shadow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.cardview_compat_inset_shadow; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.cardview_default_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_radius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.cardview_default_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_appbar_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_appbar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_item_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_active_item_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_active_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_item_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_min_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_item_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_shadow_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_shadow_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_modal_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_sheet_modal_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_peek_height_min = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_bottom_sheet_peek_height_min; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_border_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_border_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_image_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_image_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_mini = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_size_mini; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_size_normal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_translation_z_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_fab_translation_z_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_icon_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_padding_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_padding_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_separator_vertical_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_navigation_separator_vertical_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_action_inline_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_action_inline_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_background_corner_radius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_background_corner_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_extra_spacing_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_extra_spacing_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_min_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical_2lines = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical_2lines; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_snackbar_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_max_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_tab_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_scrollable_min_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_tab_scrollable_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_tab_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size_2line = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.design_tab_text_size_2line; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.disabled_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.disabled_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_default_thickness = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.fastscroll_default_thickness; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.fastscroll_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_minimum_range = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.fastscroll_minimum_range; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_colored; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.hint_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.hint_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_velocity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_velocity; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_subtext_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_corner_radius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_corner_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_horizontal_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_horizontal_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_extra_offset = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_precise_anchor_extra_offset; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_threshold = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_precise_anchor_threshold; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_vertical_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_vertical_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_non_touch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_y_offset_non_touch; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_touch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.tooltip_y_offset_touch; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ab_share_pack_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ab_share_pack_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_action_bar_item_background_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_action_bar_item_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_borderless_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_borderless_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_check_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_000 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_015 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_015; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_colored_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_colored_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_default_mtrl_shape = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_default_mtrl_shape; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_radio_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_000 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_015 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_015; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_internal_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_cab_background_internal_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_cab_background_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_cab_background_top_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_control_background_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_control_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_dialog_material_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_dialog_material_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_edit_text_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_edit_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_ab_back_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_ab_back_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_clear_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_clear_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_go_search_api_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_go_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_overflow_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_overflow_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_share_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_menu_share_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_search_api_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_16dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_16dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_36dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_36dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_48dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_48dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_16dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_16dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_36dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_36dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_48dp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_48dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_voice_search_api_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ic_voice_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_item_background_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_item_background_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_divider_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_divider_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_focused_holo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_focused_holo; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_longpressed_holo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_longpressed_holo; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_pressed_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_pressed_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_list_selector_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_popup_background_mtrl_mult = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_popup_background_mtrl_mult; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_indicator_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ratingbar_indicator_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ratingbar_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_small_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_ratingbar_small_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_primary_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_scrubber_primary_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_track_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_scrubber_track_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_thumb_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_seekbar_thumb_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_tick_mark_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_seekbar_tick_mark_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_track_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_seekbar_track_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_mtrl_am_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_spinner_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_textfield_background_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_spinner_textfield_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_thumb_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_switch_thumb_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_track_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_switch_track_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_tab_indicator_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_tab_indicator_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_cursor_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_cursor_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_activated_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_activated_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_default_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_default_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_default_mtrl_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_search_default_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_search_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_vector_test = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_vector_test; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_1; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_2; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_3; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_1; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_2; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_3; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_bottom_navigation_item_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_bottom_navigation_item_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_fab_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_fab_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_ic_visibility; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility_off = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_ic_visibility_off; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_password_eye = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_password_eye; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_snackbar_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_snackbar_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.navigation_empty_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.navigation_empty_icon; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_action_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_action_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_icon_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_icon_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_tile_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.tooltip_frame_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.tooltip_frame_light; + global::Xamarin.Forms.Platform.Android.Resource.Id.ALT = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.ALT; + global::Xamarin.Forms.Platform.Android.Resource.Id.CTRL = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.CTRL; + global::Xamarin.Forms.Platform.Android.Resource.Id.FUNCTION = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.FUNCTION; + global::Xamarin.Forms.Platform.Android.Resource.Id.META = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.META; + global::Xamarin.Forms.Platform.Android.Resource.Id.SHIFT = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.SHIFT; + global::Xamarin.Forms.Platform.Android.Resource.Id.SYM = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.SYM; + global::Xamarin.Forms.Platform.Android.Resource.Id.action0 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action0; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_activity_content = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_activity_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_root = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_root; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_spinner; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_bar_title; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_context_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_context_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_divider; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_image; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_menu_divider; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_presenter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_menu_presenter; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_mode_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar_stub = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_mode_bar_stub; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_close_button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_mode_close_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.actions; + global::Xamarin.Forms.Platform.Android.Resource.Id.activity_chooser_view_content = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.activity_chooser_view_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.add = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.add; + global::Xamarin.Forms.Platform.Android.Resource.Id.alertTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.alertTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.all = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.all; + global::Xamarin.Forms.Platform.Android.Resource.Id.always = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.always; + global::Xamarin.Forms.Platform.Android.Resource.Id.async = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.async; + global::Xamarin.Forms.Platform.Android.Resource.Id.auto = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.auto; + global::Xamarin.Forms.Platform.Android.Resource.Id.beginning = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.beginning; + global::Xamarin.Forms.Platform.Android.Resource.Id.blocking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.blocking; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.bottom; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_navarea = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.bottomtab_navarea; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_tabbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.bottomtab_tabbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.buttonPanel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.buttonPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.cancel_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.cancel_action; + global::Xamarin.Forms.Platform.Android.Resource.Id.center = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.center; + global::Xamarin.Forms.Platform.Android.Resource.Id.center_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.center_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.center_vertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.center_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.checkbox = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.checkbox; + global::Xamarin.Forms.Platform.Android.Resource.Id.chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.chronometer; + global::Xamarin.Forms.Platform.Android.Resource.Id.clip_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.clip_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.clip_vertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.clip_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.collapseActionView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.collapseActionView; + global::Xamarin.Forms.Platform.Android.Resource.Id.container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.container; + global::Xamarin.Forms.Platform.Android.Resource.Id.contentPanel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.contentPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.coordinator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.coordinator; + global::Xamarin.Forms.Platform.Android.Resource.Id.custom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.custom; + global::Xamarin.Forms.Platform.Android.Resource.Id.customPanel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.customPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.decor_content_parent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.decor_content_parent; + global::Xamarin.Forms.Platform.Android.Resource.Id.default_activity_button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.default_activity_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_bottom_sheet = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.design_bottom_sheet; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.design_menu_item_action_area; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area_stub = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.design_menu_item_action_area_stub; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.design_menu_item_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_navigation_view = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.design_navigation_view; + global::Xamarin.Forms.Platform.Android.Resource.Id.disableHome = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.disableHome; + global::Xamarin.Forms.Platform.Android.Resource.Id.edit_query = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.edit_query; + global::Xamarin.Forms.Platform.Android.Resource.Id.end = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.end; + global::Xamarin.Forms.Platform.Android.Resource.Id.end_padder = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.end_padder; + global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlways = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.enterAlways; + global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlwaysCollapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.enterAlwaysCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Id.exitUntilCollapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.exitUntilCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Id.expand_activities_button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.expand_activities_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.expanded_menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.expanded_menu; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.fill; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.fill_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill_vertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.fill_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.@fixed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.@fixed; + global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_appbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.flyoutcontent_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_recycler = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.flyoutcontent_recycler; + global::Xamarin.Forms.Platform.Android.Resource.Id.forever = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.forever; + global::Xamarin.Forms.Platform.Android.Resource.Id.ghost_view = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.ghost_view; + global::Xamarin.Forms.Platform.Android.Resource.Id.home = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.home; + global::Xamarin.Forms.Platform.Android.Resource.Id.homeAsUp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.homeAsUp; + global::Xamarin.Forms.Platform.Android.Resource.Id.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon_group; + global::Xamarin.Forms.Platform.Android.Resource.Id.ifRoom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.ifRoom; + global::Xamarin.Forms.Platform.Android.Resource.Id.image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.image; + global::Xamarin.Forms.Platform.Android.Resource.Id.info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.info; + global::Xamarin.Forms.Platform.Android.Resource.Id.italic = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.italic; + global::Xamarin.Forms.Platform.Android.Resource.Id.item_touch_helper_previous_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.item_touch_helper_previous_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Id.largeLabel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.largeLabel; + global::Xamarin.Forms.Platform.Android.Resource.Id.left = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.left; + global::Xamarin.Forms.Platform.Android.Resource.Id.line1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line1; + global::Xamarin.Forms.Platform.Android.Resource.Id.line3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line3; + global::Xamarin.Forms.Platform.Android.Resource.Id.listMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.listMode; + global::Xamarin.Forms.Platform.Android.Resource.Id.list_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.list_item; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_appbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.main_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_scrollview = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.main_scrollview; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_tablayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.main_tablayout; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.main_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.masked = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.masked; + global::Xamarin.Forms.Platform.Android.Resource.Id.media_actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.media_actions; + global::Xamarin.Forms.Platform.Android.Resource.Id.message = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.message; + global::Xamarin.Forms.Platform.Android.Resource.Id.middle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.middle; + global::Xamarin.Forms.Platform.Android.Resource.Id.mini = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.mini; + global::Xamarin.Forms.Platform.Android.Resource.Id.multiply = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.multiply; + global::Xamarin.Forms.Platform.Android.Resource.Id.navigation_header_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.navigation_header_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.never = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.never; + global::Xamarin.Forms.Platform.Android.Resource.Id.none = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.none; + global::Xamarin.Forms.Platform.Android.Resource.Id.normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.normal; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_background; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.parallax = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.parallax; + global::Xamarin.Forms.Platform.Android.Resource.Id.parentPanel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.parentPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.parent_matrix = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.parent_matrix; + global::Xamarin.Forms.Platform.Android.Resource.Id.pin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.pin; + global::Xamarin.Forms.Platform.Android.Resource.Id.progress_circular = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.progress_circular; + global::Xamarin.Forms.Platform.Android.Resource.Id.progress_horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.progress_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.radio = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.radio; + global::Xamarin.Forms.Platform.Android.Resource.Id.right = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right; + global::Xamarin.Forms.Platform.Android.Resource.Id.right_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.right_side = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_side; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_image_matrix = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.save_image_matrix; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_non_transition_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.save_non_transition_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_scale_type = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.save_scale_type; + global::Xamarin.Forms.Platform.Android.Resource.Id.screen = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.screen; + global::Xamarin.Forms.Platform.Android.Resource.Id.scroll = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.scroll; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.scrollIndicatorDown; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorUp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.scrollIndicatorUp; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.scrollView; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.scrollable; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_badge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_badge; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_close_btn = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_close_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_edit_frame = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_edit_frame; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_go_btn = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_go_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_mag_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_mag_icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_plate = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_plate; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_src_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_src_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_voice_btn = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.search_voice_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.select_dialog_listview = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.select_dialog_listview; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_appbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.shellcontent_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_scrollview = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.shellcontent_scrollview; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.shellcontent_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.shortcut = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.shortcut; + global::Xamarin.Forms.Platform.Android.Resource.Id.showCustom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.showCustom; + global::Xamarin.Forms.Platform.Android.Resource.Id.showHome = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.showHome; + global::Xamarin.Forms.Platform.Android.Resource.Id.showTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.smallLabel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.smallLabel; + global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.snackbar_action; + global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.snackbar_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.snap = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.snap; + global::Xamarin.Forms.Platform.Android.Resource.Id.spacer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.spacer; + global::Xamarin.Forms.Platform.Android.Resource.Id.split_action_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.split_action_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_atop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.src_atop; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.src_in; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_over = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.src_over; + global::Xamarin.Forms.Platform.Android.Resource.Id.start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.start; + global::Xamarin.Forms.Platform.Android.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.status_bar_latest_event_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.submenuarrow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.submenuarrow; + global::Xamarin.Forms.Platform.Android.Resource.Id.submit_area = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.submit_area; + global::Xamarin.Forms.Platform.Android.Resource.Id.tabMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Id.tag_transition_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tag_transition_group; + global::Xamarin.Forms.Platform.Android.Resource.Id.text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text; + global::Xamarin.Forms.Platform.Android.Resource.Id.text2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text2; + global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoButtons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.textSpacerNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.textSpacerNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.text_input_password_toggle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text_input_password_toggle; + global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_counter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.textinput_counter; + global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_error = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.textinput_error; + global::Xamarin.Forms.Platform.Android.Resource.Id.time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.time; + global::Xamarin.Forms.Platform.Android.Resource.Id.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title; + global::Xamarin.Forms.Platform.Android.Resource.Id.titleDividerNoCustom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.titleDividerNoCustom; + global::Xamarin.Forms.Platform.Android.Resource.Id.title_template = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title_template; + global::Xamarin.Forms.Platform.Android.Resource.Id.top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.top; + global::Xamarin.Forms.Platform.Android.Resource.Id.topPanel = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.topPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.touch_outside = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.touch_outside; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_current_scene = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.transition_current_scene; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_layout_save = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.transition_layout_save; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_position = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.transition_position; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_scene_layoutid_cache = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.transition_scene_layoutid_cache; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_transform = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.transition_transform; + global::Xamarin.Forms.Platform.Android.Resource.Id.uniform = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.uniform; + global::Xamarin.Forms.Platform.Android.Resource.Id.up = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.up; + global::Xamarin.Forms.Platform.Android.Resource.Id.useLogo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.useLogo; + global::Xamarin.Forms.Platform.Android.Resource.Id.view_offset_helper = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.view_offset_helper; + global::Xamarin.Forms.Platform.Android.Resource.Id.visible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.visible; + global::Xamarin.Forms.Platform.Android.Resource.Id.withText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.withText; + global::Xamarin.Forms.Platform.Android.Resource.Id.wrap_content = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.wrap_content; + global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityDefaultDur = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.abc_config_activityDefaultDur; + global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityShortDur = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.abc_config_activityShortDur; + global::Xamarin.Forms.Platform.Android.Resource.Integer.app_bar_elevation_anim_duration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.app_bar_elevation_anim_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.bottom_sheet_slide_duration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.bottom_sheet_slide_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.cancel_button_image_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Integer.config_tooltipAnimTime = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.config_tooltipAnimTime; + global::Xamarin.Forms.Platform.Android.Resource.Integer.design_snackbar_text_max_lines = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.design_snackbar_text_max_lines; + global::Xamarin.Forms.Platform.Android.Resource.Integer.hide_password_duration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.hide_password_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.show_password_duration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.show_password_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_title_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_bar_title_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_up_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_bar_up_container; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_item_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_menu_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_bar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_mode_bar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_close_item_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_action_mode_close_item_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_activity_chooser_view; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view_list_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_activity_chooser_view_list_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_button_bar_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_alert_dialog_button_bar_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_alert_dialog_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_title_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_alert_dialog_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_dialog_title_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_dialog_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_expanded_menu_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_expanded_menu_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_checkbox = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_list_menu_item_checkbox; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_list_menu_item_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_list_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_radio = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_list_menu_item_radio; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_header_item_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_popup_menu_header_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_item_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_popup_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_content_include = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_screen_content_include; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_screen_simple; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple_overlay_action_mode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_screen_simple_overlay_action_mode; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_screen_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_dropdown_item_icons_2line = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_search_dropdown_item_icons_2line; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_view = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_search_view; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_select_dialog_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.abc_select_dialog_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.BottomTabLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.BottomTabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_navigation_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_bottom_navigation_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_sheet_dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_bottom_sheet_dialog; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_layout_snackbar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar_include = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_layout_snackbar_include; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_layout_tab_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_layout_tab_text; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_menu_item_action_area = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_menu_item_action_area; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_header = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_item_header; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_separator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_item_separator; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_subheader = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_item_subheader; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_menu; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_navigation_menu_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_text_input_password_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.design_text_input_password_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.FlyoutContent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.FlyoutContent; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action_tombstone; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_media_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_media_cancel_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_big_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_big_media_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_big_media_narrow; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_custom_big; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_icon_group; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_lines_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_media_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_time; + global::Xamarin.Forms.Platform.Android.Resource.Layout.RootLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.RootLayout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_item_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.select_dialog_item_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_multichoice_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.select_dialog_multichoice_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_singlechoice_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.select_dialog_singlechoice_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.ShellContent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.ShellContent; + global::Xamarin.Forms.Platform.Android.Resource.Layout.support_simple_spinner_dropdown_item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.support_simple_spinner_dropdown_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.tooltip = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.tooltip; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_home_description = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_action_bar_home_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_up_description = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_action_bar_up_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_menu_overflow_description = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_action_menu_overflow_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_mode_done = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_action_mode_done; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_activity_chooser_view_see_all = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_activity_chooser_view_see_all; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_activitychooserview_choose_application = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_activitychooserview_choose_application; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_off = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_capital_off; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_on = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_capital_on; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_1_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_body_1_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_2_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_body_2_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_button_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_button_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_caption_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_caption_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_1_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_display_1_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_2_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_display_2_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_3_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_display_3_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_4_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_display_4_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_headline_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_headline_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_menu_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_menu_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_subhead_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_subhead_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_title_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_font_family_title_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_search_hint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_search_hint; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_clear = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_searchview_description_clear; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_query = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_searchview_description_query; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_search = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_searchview_description_search; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_submit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_searchview_description_submit; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_voice = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_searchview_description_voice; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_shareactionprovider_share_with; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with_application = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_shareactionprovider_share_with_application; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_toolbar_collapse_description = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.abc_toolbar_collapse_description; + global::Xamarin.Forms.Platform.Android.Resource.String.appbar_scrolling_view_behavior = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.appbar_scrolling_view_behavior; + global::Xamarin.Forms.Platform.Android.Resource.String.bottom_sheet_behavior = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.bottom_sheet_behavior; + global::Xamarin.Forms.Platform.Android.Resource.String.character_counter_pattern = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.character_counter_pattern; + global::Xamarin.Forms.Platform.Android.Resource.String.password_toggle_content_description = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.password_toggle_content_description; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.path_password_eye; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_strike_through = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.path_password_eye_mask_strike_through; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_visible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.path_password_eye_mask_visible; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_strike_through = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.path_password_strike_through; + global::Xamarin.Forms.Platform.Android.Resource.String.search_menu_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.search_menu_title; + global::Xamarin.Forms.Platform.Android.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.AlertDialog_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.AlertDialog_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Animation_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_DropDownUp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Animation_AppCompat_DropDownUp; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Tooltip = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Animation_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_Design_BottomSheetDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Animation_Design_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_DropDownUp = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_DropDownUp; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Tooltip = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_CardView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_CardView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitle_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_DialogWindowTitle_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitleBackground_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_DialogWindowTitleBackground_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body1; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body2; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Caption = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Caption; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display1; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display2; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display3; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display4 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display4; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Headline = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Headline; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_CompactMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_CompactMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_EditText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V14_Widget_Design_AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V14_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Widget_Design_AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V21_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_AppCompat_Toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V26_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_Design_AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V26_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_EditText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_Toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_EditText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ImageButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ImageButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListMenuView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_TabLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Base_Widget_Design_TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.CardView; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.CardView_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.CardView_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V11_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V11_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V14_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V14_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V21_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V21_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V25_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_V25_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_Widget_AppCompat_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Platform_Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body1; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Caption = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Caption; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display1; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display3; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display4 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display4; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Headline = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Headline; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Counter; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Counter_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Error = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Error; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Hint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Hint; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Snackbar_Message = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Snackbar_Message; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Tab = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Design_Tab; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_CompactMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_CompactMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_NoActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_NoActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_NoActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_AppCompat_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_BottomSheetDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_BottomSheetDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design_Light_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_NoActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design_Light_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_NoActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Theme_Design_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Colored = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_EditText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ImageButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ImageButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActivityChooserView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListPopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListView_DropDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_SearchView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListMenuView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_Menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Indicator; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_SearchView_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar_Discrete; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_Underlined; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomNavigationView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_BottomNavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomSheet_Modal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_BottomSheet_Modal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CollapsingToolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_CollapsingToolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CoordinatorLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_CoordinatorLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_FloatingActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_FloatingActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_NavigationView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_NavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_ScrimInsetsFrameLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_ScrimInsetsFrameLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_Snackbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_Snackbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TabLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TextInputLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Design_TextInputLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundSplit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundStacked = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_backgroundStacked; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEndWithActions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStartWithNavigation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_customNavigationLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_customNavigationLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_displayOptions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_displayOptions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_divider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_hideOnContentScroll = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_hideOnContentScroll; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeAsUpIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_homeLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_indeterminateProgressStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_indeterminateProgressStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_itemPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_itemPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_logo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_logo; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_navigationMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_navigationMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_popupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_progressBarPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_progressBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_titleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBar_titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout_android_layout_gravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionBarLayout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMenuItemView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView_android_minWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMenuItemView_android_minWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_backgroundSplit = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_closeItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_closeItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_subtitleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_titleTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActionMode_titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_initialActivityCount = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ActivityChooserView_initialActivityCount; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_android_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_android_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_buttonPanelSideLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_buttonPanelSideLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_listItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_listLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_multiChoiceItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_multiChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_showTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_singleChoiceItemLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AlertDialog_singleChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_expanded = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_expanded; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatImageView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_android_src = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatImageView_android_src; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_srcCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatImageView_srcCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatImageView_tint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatImageView_tintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_android_thumb = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_android_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMark; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_textAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_textAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_android_textAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_android_textAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizePresetSizes = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizePresetSizes; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeTextType = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeTextType; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_fontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_textAllCaps = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTextView_textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarDivider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarItemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarPopupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarPopupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSplitStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSplitStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionDropDownStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionDropDownStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCutDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCutDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeFindDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeFindDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePasteDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePasteDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeShareDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeShareDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSplitBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSplitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_activityChooserViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_activityChooserViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowIsFloating = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowIsFloating; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_borderlessButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_borderlessButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyleSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkboxStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_checkboxStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkedTextViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_checkedTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorAccent = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorAccent; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorBackgroundFloating = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorBackgroundFloating; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorButtonNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorButtonNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlActivated = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlActivated; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlHighlight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlHighlight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorError = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorError; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimary = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimary; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimaryDark = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimaryDark; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_controlBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_controlBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogPreferredPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dialogPreferredPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerHorizontal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dividerHorizontal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerVertical = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dividerVertical; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropDownListViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dropDownListViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_homeAsUpIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_imageButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_imageButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listDividerAlertDialog = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listDividerAlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listMenuViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listMenuViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPopupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupMenuStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_popupMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupWindowStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_popupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_radioButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_radioButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_searchViewStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_searchViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_seekBarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_seekBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_switchStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_switchStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorSearchUrl = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textColorSearchUrl; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipForegroundColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipForegroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipFrameBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipFrameBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBarOverlay = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBarOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionModeOverlay = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionModeOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMajor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMinor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowNoTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemIconTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomNavigationView_menu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ButtonBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout_allowStacking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ButtonBarLayout_allowStacking; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_android_minHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_android_minWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardBackgroundColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardBackgroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardCornerRadius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardCornerRadius; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardElevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardElevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardMaxElevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardMaxElevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardPreventCornerOverlap = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardPreventCornerOverlap; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardUseCompatPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_cardUseCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_contentPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_contentScrim = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_contentScrim; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_titleEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_titleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_toolbarId = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_toolbarId; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ColorStateListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ColorStateListItem_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_alpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ColorStateListItem_android_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ColorStateListItem_android_color; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CompoundButton; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_android_button = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CompoundButton_android_button; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CompoundButton_buttonTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CompoundButton_buttonTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_keylines = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_keylines; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_statusBarBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_statusBarBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DesignTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetDialogTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_textColorError = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DesignTheme_textColorError; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowHeadLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowHeadLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowShaftLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowShaftLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_barLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_barLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_color; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_drawableSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_drawableSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_gapBetweenBars = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_gapBetweenBars; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_spinBars = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_spinBars; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_thickness = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_thickness; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_borderWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_borderWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_fabSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_fabSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_pressedTranslationZ = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_pressedTranslationZ; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_rippleColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_rippleColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_useCompatPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_useCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foreground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foreground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAligned = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAligned; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_gravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_orientation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_orientation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_weightSum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_weightSum; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_divider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_dividerPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_dividerPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_showDividers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_showDividers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_checkableBehavior = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_checkableBehavior; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_enabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_enabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_id = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_menuCategory = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_menuCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_orderInCategory = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_orderInCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_visible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuGroup_android_visible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_actionLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionProviderClass = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_actionProviderClass; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionViewClass = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_actionViewClass; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_alphabeticModifiers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_alphabeticModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_alphabeticShortcut = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_alphabeticShortcut; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checkable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_checkable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checked = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_checked; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_enabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_enabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_id = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_menuCategory = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_menuCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_numericShortcut = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_numericShortcut; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_onClick = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_onClick; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_orderInCategory = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_orderInCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_titleCondensed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_titleCondensed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_visible = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_android_visible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_contentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_contentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_iconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_iconTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_numericModifiers = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_numericModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_showAsAction = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_showAsAction; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_tooltipText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuItem_tooltipText; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_headerBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_headerBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_horizontalDivider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_horizontalDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemIconDisabledAlpha = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_itemIconDisabledAlpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_verticalDivider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_verticalDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_windowAnimationStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_android_windowAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_preserveIconSpacing = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_preserveIconSpacing; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_subMenuArrow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.MenuView_subMenuArrow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_fitsSystemWindows = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_android_fitsSystemWindows; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_maxWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_headerLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_headerLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemIconTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_menu = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.NavigationView_menu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupAnimationStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindow_android_popupAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindow_android_popupBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_overlapAnchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindow_overlapAnchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindowBackgroundState; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecycleListView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingBottomNoButtons = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecycleListView_paddingBottomNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingTopNoTitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecycleListView_paddingTopNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_descendantFocusability = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_android_descendantFocusability; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_orientation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_android_orientation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_layoutManager = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_layoutManager; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_reverseLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_reverseLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_spanCount = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_spanCount; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_stackFromEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.RecyclerView_stackFromEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_focusable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_android_focusable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_imeOptions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_android_imeOptions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_inputType = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_android_inputType; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_maxWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_closeIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_closeIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_commitIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_commitIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_defaultQueryHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_defaultQueryHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_goIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_goIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_iconifiedByDefault = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_iconifiedByDefault; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_queryBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_queryHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchHintIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_searchHintIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_searchIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_submitBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_submitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_suggestionRowLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_suggestionRowLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_voiceIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SearchView_voiceIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SnackbarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_android_maxWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SnackbarLayout_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_elevation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SnackbarLayout_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_maxActionInlineWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SnackbarLayout_maxActionInlineWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_dropDownWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner_android_dropDownWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_entries = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner_android_entries; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_popupBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner_android_popupBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_prompt = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner_android_prompt; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_popupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Spinner_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOff = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_textOff; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOn = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_textOn; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_thumb = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_showText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_showText; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_splitTrack = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_splitTrack; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchMinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTextPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTextPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_track = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_track; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_trackTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.SwitchCompat_trackTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabItem_android_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabItem_android_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabItem_android_text; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabBackground = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabContentStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabContentStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMaxWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabMaxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMinWidth = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPadding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabSelectedTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabSelectedTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TabLayout_tabTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_fontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDx = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDx; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDy; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowRadius = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowRadius; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColorHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorLink = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColorLink; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textSize = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_typeface = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_android_typeface; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_fontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_textAllCaps = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextAppearance_textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_hint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_android_hint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_textColorHint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_android_textColorHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterMaxLength = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterMaxLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_errorEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_errorTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintAnimationEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintAnimationEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleDrawable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleEnabled = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_gravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_android_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_minHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_android_minHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_buttonGravity = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_buttonGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_collapseContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_collapseIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEndWithActions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetLeft = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetRight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStartWithNavigation = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logo = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_logo; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logoDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_logoDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_maxButtonHeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_maxButtonHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationContentDescription = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_navigationContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationIcon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_navigationIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_popupTheme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_subtitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_subtitleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginBottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginTop = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargins = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleMargins; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextAppearance = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextColor = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.Toolbar_titleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_focusable = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View_android_focusable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_theme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View_android_theme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingEnd = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View_paddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingStart = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View_paddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_theme = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.View_theme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_android_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTint = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewStubCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_id = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; + } + + public partial class Animation + { + + // aapt resource value: 0x7f050000 + public const int abc_fade_in = 2131034112; + + // aapt resource value: 0x7f050001 + public const int abc_fade_out = 2131034113; + + // aapt resource value: 0x7f050002 + public const int abc_grow_fade_in_from_bottom = 2131034114; + + // aapt resource value: 0x7f050003 + public const int abc_popup_enter = 2131034115; + + // aapt resource value: 0x7f050004 + public const int abc_popup_exit = 2131034116; + + // aapt resource value: 0x7f050005 + public const int abc_shrink_fade_out_from_bottom = 2131034117; + + // aapt resource value: 0x7f050006 + public const int abc_slide_in_bottom = 2131034118; + + // aapt resource value: 0x7f050007 + public const int abc_slide_in_top = 2131034119; + + // aapt resource value: 0x7f050008 + public const int abc_slide_out_bottom = 2131034120; + + // aapt resource value: 0x7f050009 + public const int abc_slide_out_top = 2131034121; + + // aapt resource value: 0x7f05000a + public const int design_bottom_sheet_slide_in = 2131034122; + + // aapt resource value: 0x7f05000b + public const int design_bottom_sheet_slide_out = 2131034123; + + // aapt resource value: 0x7f05000c + public const int design_snackbar_in = 2131034124; + + // aapt resource value: 0x7f05000d + public const int design_snackbar_out = 2131034125; + + // aapt resource value: 0x7f05000e + public const int EnterFromLeft = 2131034126; + + // aapt resource value: 0x7f05000f + public const int EnterFromRight = 2131034127; + + // aapt resource value: 0x7f050010 + public const int ExitToLeft = 2131034128; + + // aapt resource value: 0x7f050011 + public const int ExitToRight = 2131034129; + + // aapt resource value: 0x7f050012 + public const int tooltip_enter = 2131034130; + + // aapt resource value: 0x7f050013 + public const int tooltip_exit = 2131034131; + + static Animation() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Animation() + { + } + } + + public partial class Animator + { + + // aapt resource value: 0x7f060000 + public const int design_appbar_state_list_animator = 2131099648; + + static Animator() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Animator() + { + } + } + + public partial class Attribute + { + + // aapt resource value: 0x7f01006b + public const int actionBarDivider = 2130772075; + + // aapt resource value: 0x7f01006c + public const int actionBarItemBackground = 2130772076; + + // aapt resource value: 0x7f010065 + public const int actionBarPopupTheme = 2130772069; + + // aapt resource value: 0x7f01006a + public const int actionBarSize = 2130772074; + + // aapt resource value: 0x7f010067 + public const int actionBarSplitStyle = 2130772071; + + // aapt resource value: 0x7f010066 + public const int actionBarStyle = 2130772070; + + // aapt resource value: 0x7f010061 + public const int actionBarTabBarStyle = 2130772065; + + // aapt resource value: 0x7f010060 + public const int actionBarTabStyle = 2130772064; + + // aapt resource value: 0x7f010062 + public const int actionBarTabTextStyle = 2130772066; + + // aapt resource value: 0x7f010068 + public const int actionBarTheme = 2130772072; + + // aapt resource value: 0x7f010069 + public const int actionBarWidgetTheme = 2130772073; + + // aapt resource value: 0x7f010086 + public const int actionButtonStyle = 2130772102; + + // aapt resource value: 0x7f010082 + public const int actionDropDownStyle = 2130772098; + + // aapt resource value: 0x7f0100dd + public const int actionLayout = 2130772189; + + // aapt resource value: 0x7f01006d + public const int actionMenuTextAppearance = 2130772077; + + // aapt resource value: 0x7f01006e + public const int actionMenuTextColor = 2130772078; + + // aapt resource value: 0x7f010071 + public const int actionModeBackground = 2130772081; + + // aapt resource value: 0x7f010070 + public const int actionModeCloseButtonStyle = 2130772080; + + // aapt resource value: 0x7f010073 + public const int actionModeCloseDrawable = 2130772083; + + // aapt resource value: 0x7f010075 + public const int actionModeCopyDrawable = 2130772085; + + // aapt resource value: 0x7f010074 + public const int actionModeCutDrawable = 2130772084; + + // aapt resource value: 0x7f010079 + public const int actionModeFindDrawable = 2130772089; + + // aapt resource value: 0x7f010076 + public const int actionModePasteDrawable = 2130772086; + + // aapt resource value: 0x7f01007b + public const int actionModePopupWindowStyle = 2130772091; + + // aapt resource value: 0x7f010077 + public const int actionModeSelectAllDrawable = 2130772087; + + // aapt resource value: 0x7f010078 + public const int actionModeShareDrawable = 2130772088; + + // aapt resource value: 0x7f010072 + public const int actionModeSplitBackground = 2130772082; + + // aapt resource value: 0x7f01006f + public const int actionModeStyle = 2130772079; + + // aapt resource value: 0x7f01007a + public const int actionModeWebSearchDrawable = 2130772090; + + // aapt resource value: 0x7f010063 + public const int actionOverflowButtonStyle = 2130772067; + + // aapt resource value: 0x7f010064 + public const int actionOverflowMenuStyle = 2130772068; + + // aapt resource value: 0x7f0100df + public const int actionProviderClass = 2130772191; + + // aapt resource value: 0x7f0100de + public const int actionViewClass = 2130772190; + + // aapt resource value: 0x7f01008e + public const int activityChooserViewStyle = 2130772110; + + // aapt resource value: 0x7f0100b3 + public const int alertDialogButtonGroupStyle = 2130772147; + + // aapt resource value: 0x7f0100b4 + public const int alertDialogCenterButtons = 2130772148; + + // aapt resource value: 0x7f0100b2 + public const int alertDialogStyle = 2130772146; + + // aapt resource value: 0x7f0100b5 + public const int alertDialogTheme = 2130772149; + + // aapt resource value: 0x7f0100cb + public const int allowStacking = 2130772171; + + // aapt resource value: 0x7f0100cc + public const int alpha = 2130772172; + + // aapt resource value: 0x7f0100da + public const int alphabeticModifiers = 2130772186; + + // aapt resource value: 0x7f0100d3 + public const int arrowHeadLength = 2130772179; + + // aapt resource value: 0x7f0100d4 + public const int arrowShaftLength = 2130772180; + + // aapt resource value: 0x7f0100ba + public const int autoCompleteTextViewStyle = 2130772154; + + // aapt resource value: 0x7f010054 + public const int autoSizeMaxTextSize = 2130772052; + + // aapt resource value: 0x7f010053 + public const int autoSizeMinTextSize = 2130772051; + + // aapt resource value: 0x7f010052 + public const int autoSizePresetSizes = 2130772050; + + // aapt resource value: 0x7f010051 + public const int autoSizeStepGranularity = 2130772049; + + // aapt resource value: 0x7f010050 + public const int autoSizeTextType = 2130772048; + + // aapt resource value: 0x7f01002e + public const int background = 2130772014; + + // aapt resource value: 0x7f010030 + public const int backgroundSplit = 2130772016; + + // aapt resource value: 0x7f01002f + public const int backgroundStacked = 2130772015; + + // aapt resource value: 0x7f010116 + public const int backgroundTint = 2130772246; + + // aapt resource value: 0x7f010117 + public const int backgroundTintMode = 2130772247; + + // aapt resource value: 0x7f0100d5 + public const int barLength = 2130772181; + + // aapt resource value: 0x7f010141 + public const int behavior_autoHide = 2130772289; + + // aapt resource value: 0x7f01011e + public const int behavior_hideable = 2130772254; + + // aapt resource value: 0x7f01014a + public const int behavior_overlapTop = 2130772298; + + // aapt resource value: 0x7f01011d + public const int behavior_peekHeight = 2130772253; + + // aapt resource value: 0x7f01011f + public const int behavior_skipCollapsed = 2130772255; + + // aapt resource value: 0x7f01013f + public const int borderWidth = 2130772287; + + // aapt resource value: 0x7f01008b + public const int borderlessButtonStyle = 2130772107; + + // aapt resource value: 0x7f010139 + public const int bottomSheetDialogTheme = 2130772281; + + // aapt resource value: 0x7f01013a + public const int bottomSheetStyle = 2130772282; + + // aapt resource value: 0x7f010088 + public const int buttonBarButtonStyle = 2130772104; + + // aapt resource value: 0x7f0100b8 + public const int buttonBarNegativeButtonStyle = 2130772152; + + // aapt resource value: 0x7f0100b9 + public const int buttonBarNeutralButtonStyle = 2130772153; + + // aapt resource value: 0x7f0100b7 + public const int buttonBarPositiveButtonStyle = 2130772151; + + // aapt resource value: 0x7f010087 + public const int buttonBarStyle = 2130772103; + + // aapt resource value: 0x7f01010b + public const int buttonGravity = 2130772235; + + // aapt resource value: 0x7f010043 + public const int buttonPanelSideLayout = 2130772035; + + // aapt resource value: 0x7f0100bb + public const int buttonStyle = 2130772155; + + // aapt resource value: 0x7f0100bc + public const int buttonStyleSmall = 2130772156; + + // aapt resource value: 0x7f0100cd + public const int buttonTint = 2130772173; + + // aapt resource value: 0x7f0100ce + public const int buttonTintMode = 2130772174; + + // aapt resource value: 0x7f010017 + public const int cardBackgroundColor = 2130771991; + + // aapt resource value: 0x7f010018 + public const int cardCornerRadius = 2130771992; + + // aapt resource value: 0x7f010019 + public const int cardElevation = 2130771993; + + // aapt resource value: 0x7f01001a + public const int cardMaxElevation = 2130771994; + + // aapt resource value: 0x7f01001c + public const int cardPreventCornerOverlap = 2130771996; + + // aapt resource value: 0x7f01001b + public const int cardUseCompatPadding = 2130771995; + + // aapt resource value: 0x7f0100bd + public const int checkboxStyle = 2130772157; + + // aapt resource value: 0x7f0100be + public const int checkedTextViewStyle = 2130772158; + + // aapt resource value: 0x7f0100ee + public const int closeIcon = 2130772206; + + // aapt resource value: 0x7f010040 + public const int closeItemLayout = 2130772032; + + // aapt resource value: 0x7f01010d + public const int collapseContentDescription = 2130772237; + + // aapt resource value: 0x7f01010c + public const int collapseIcon = 2130772236; + + // aapt resource value: 0x7f01012c + public const int collapsedTitleGravity = 2130772268; + + // aapt resource value: 0x7f010126 + public const int collapsedTitleTextAppearance = 2130772262; + + // aapt resource value: 0x7f0100cf + public const int color = 2130772175; + + // aapt resource value: 0x7f0100aa + public const int colorAccent = 2130772138; + + // aapt resource value: 0x7f0100b1 + public const int colorBackgroundFloating = 2130772145; + + // aapt resource value: 0x7f0100ae + public const int colorButtonNormal = 2130772142; + + // aapt resource value: 0x7f0100ac + public const int colorControlActivated = 2130772140; + + // aapt resource value: 0x7f0100ad + public const int colorControlHighlight = 2130772141; + + // aapt resource value: 0x7f0100ab + public const int colorControlNormal = 2130772139; + + // aapt resource value: 0x7f0100ca + public const int colorError = 2130772170; + + // aapt resource value: 0x7f0100a8 + public const int colorPrimary = 2130772136; + + // aapt resource value: 0x7f0100a9 + public const int colorPrimaryDark = 2130772137; + + // aapt resource value: 0x7f0100af + public const int colorSwitchThumbNormal = 2130772143; + + // aapt resource value: 0x7f0100f3 + public const int commitIcon = 2130772211; + + // aapt resource value: 0x7f0100e0 + public const int contentDescription = 2130772192; + + // aapt resource value: 0x7f010039 + public const int contentInsetEnd = 2130772025; + + // aapt resource value: 0x7f01003d + public const int contentInsetEndWithActions = 2130772029; + + // aapt resource value: 0x7f01003a + public const int contentInsetLeft = 2130772026; + + // aapt resource value: 0x7f01003b + public const int contentInsetRight = 2130772027; + + // aapt resource value: 0x7f010038 + public const int contentInsetStart = 2130772024; + + // aapt resource value: 0x7f01003c + public const int contentInsetStartWithNavigation = 2130772028; + + // aapt resource value: 0x7f01001d + public const int contentPadding = 2130771997; + + // aapt resource value: 0x7f010021 + public const int contentPaddingBottom = 2130772001; + + // aapt resource value: 0x7f01001e + public const int contentPaddingLeft = 2130771998; + + // aapt resource value: 0x7f01001f + public const int contentPaddingRight = 2130771999; + + // aapt resource value: 0x7f010020 + public const int contentPaddingTop = 2130772000; + + // aapt resource value: 0x7f010127 + public const int contentScrim = 2130772263; + + // aapt resource value: 0x7f0100b0 + public const int controlBackground = 2130772144; + + // aapt resource value: 0x7f010160 + public const int counterEnabled = 2130772320; + + // aapt resource value: 0x7f010161 + public const int counterMaxLength = 2130772321; + + // aapt resource value: 0x7f010163 + public const int counterOverflowTextAppearance = 2130772323; + + // aapt resource value: 0x7f010162 + public const int counterTextAppearance = 2130772322; + + // aapt resource value: 0x7f010031 + public const int customNavigationLayout = 2130772017; + + // aapt resource value: 0x7f0100ed + public const int defaultQueryHint = 2130772205; + + // aapt resource value: 0x7f010080 + public const int dialogPreferredPadding = 2130772096; + + // aapt resource value: 0x7f01007f + public const int dialogTheme = 2130772095; + + // aapt resource value: 0x7f010027 + public const int displayOptions = 2130772007; + + // aapt resource value: 0x7f01002d + public const int divider = 2130772013; + + // aapt resource value: 0x7f01008d + public const int dividerHorizontal = 2130772109; + + // aapt resource value: 0x7f0100d9 + public const int dividerPadding = 2130772185; + + // aapt resource value: 0x7f01008c + public const int dividerVertical = 2130772108; + + // aapt resource value: 0x7f0100d1 + public const int drawableSize = 2130772177; + + // aapt resource value: 0x7f010022 + public const int drawerArrowStyle = 2130772002; + + // aapt resource value: 0x7f01009f + public const int dropDownListViewStyle = 2130772127; + + // aapt resource value: 0x7f010083 + public const int dropdownListPreferredItemHeight = 2130772099; + + // aapt resource value: 0x7f010094 + public const int editTextBackground = 2130772116; + + // aapt resource value: 0x7f010093 + public const int editTextColor = 2130772115; + + // aapt resource value: 0x7f0100bf + public const int editTextStyle = 2130772159; + + // aapt resource value: 0x7f01003e + public const int elevation = 2130772030; + + // aapt resource value: 0x7f01015e + public const int errorEnabled = 2130772318; + + // aapt resource value: 0x7f01015f + public const int errorTextAppearance = 2130772319; + + // aapt resource value: 0x7f010042 + public const int expandActivityOverflowButtonDrawable = 2130772034; + + // aapt resource value: 0x7f010118 + public const int expanded = 2130772248; + + // aapt resource value: 0x7f01012d + public const int expandedTitleGravity = 2130772269; + + // aapt resource value: 0x7f010120 + public const int expandedTitleMargin = 2130772256; + + // aapt resource value: 0x7f010124 + public const int expandedTitleMarginBottom = 2130772260; + + // aapt resource value: 0x7f010123 + public const int expandedTitleMarginEnd = 2130772259; + + // aapt resource value: 0x7f010121 + public const int expandedTitleMarginStart = 2130772257; + + // aapt resource value: 0x7f010122 + public const int expandedTitleMarginTop = 2130772258; + + // aapt resource value: 0x7f010125 + public const int expandedTitleTextAppearance = 2130772261; + + // aapt resource value: 0x7f010015 + public const int externalRouteEnabledDrawable = 2130771989; + + // aapt resource value: 0x7f01013d + public const int fabSize = 2130772285; + + // aapt resource value: 0x7f010004 + public const int fastScrollEnabled = 2130771972; + + // aapt resource value: 0x7f010007 + public const int fastScrollHorizontalThumbDrawable = 2130771975; + + // aapt resource value: 0x7f010008 + public const int fastScrollHorizontalTrackDrawable = 2130771976; + + // aapt resource value: 0x7f010005 + public const int fastScrollVerticalThumbDrawable = 2130771973; + + // aapt resource value: 0x7f010006 + public const int fastScrollVerticalTrackDrawable = 2130771974; + + // aapt resource value: 0x7f010171 + public const int font = 2130772337; + + // aapt resource value: 0x7f010055 + public const int fontFamily = 2130772053; + + // aapt resource value: 0x7f01016a + public const int fontProviderAuthority = 2130772330; + + // aapt resource value: 0x7f01016d + public const int fontProviderCerts = 2130772333; + + // aapt resource value: 0x7f01016e + public const int fontProviderFetchStrategy = 2130772334; + + // aapt resource value: 0x7f01016f + public const int fontProviderFetchTimeout = 2130772335; + + // aapt resource value: 0x7f01016b + public const int fontProviderPackage = 2130772331; + + // aapt resource value: 0x7f01016c + public const int fontProviderQuery = 2130772332; + + // aapt resource value: 0x7f010170 + public const int fontStyle = 2130772336; + + // aapt resource value: 0x7f010172 + public const int fontWeight = 2130772338; + + // aapt resource value: 0x7f010142 + public const int foregroundInsidePadding = 2130772290; + + // aapt resource value: 0x7f0100d2 + public const int gapBetweenBars = 2130772178; + + // aapt resource value: 0x7f0100ef + public const int goIcon = 2130772207; + + // aapt resource value: 0x7f010148 + public const int headerLayout = 2130772296; + + // aapt resource value: 0x7f010023 + public const int height = 2130772003; + + // aapt resource value: 0x7f010037 + public const int hideOnContentScroll = 2130772023; + + // aapt resource value: 0x7f010164 + public const int hintAnimationEnabled = 2130772324; + + // aapt resource value: 0x7f01015d + public const int hintEnabled = 2130772317; + + // aapt resource value: 0x7f01015c + public const int hintTextAppearance = 2130772316; + + // aapt resource value: 0x7f010085 + public const int homeAsUpIndicator = 2130772101; + + // aapt resource value: 0x7f010032 + public const int homeLayout = 2130772018; + + // aapt resource value: 0x7f01002b + public const int icon = 2130772011; + + // aapt resource value: 0x7f0100e2 + public const int iconTint = 2130772194; + + // aapt resource value: 0x7f0100e3 + public const int iconTintMode = 2130772195; + + // aapt resource value: 0x7f0100eb + public const int iconifiedByDefault = 2130772203; + + // aapt resource value: 0x7f010095 + public const int imageButtonStyle = 2130772117; + + // aapt resource value: 0x7f010034 + public const int indeterminateProgressStyle = 2130772020; + + // aapt resource value: 0x7f010041 + public const int initialActivityCount = 2130772033; + + // aapt resource value: 0x7f010149 + public const int insetForeground = 2130772297; + + // aapt resource value: 0x7f010024 + public const int isLightTheme = 2130772004; + + // aapt resource value: 0x7f010146 + public const int itemBackground = 2130772294; + + // aapt resource value: 0x7f010144 + public const int itemIconTint = 2130772292; + + // aapt resource value: 0x7f010036 + public const int itemPadding = 2130772022; + + // aapt resource value: 0x7f010147 + public const int itemTextAppearance = 2130772295; + + // aapt resource value: 0x7f010145 + public const int itemTextColor = 2130772293; + + // aapt resource value: 0x7f010131 + public const int keylines = 2130772273; + + // aapt resource value: 0x7f0100ea + public const int layout = 2130772202; + + // aapt resource value: 0x7f010000 + public const int layoutManager = 2130771968; + + // aapt resource value: 0x7f010134 + public const int layout_anchor = 2130772276; + + // aapt resource value: 0x7f010136 + public const int layout_anchorGravity = 2130772278; + + // aapt resource value: 0x7f010133 + public const int layout_behavior = 2130772275; + + // aapt resource value: 0x7f01012f + public const int layout_collapseMode = 2130772271; + + // aapt resource value: 0x7f010130 + public const int layout_collapseParallaxMultiplier = 2130772272; + + // aapt resource value: 0x7f010138 + public const int layout_dodgeInsetEdges = 2130772280; + + // aapt resource value: 0x7f010137 + public const int layout_insetEdge = 2130772279; + + // aapt resource value: 0x7f010135 + public const int layout_keyline = 2130772277; + + // aapt resource value: 0x7f01011b + public const int layout_scrollFlags = 2130772251; + + // aapt resource value: 0x7f01011c + public const int layout_scrollInterpolator = 2130772252; + + // aapt resource value: 0x7f0100a7 + public const int listChoiceBackgroundIndicator = 2130772135; + + // aapt resource value: 0x7f010081 + public const int listDividerAlertDialog = 2130772097; + + // aapt resource value: 0x7f010047 + public const int listItemLayout = 2130772039; + + // aapt resource value: 0x7f010044 + public const int listLayout = 2130772036; + + // aapt resource value: 0x7f0100c7 + public const int listMenuViewStyle = 2130772167; + + // aapt resource value: 0x7f0100a0 + public const int listPopupWindowStyle = 2130772128; + + // aapt resource value: 0x7f01009a + public const int listPreferredItemHeight = 2130772122; + + // aapt resource value: 0x7f01009c + public const int listPreferredItemHeightLarge = 2130772124; + + // aapt resource value: 0x7f01009b + public const int listPreferredItemHeightSmall = 2130772123; + + // aapt resource value: 0x7f01009d + public const int listPreferredItemPaddingLeft = 2130772125; + + // aapt resource value: 0x7f01009e + public const int listPreferredItemPaddingRight = 2130772126; + + // aapt resource value: 0x7f01002c + public const int logo = 2130772012; + + // aapt resource value: 0x7f010110 + public const int logoDescription = 2130772240; + + // aapt resource value: 0x7f01014b + public const int maxActionInlineWidth = 2130772299; + + // aapt resource value: 0x7f01010a + public const int maxButtonHeight = 2130772234; + + // aapt resource value: 0x7f0100d7 + public const int measureWithLargestChild = 2130772183; + + // aapt resource value: 0x7f010009 + public const int mediaRouteAudioTrackDrawable = 2130771977; + + // aapt resource value: 0x7f01000a + public const int mediaRouteButtonStyle = 2130771978; + + // aapt resource value: 0x7f010016 + public const int mediaRouteButtonTint = 2130771990; + + // aapt resource value: 0x7f01000b + public const int mediaRouteCloseDrawable = 2130771979; + + // aapt resource value: 0x7f01000c + public const int mediaRouteControlPanelThemeOverlay = 2130771980; + + // aapt resource value: 0x7f01000d + public const int mediaRouteDefaultIconDrawable = 2130771981; + + // aapt resource value: 0x7f01000e + public const int mediaRoutePauseDrawable = 2130771982; + + // aapt resource value: 0x7f01000f + public const int mediaRoutePlayDrawable = 2130771983; + + // aapt resource value: 0x7f010010 + public const int mediaRouteSpeakerGroupIconDrawable = 2130771984; + + // aapt resource value: 0x7f010011 + public const int mediaRouteSpeakerIconDrawable = 2130771985; + + // aapt resource value: 0x7f010012 + public const int mediaRouteStopDrawable = 2130771986; + + // aapt resource value: 0x7f010013 + public const int mediaRouteTheme = 2130771987; + + // aapt resource value: 0x7f010014 + public const int mediaRouteTvIconDrawable = 2130771988; + + // aapt resource value: 0x7f010143 + public const int menu = 2130772291; + + // aapt resource value: 0x7f010045 + public const int multiChoiceItemLayout = 2130772037; + + // aapt resource value: 0x7f01010f + public const int navigationContentDescription = 2130772239; + + // aapt resource value: 0x7f01010e + public const int navigationIcon = 2130772238; + + // aapt resource value: 0x7f010026 + public const int navigationMode = 2130772006; + + // aapt resource value: 0x7f0100db + public const int numericModifiers = 2130772187; + + // aapt resource value: 0x7f0100e6 + public const int overlapAnchor = 2130772198; + + // aapt resource value: 0x7f0100e8 + public const int paddingBottomNoButtons = 2130772200; + + // aapt resource value: 0x7f010114 + public const int paddingEnd = 2130772244; + + // aapt resource value: 0x7f010113 + public const int paddingStart = 2130772243; + + // aapt resource value: 0x7f0100e9 + public const int paddingTopNoTitle = 2130772201; + + // aapt resource value: 0x7f0100a4 + public const int panelBackground = 2130772132; + + // aapt resource value: 0x7f0100a6 + public const int panelMenuListTheme = 2130772134; + + // aapt resource value: 0x7f0100a5 + public const int panelMenuListWidth = 2130772133; + + // aapt resource value: 0x7f010167 + public const int passwordToggleContentDescription = 2130772327; + + // aapt resource value: 0x7f010166 + public const int passwordToggleDrawable = 2130772326; + + // aapt resource value: 0x7f010165 + public const int passwordToggleEnabled = 2130772325; + + // aapt resource value: 0x7f010168 + public const int passwordToggleTint = 2130772328; + + // aapt resource value: 0x7f010169 + public const int passwordToggleTintMode = 2130772329; + + // aapt resource value: 0x7f010091 + public const int popupMenuStyle = 2130772113; + + // aapt resource value: 0x7f01003f + public const int popupTheme = 2130772031; + + // aapt resource value: 0x7f010092 + public const int popupWindowStyle = 2130772114; + + // aapt resource value: 0x7f0100e4 + public const int preserveIconSpacing = 2130772196; + + // aapt resource value: 0x7f01013e + public const int pressedTranslationZ = 2130772286; + + // aapt resource value: 0x7f010035 + public const int progressBarPadding = 2130772021; + + // aapt resource value: 0x7f010033 + public const int progressBarStyle = 2130772019; + + // aapt resource value: 0x7f0100f5 + public const int queryBackground = 2130772213; + + // aapt resource value: 0x7f0100ec + public const int queryHint = 2130772204; + + // aapt resource value: 0x7f0100c0 + public const int radioButtonStyle = 2130772160; + + // aapt resource value: 0x7f0100c1 + public const int ratingBarStyle = 2130772161; + + // aapt resource value: 0x7f0100c2 + public const int ratingBarStyleIndicator = 2130772162; + + // aapt resource value: 0x7f0100c3 + public const int ratingBarStyleSmall = 2130772163; + + // aapt resource value: 0x7f010002 + public const int reverseLayout = 2130771970; + + // aapt resource value: 0x7f01013c + public const int rippleColor = 2130772284; + + // aapt resource value: 0x7f01012b + public const int scrimAnimationDuration = 2130772267; + + // aapt resource value: 0x7f01012a + public const int scrimVisibleHeightTrigger = 2130772266; + + // aapt resource value: 0x7f0100f1 + public const int searchHintIcon = 2130772209; + + // aapt resource value: 0x7f0100f0 + public const int searchIcon = 2130772208; + + // aapt resource value: 0x7f010099 + public const int searchViewStyle = 2130772121; + + // aapt resource value: 0x7f0100c4 + public const int seekBarStyle = 2130772164; + + // aapt resource value: 0x7f010089 + public const int selectableItemBackground = 2130772105; + + // aapt resource value: 0x7f01008a + public const int selectableItemBackgroundBorderless = 2130772106; + + // aapt resource value: 0x7f0100dc + public const int showAsAction = 2130772188; + + // aapt resource value: 0x7f0100d8 + public const int showDividers = 2130772184; + + // aapt resource value: 0x7f010101 + public const int showText = 2130772225; + + // aapt resource value: 0x7f010048 + public const int showTitle = 2130772040; + + // aapt resource value: 0x7f010046 + public const int singleChoiceItemLayout = 2130772038; + + // aapt resource value: 0x7f010001 + public const int spanCount = 2130771969; + + // aapt resource value: 0x7f0100d0 + public const int spinBars = 2130772176; + + // aapt resource value: 0x7f010084 + public const int spinnerDropDownItemStyle = 2130772100; + + // aapt resource value: 0x7f0100c5 + public const int spinnerStyle = 2130772165; + + // aapt resource value: 0x7f010100 + public const int splitTrack = 2130772224; + + // aapt resource value: 0x7f010049 + public const int srcCompat = 2130772041; + + // aapt resource value: 0x7f010003 + public const int stackFromEnd = 2130771971; + + // aapt resource value: 0x7f0100e7 + public const int state_above_anchor = 2130772199; + + // aapt resource value: 0x7f010119 + public const int state_collapsed = 2130772249; + + // aapt resource value: 0x7f01011a + public const int state_collapsible = 2130772250; + + // aapt resource value: 0x7f010132 + public const int statusBarBackground = 2130772274; + + // aapt resource value: 0x7f010128 + public const int statusBarScrim = 2130772264; + + // aapt resource value: 0x7f0100e5 + public const int subMenuArrow = 2130772197; + + // aapt resource value: 0x7f0100f6 + public const int submitBackground = 2130772214; + + // aapt resource value: 0x7f010028 + public const int subtitle = 2130772008; + + // aapt resource value: 0x7f010103 + public const int subtitleTextAppearance = 2130772227; + + // aapt resource value: 0x7f010112 + public const int subtitleTextColor = 2130772242; + + // aapt resource value: 0x7f01002a + public const int subtitleTextStyle = 2130772010; + + // aapt resource value: 0x7f0100f4 + public const int suggestionRowLayout = 2130772212; + + // aapt resource value: 0x7f0100fe + public const int switchMinWidth = 2130772222; + + // aapt resource value: 0x7f0100ff + public const int switchPadding = 2130772223; + + // aapt resource value: 0x7f0100c6 + public const int switchStyle = 2130772166; + + // aapt resource value: 0x7f0100fd + public const int switchTextAppearance = 2130772221; + + // aapt resource value: 0x7f01014f + public const int tabBackground = 2130772303; + + // aapt resource value: 0x7f01014e + public const int tabContentStart = 2130772302; + + // aapt resource value: 0x7f010151 + public const int tabGravity = 2130772305; + + // aapt resource value: 0x7f01014c + public const int tabIndicatorColor = 2130772300; + + // aapt resource value: 0x7f01014d + public const int tabIndicatorHeight = 2130772301; + + // aapt resource value: 0x7f010153 + public const int tabMaxWidth = 2130772307; + + // aapt resource value: 0x7f010152 + public const int tabMinWidth = 2130772306; + + // aapt resource value: 0x7f010150 + public const int tabMode = 2130772304; + + // aapt resource value: 0x7f01015b + public const int tabPadding = 2130772315; + + // aapt resource value: 0x7f01015a + public const int tabPaddingBottom = 2130772314; + + // aapt resource value: 0x7f010159 + public const int tabPaddingEnd = 2130772313; + + // aapt resource value: 0x7f010157 + public const int tabPaddingStart = 2130772311; + + // aapt resource value: 0x7f010158 + public const int tabPaddingTop = 2130772312; + + // aapt resource value: 0x7f010156 + public const int tabSelectedTextColor = 2130772310; + + // aapt resource value: 0x7f010154 + public const int tabTextAppearance = 2130772308; + + // aapt resource value: 0x7f010155 + public const int tabTextColor = 2130772309; + + // aapt resource value: 0x7f01004f + public const int textAllCaps = 2130772047; + + // aapt resource value: 0x7f01007c + public const int textAppearanceLargePopupMenu = 2130772092; + + // aapt resource value: 0x7f0100a1 + public const int textAppearanceListItem = 2130772129; + + // aapt resource value: 0x7f0100a2 + public const int textAppearanceListItemSecondary = 2130772130; + + // aapt resource value: 0x7f0100a3 + public const int textAppearanceListItemSmall = 2130772131; + + // aapt resource value: 0x7f01007e + public const int textAppearancePopupMenuHeader = 2130772094; + + // aapt resource value: 0x7f010097 + public const int textAppearanceSearchResultSubtitle = 2130772119; + + // aapt resource value: 0x7f010096 + public const int textAppearanceSearchResultTitle = 2130772118; + + // aapt resource value: 0x7f01007d + public const int textAppearanceSmallPopupMenu = 2130772093; + + // aapt resource value: 0x7f0100b6 + public const int textColorAlertDialogListItem = 2130772150; + + // aapt resource value: 0x7f01013b + public const int textColorError = 2130772283; + + // aapt resource value: 0x7f010098 + public const int textColorSearchUrl = 2130772120; + + // aapt resource value: 0x7f010115 + public const int theme = 2130772245; + + // aapt resource value: 0x7f0100d6 + public const int thickness = 2130772182; + + // aapt resource value: 0x7f0100fc + public const int thumbTextPadding = 2130772220; + + // aapt resource value: 0x7f0100f7 + public const int thumbTint = 2130772215; + + // aapt resource value: 0x7f0100f8 + public const int thumbTintMode = 2130772216; + + // aapt resource value: 0x7f01004c + public const int tickMark = 2130772044; + + // aapt resource value: 0x7f01004d + public const int tickMarkTint = 2130772045; + + // aapt resource value: 0x7f01004e + public const int tickMarkTintMode = 2130772046; + + // aapt resource value: 0x7f01004a + public const int tint = 2130772042; + + // aapt resource value: 0x7f01004b + public const int tintMode = 2130772043; + + // aapt resource value: 0x7f010025 + public const int title = 2130772005; + + // aapt resource value: 0x7f01012e + public const int titleEnabled = 2130772270; + + // aapt resource value: 0x7f010104 + public const int titleMargin = 2130772228; + + // aapt resource value: 0x7f010108 + public const int titleMarginBottom = 2130772232; + + // aapt resource value: 0x7f010106 + public const int titleMarginEnd = 2130772230; + + // aapt resource value: 0x7f010105 + public const int titleMarginStart = 2130772229; + + // aapt resource value: 0x7f010107 + public const int titleMarginTop = 2130772231; + + // aapt resource value: 0x7f010109 + public const int titleMargins = 2130772233; + + // aapt resource value: 0x7f010102 + public const int titleTextAppearance = 2130772226; + + // aapt resource value: 0x7f010111 + public const int titleTextColor = 2130772241; + + // aapt resource value: 0x7f010029 + public const int titleTextStyle = 2130772009; + + // aapt resource value: 0x7f010129 + public const int toolbarId = 2130772265; + + // aapt resource value: 0x7f010090 + public const int toolbarNavigationButtonStyle = 2130772112; + + // aapt resource value: 0x7f01008f + public const int toolbarStyle = 2130772111; + + // aapt resource value: 0x7f0100c9 + public const int tooltipForegroundColor = 2130772169; + + // aapt resource value: 0x7f0100c8 + public const int tooltipFrameBackground = 2130772168; + + // aapt resource value: 0x7f0100e1 + public const int tooltipText = 2130772193; + + // aapt resource value: 0x7f0100f9 + public const int track = 2130772217; + + // aapt resource value: 0x7f0100fa + public const int trackTint = 2130772218; + + // aapt resource value: 0x7f0100fb + public const int trackTintMode = 2130772219; + + // aapt resource value: 0x7f010140 + public const int useCompatPadding = 2130772288; + + // aapt resource value: 0x7f0100f2 + public const int voiceIcon = 2130772210; + + // aapt resource value: 0x7f010056 + public const int windowActionBar = 2130772054; + + // aapt resource value: 0x7f010058 + public const int windowActionBarOverlay = 2130772056; + + // aapt resource value: 0x7f010059 + public const int windowActionModeOverlay = 2130772057; + + // aapt resource value: 0x7f01005d + public const int windowFixedHeightMajor = 2130772061; + + // aapt resource value: 0x7f01005b + public const int windowFixedHeightMinor = 2130772059; + + // aapt resource value: 0x7f01005a + public const int windowFixedWidthMajor = 2130772058; + + // aapt resource value: 0x7f01005c + public const int windowFixedWidthMinor = 2130772060; + + // aapt resource value: 0x7f01005e + public const int windowMinWidthMajor = 2130772062; + + // aapt resource value: 0x7f01005f + public const int windowMinWidthMinor = 2130772063; + + // aapt resource value: 0x7f010057 + public const int windowNoTitle = 2130772055; + + static Attribute() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Attribute() + { + } + } + + public partial class Boolean + { + + // aapt resource value: 0x7f0e0000 + public const int abc_action_bar_embed_tabs = 2131623936; + + // aapt resource value: 0x7f0e0001 + public const int abc_allow_stacked_button_bar = 2131623937; + + // aapt resource value: 0x7f0e0002 + public const int abc_config_actionMenuItemAllCaps = 2131623938; + + // aapt resource value: 0x7f0e0003 + public const int abc_config_closeDialogWhenTouchOutside = 2131623939; + + // aapt resource value: 0x7f0e0004 + public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2131623940; + + static Boolean() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Boolean() + { + } + } + + public partial class Color + { + + // aapt resource value: 0x7f0d004f + public const int abc_background_cache_hint_selector_material_dark = 2131558479; + + // aapt resource value: 0x7f0d0050 + public const int abc_background_cache_hint_selector_material_light = 2131558480; + + // aapt resource value: 0x7f0d0051 + public const int abc_btn_colored_borderless_text_material = 2131558481; + + // aapt resource value: 0x7f0d0052 + public const int abc_btn_colored_text_material = 2131558482; + + // aapt resource value: 0x7f0d0053 + public const int abc_color_highlight_material = 2131558483; + + // aapt resource value: 0x7f0d0054 + public const int abc_hint_foreground_material_dark = 2131558484; + + // aapt resource value: 0x7f0d0055 + public const int abc_hint_foreground_material_light = 2131558485; + + // aapt resource value: 0x7f0d0004 + public const int abc_input_method_navigation_guard = 2131558404; + + // aapt resource value: 0x7f0d0056 + public const int abc_primary_text_disable_only_material_dark = 2131558486; + + // aapt resource value: 0x7f0d0057 + public const int abc_primary_text_disable_only_material_light = 2131558487; + + // aapt resource value: 0x7f0d0058 + public const int abc_primary_text_material_dark = 2131558488; + + // aapt resource value: 0x7f0d0059 + public const int abc_primary_text_material_light = 2131558489; + + // aapt resource value: 0x7f0d005a + public const int abc_search_url_text = 2131558490; + + // aapt resource value: 0x7f0d0005 + public const int abc_search_url_text_normal = 2131558405; + + // aapt resource value: 0x7f0d0006 + public const int abc_search_url_text_pressed = 2131558406; + + // aapt resource value: 0x7f0d0007 + public const int abc_search_url_text_selected = 2131558407; + + // aapt resource value: 0x7f0d005b + public const int abc_secondary_text_material_dark = 2131558491; + + // aapt resource value: 0x7f0d005c + public const int abc_secondary_text_material_light = 2131558492; + + // aapt resource value: 0x7f0d005d + public const int abc_tint_btn_checkable = 2131558493; + + // aapt resource value: 0x7f0d005e + public const int abc_tint_default = 2131558494; + + // aapt resource value: 0x7f0d005f + public const int abc_tint_edittext = 2131558495; + + // aapt resource value: 0x7f0d0060 + public const int abc_tint_seek_thumb = 2131558496; + + // aapt resource value: 0x7f0d0061 + public const int abc_tint_spinner = 2131558497; + + // aapt resource value: 0x7f0d0062 + public const int abc_tint_switch_track = 2131558498; + + // aapt resource value: 0x7f0d0008 + public const int accent_material_dark = 2131558408; + + // aapt resource value: 0x7f0d0009 + public const int accent_material_light = 2131558409; + + // aapt resource value: 0x7f0d000a + public const int background_floating_material_dark = 2131558410; + + // aapt resource value: 0x7f0d000b + public const int background_floating_material_light = 2131558411; + + // aapt resource value: 0x7f0d000c + public const int background_material_dark = 2131558412; + + // aapt resource value: 0x7f0d000d + public const int background_material_light = 2131558413; + + // aapt resource value: 0x7f0d000e + public const int bright_foreground_disabled_material_dark = 2131558414; + + // aapt resource value: 0x7f0d000f + public const int bright_foreground_disabled_material_light = 2131558415; + + // aapt resource value: 0x7f0d0010 + public const int bright_foreground_inverse_material_dark = 2131558416; + + // aapt resource value: 0x7f0d0011 + public const int bright_foreground_inverse_material_light = 2131558417; + + // aapt resource value: 0x7f0d0012 + public const int bright_foreground_material_dark = 2131558418; + + // aapt resource value: 0x7f0d0013 + public const int bright_foreground_material_light = 2131558419; + + // aapt resource value: 0x7f0d0014 + public const int button_material_dark = 2131558420; + + // aapt resource value: 0x7f0d0015 + public const int button_material_light = 2131558421; + + // aapt resource value: 0x7f0d0000 + public const int cardview_dark_background = 2131558400; + + // aapt resource value: 0x7f0d0001 + public const int cardview_light_background = 2131558401; + + // aapt resource value: 0x7f0d0002 + public const int cardview_shadow_end_color = 2131558402; + + // aapt resource value: 0x7f0d0003 + public const int cardview_shadow_start_color = 2131558403; + + // aapt resource value: 0x7f0d004d + public const int colorAccent = 2131558477; + + // aapt resource value: 0x7f0d004b + public const int colorPrimary = 2131558475; + + // aapt resource value: 0x7f0d004c + public const int colorPrimaryDark = 2131558476; + + // aapt resource value: 0x7f0d0040 + public const int design_bottom_navigation_shadow_color = 2131558464; + + // aapt resource value: 0x7f0d0063 + public const int design_error = 2131558499; + + // aapt resource value: 0x7f0d0041 + public const int design_fab_shadow_end_color = 2131558465; + + // aapt resource value: 0x7f0d0042 + public const int design_fab_shadow_mid_color = 2131558466; + + // aapt resource value: 0x7f0d0043 + public const int design_fab_shadow_start_color = 2131558467; + + // aapt resource value: 0x7f0d0044 + public const int design_fab_stroke_end_inner_color = 2131558468; + + // aapt resource value: 0x7f0d0045 + public const int design_fab_stroke_end_outer_color = 2131558469; + + // aapt resource value: 0x7f0d0046 + public const int design_fab_stroke_top_inner_color = 2131558470; + + // aapt resource value: 0x7f0d0047 + public const int design_fab_stroke_top_outer_color = 2131558471; + + // aapt resource value: 0x7f0d0048 + public const int design_snackbar_background_color = 2131558472; + + // aapt resource value: 0x7f0d0064 + public const int design_tint_password_toggle = 2131558500; + + // aapt resource value: 0x7f0d0016 + public const int dim_foreground_disabled_material_dark = 2131558422; + + // aapt resource value: 0x7f0d0017 + public const int dim_foreground_disabled_material_light = 2131558423; + + // aapt resource value: 0x7f0d0018 + public const int dim_foreground_material_dark = 2131558424; + + // aapt resource value: 0x7f0d0019 + public const int dim_foreground_material_light = 2131558425; + + // aapt resource value: 0x7f0d001a + public const int error_color_material = 2131558426; + + // aapt resource value: 0x7f0d001b + public const int foreground_material_dark = 2131558427; + + // aapt resource value: 0x7f0d001c + public const int foreground_material_light = 2131558428; + + // aapt resource value: 0x7f0d001d + public const int highlighted_text_material_dark = 2131558429; + + // aapt resource value: 0x7f0d001e + public const int highlighted_text_material_light = 2131558430; + + // aapt resource value: 0x7f0d004e + public const int ic_launcher_background = 2131558478; + + // aapt resource value: 0x7f0d001f + public const int material_blue_grey_800 = 2131558431; + + // aapt resource value: 0x7f0d0020 + public const int material_blue_grey_900 = 2131558432; + + // aapt resource value: 0x7f0d0021 + public const int material_blue_grey_950 = 2131558433; + + // aapt resource value: 0x7f0d0022 + public const int material_deep_teal_200 = 2131558434; + + // aapt resource value: 0x7f0d0023 + public const int material_deep_teal_500 = 2131558435; + + // aapt resource value: 0x7f0d0024 + public const int material_grey_100 = 2131558436; + + // aapt resource value: 0x7f0d0025 + public const int material_grey_300 = 2131558437; + + // aapt resource value: 0x7f0d0026 + public const int material_grey_50 = 2131558438; + + // aapt resource value: 0x7f0d0027 + public const int material_grey_600 = 2131558439; + + // aapt resource value: 0x7f0d0028 + public const int material_grey_800 = 2131558440; + + // aapt resource value: 0x7f0d0029 + public const int material_grey_850 = 2131558441; + + // aapt resource value: 0x7f0d002a + public const int material_grey_900 = 2131558442; + + // aapt resource value: 0x7f0d0049 + public const int notification_action_color_filter = 2131558473; + + // aapt resource value: 0x7f0d004a + public const int notification_icon_bg_color = 2131558474; + + // aapt resource value: 0x7f0d003f + public const int notification_material_background_media_default_color = 2131558463; + + // aapt resource value: 0x7f0d002b + public const int primary_dark_material_dark = 2131558443; + + // aapt resource value: 0x7f0d002c + public const int primary_dark_material_light = 2131558444; + + // aapt resource value: 0x7f0d002d + public const int primary_material_dark = 2131558445; + + // aapt resource value: 0x7f0d002e + public const int primary_material_light = 2131558446; + + // aapt resource value: 0x7f0d002f + public const int primary_text_default_material_dark = 2131558447; + + // aapt resource value: 0x7f0d0030 + public const int primary_text_default_material_light = 2131558448; + + // aapt resource value: 0x7f0d0031 + public const int primary_text_disabled_material_dark = 2131558449; + + // aapt resource value: 0x7f0d0032 + public const int primary_text_disabled_material_light = 2131558450; + + // aapt resource value: 0x7f0d0033 + public const int ripple_material_dark = 2131558451; + + // aapt resource value: 0x7f0d0034 + public const int ripple_material_light = 2131558452; + + // aapt resource value: 0x7f0d0035 + public const int secondary_text_default_material_dark = 2131558453; + + // aapt resource value: 0x7f0d0036 + public const int secondary_text_default_material_light = 2131558454; + + // aapt resource value: 0x7f0d0037 + public const int secondary_text_disabled_material_dark = 2131558455; + + // aapt resource value: 0x7f0d0038 + public const int secondary_text_disabled_material_light = 2131558456; + + // aapt resource value: 0x7f0d0039 + public const int switch_thumb_disabled_material_dark = 2131558457; + + // aapt resource value: 0x7f0d003a + public const int switch_thumb_disabled_material_light = 2131558458; + + // aapt resource value: 0x7f0d0065 + public const int switch_thumb_material_dark = 2131558501; + + // aapt resource value: 0x7f0d0066 + public const int switch_thumb_material_light = 2131558502; + + // aapt resource value: 0x7f0d003b + public const int switch_thumb_normal_material_dark = 2131558459; + + // aapt resource value: 0x7f0d003c + public const int switch_thumb_normal_material_light = 2131558460; + + // aapt resource value: 0x7f0d003d + public const int tooltip_background_dark = 2131558461; + + // aapt resource value: 0x7f0d003e + public const int tooltip_background_light = 2131558462; + + static Color() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Color() + { + } + } + + public partial class Dimension + { + + // aapt resource value: 0x7f08001b + public const int abc_action_bar_content_inset_material = 2131230747; + + // aapt resource value: 0x7f08001c + public const int abc_action_bar_content_inset_with_nav = 2131230748; + + // aapt resource value: 0x7f080010 + public const int abc_action_bar_default_height_material = 2131230736; + + // aapt resource value: 0x7f08001d + public const int abc_action_bar_default_padding_end_material = 2131230749; + + // aapt resource value: 0x7f08001e + public const int abc_action_bar_default_padding_start_material = 2131230750; + + // aapt resource value: 0x7f080020 + public const int abc_action_bar_elevation_material = 2131230752; + + // aapt resource value: 0x7f080021 + public const int abc_action_bar_icon_vertical_padding_material = 2131230753; + + // aapt resource value: 0x7f080022 + public const int abc_action_bar_overflow_padding_end_material = 2131230754; + + // aapt resource value: 0x7f080023 + public const int abc_action_bar_overflow_padding_start_material = 2131230755; + + // aapt resource value: 0x7f080011 + public const int abc_action_bar_progress_bar_size = 2131230737; + + // aapt resource value: 0x7f080024 + public const int abc_action_bar_stacked_max_height = 2131230756; + + // aapt resource value: 0x7f080025 + public const int abc_action_bar_stacked_tab_max_width = 2131230757; + + // aapt resource value: 0x7f080026 + public const int abc_action_bar_subtitle_bottom_margin_material = 2131230758; + + // aapt resource value: 0x7f080027 + public const int abc_action_bar_subtitle_top_margin_material = 2131230759; + + // aapt resource value: 0x7f080028 + public const int abc_action_button_min_height_material = 2131230760; + + // aapt resource value: 0x7f080029 + public const int abc_action_button_min_width_material = 2131230761; + + // aapt resource value: 0x7f08002a + public const int abc_action_button_min_width_overflow_material = 2131230762; + + // aapt resource value: 0x7f08000f + public const int abc_alert_dialog_button_bar_height = 2131230735; + + // aapt resource value: 0x7f08002b + public const int abc_button_inset_horizontal_material = 2131230763; + + // aapt resource value: 0x7f08002c + public const int abc_button_inset_vertical_material = 2131230764; + + // aapt resource value: 0x7f08002d + public const int abc_button_padding_horizontal_material = 2131230765; + + // aapt resource value: 0x7f08002e + public const int abc_button_padding_vertical_material = 2131230766; + + // aapt resource value: 0x7f08002f + public const int abc_cascading_menus_min_smallest_width = 2131230767; + + // aapt resource value: 0x7f080014 + public const int abc_config_prefDialogWidth = 2131230740; + + // aapt resource value: 0x7f080030 + public const int abc_control_corner_material = 2131230768; + + // aapt resource value: 0x7f080031 + public const int abc_control_inset_material = 2131230769; + + // aapt resource value: 0x7f080032 + public const int abc_control_padding_material = 2131230770; + + // aapt resource value: 0x7f080015 + public const int abc_dialog_fixed_height_major = 2131230741; + + // aapt resource value: 0x7f080016 + public const int abc_dialog_fixed_height_minor = 2131230742; + + // aapt resource value: 0x7f080017 + public const int abc_dialog_fixed_width_major = 2131230743; + + // aapt resource value: 0x7f080018 + public const int abc_dialog_fixed_width_minor = 2131230744; + + // aapt resource value: 0x7f080033 + public const int abc_dialog_list_padding_bottom_no_buttons = 2131230771; + + // aapt resource value: 0x7f080034 + public const int abc_dialog_list_padding_top_no_title = 2131230772; + + // aapt resource value: 0x7f080019 + public const int abc_dialog_min_width_major = 2131230745; + + // aapt resource value: 0x7f08001a + public const int abc_dialog_min_width_minor = 2131230746; + + // aapt resource value: 0x7f080035 + public const int abc_dialog_padding_material = 2131230773; + + // aapt resource value: 0x7f080036 + public const int abc_dialog_padding_top_material = 2131230774; + + // aapt resource value: 0x7f080037 + public const int abc_dialog_title_divider_material = 2131230775; + + // aapt resource value: 0x7f080038 + public const int abc_disabled_alpha_material_dark = 2131230776; + + // aapt resource value: 0x7f080039 + public const int abc_disabled_alpha_material_light = 2131230777; + + // aapt resource value: 0x7f08003a + public const int abc_dropdownitem_icon_width = 2131230778; + + // aapt resource value: 0x7f08003b + public const int abc_dropdownitem_text_padding_left = 2131230779; + + // aapt resource value: 0x7f08003c + public const int abc_dropdownitem_text_padding_right = 2131230780; + + // aapt resource value: 0x7f08003d + public const int abc_edit_text_inset_bottom_material = 2131230781; + + // aapt resource value: 0x7f08003e + public const int abc_edit_text_inset_horizontal_material = 2131230782; + + // aapt resource value: 0x7f08003f + public const int abc_edit_text_inset_top_material = 2131230783; + + // aapt resource value: 0x7f080040 + public const int abc_floating_window_z = 2131230784; + + // aapt resource value: 0x7f080041 + public const int abc_list_item_padding_horizontal_material = 2131230785; + + // aapt resource value: 0x7f080042 + public const int abc_panel_menu_list_width = 2131230786; + + // aapt resource value: 0x7f080043 + public const int abc_progress_bar_height_material = 2131230787; + + // aapt resource value: 0x7f080044 + public const int abc_search_view_preferred_height = 2131230788; + + // aapt resource value: 0x7f080045 + public const int abc_search_view_preferred_width = 2131230789; + + // aapt resource value: 0x7f080046 + public const int abc_seekbar_track_background_height_material = 2131230790; + + // aapt resource value: 0x7f080047 + public const int abc_seekbar_track_progress_height_material = 2131230791; + + // aapt resource value: 0x7f080048 + public const int abc_select_dialog_padding_start_material = 2131230792; + + // aapt resource value: 0x7f08001f + public const int abc_switch_padding = 2131230751; + + // aapt resource value: 0x7f080049 + public const int abc_text_size_body_1_material = 2131230793; + + // aapt resource value: 0x7f08004a + public const int abc_text_size_body_2_material = 2131230794; + + // aapt resource value: 0x7f08004b + public const int abc_text_size_button_material = 2131230795; + + // aapt resource value: 0x7f08004c + public const int abc_text_size_caption_material = 2131230796; + + // aapt resource value: 0x7f08004d + public const int abc_text_size_display_1_material = 2131230797; + + // aapt resource value: 0x7f08004e + public const int abc_text_size_display_2_material = 2131230798; + + // aapt resource value: 0x7f08004f + public const int abc_text_size_display_3_material = 2131230799; + + // aapt resource value: 0x7f080050 + public const int abc_text_size_display_4_material = 2131230800; + + // aapt resource value: 0x7f080051 + public const int abc_text_size_headline_material = 2131230801; + + // aapt resource value: 0x7f080052 + public const int abc_text_size_large_material = 2131230802; + + // aapt resource value: 0x7f080053 + public const int abc_text_size_medium_material = 2131230803; + + // aapt resource value: 0x7f080054 + public const int abc_text_size_menu_header_material = 2131230804; + + // aapt resource value: 0x7f080055 + public const int abc_text_size_menu_material = 2131230805; + + // aapt resource value: 0x7f080056 + public const int abc_text_size_small_material = 2131230806; + + // aapt resource value: 0x7f080057 + public const int abc_text_size_subhead_material = 2131230807; + + // aapt resource value: 0x7f080012 + public const int abc_text_size_subtitle_material_toolbar = 2131230738; + + // aapt resource value: 0x7f080058 + public const int abc_text_size_title_material = 2131230808; + + // aapt resource value: 0x7f080013 + public const int abc_text_size_title_material_toolbar = 2131230739; + + // aapt resource value: 0x7f08000c + public const int cardview_compat_inset_shadow = 2131230732; + + // aapt resource value: 0x7f08000d + public const int cardview_default_elevation = 2131230733; + + // aapt resource value: 0x7f08000e + public const int cardview_default_radius = 2131230734; + + // aapt resource value: 0x7f080094 + public const int compat_button_inset_horizontal_material = 2131230868; + + // aapt resource value: 0x7f080095 + public const int compat_button_inset_vertical_material = 2131230869; + + // aapt resource value: 0x7f080096 + public const int compat_button_padding_horizontal_material = 2131230870; + + // aapt resource value: 0x7f080097 + public const int compat_button_padding_vertical_material = 2131230871; + + // aapt resource value: 0x7f080098 + public const int compat_control_corner_material = 2131230872; + + // aapt resource value: 0x7f080072 + public const int design_appbar_elevation = 2131230834; + + // aapt resource value: 0x7f080073 + public const int design_bottom_navigation_active_item_max_width = 2131230835; + + // aapt resource value: 0x7f080074 + public const int design_bottom_navigation_active_text_size = 2131230836; + + // aapt resource value: 0x7f080075 + public const int design_bottom_navigation_elevation = 2131230837; + + // aapt resource value: 0x7f080076 + public const int design_bottom_navigation_height = 2131230838; + + // aapt resource value: 0x7f080077 + public const int design_bottom_navigation_item_max_width = 2131230839; + + // aapt resource value: 0x7f080078 + public const int design_bottom_navigation_item_min_width = 2131230840; + + // aapt resource value: 0x7f080079 + public const int design_bottom_navigation_margin = 2131230841; + + // aapt resource value: 0x7f08007a + public const int design_bottom_navigation_shadow_height = 2131230842; + + // aapt resource value: 0x7f08007b + public const int design_bottom_navigation_text_size = 2131230843; + + // aapt resource value: 0x7f08007c + public const int design_bottom_sheet_modal_elevation = 2131230844; + + // aapt resource value: 0x7f08007d + public const int design_bottom_sheet_peek_height_min = 2131230845; + + // aapt resource value: 0x7f08007e + public const int design_fab_border_width = 2131230846; + + // aapt resource value: 0x7f08007f + public const int design_fab_elevation = 2131230847; + + // aapt resource value: 0x7f080080 + public const int design_fab_image_size = 2131230848; + + // aapt resource value: 0x7f080081 + public const int design_fab_size_mini = 2131230849; + + // aapt resource value: 0x7f080082 + public const int design_fab_size_normal = 2131230850; + + // aapt resource value: 0x7f080083 + public const int design_fab_translation_z_pressed = 2131230851; + + // aapt resource value: 0x7f080084 + public const int design_navigation_elevation = 2131230852; + + // aapt resource value: 0x7f080085 + public const int design_navigation_icon_padding = 2131230853; + + // aapt resource value: 0x7f080086 + public const int design_navigation_icon_size = 2131230854; + + // aapt resource value: 0x7f08006a + public const int design_navigation_max_width = 2131230826; + + // aapt resource value: 0x7f080087 + public const int design_navigation_padding_bottom = 2131230855; + + // aapt resource value: 0x7f080088 + public const int design_navigation_separator_vertical_padding = 2131230856; + + // aapt resource value: 0x7f08006b + public const int design_snackbar_action_inline_max_width = 2131230827; + + // aapt resource value: 0x7f08006c + public const int design_snackbar_background_corner_radius = 2131230828; + + // aapt resource value: 0x7f080089 + public const int design_snackbar_elevation = 2131230857; + + // aapt resource value: 0x7f08006d + public const int design_snackbar_extra_spacing_horizontal = 2131230829; + + // aapt resource value: 0x7f08006e + public const int design_snackbar_max_width = 2131230830; + + // aapt resource value: 0x7f08006f + public const int design_snackbar_min_width = 2131230831; + + // aapt resource value: 0x7f08008a + public const int design_snackbar_padding_horizontal = 2131230858; + + // aapt resource value: 0x7f08008b + public const int design_snackbar_padding_vertical = 2131230859; + + // aapt resource value: 0x7f080070 + public const int design_snackbar_padding_vertical_2lines = 2131230832; + + // aapt resource value: 0x7f08008c + public const int design_snackbar_text_size = 2131230860; + + // aapt resource value: 0x7f08008d + public const int design_tab_max_width = 2131230861; + + // aapt resource value: 0x7f080071 + public const int design_tab_scrollable_min_width = 2131230833; + + // aapt resource value: 0x7f08008e + public const int design_tab_text_size = 2131230862; + + // aapt resource value: 0x7f08008f + public const int design_tab_text_size_2line = 2131230863; + + // aapt resource value: 0x7f080059 + public const int disabled_alpha_material_dark = 2131230809; + + // aapt resource value: 0x7f08005a + public const int disabled_alpha_material_light = 2131230810; + + // aapt resource value: 0x7f080000 + public const int fastscroll_default_thickness = 2131230720; + + // aapt resource value: 0x7f080001 + public const int fastscroll_margin = 2131230721; + + // aapt resource value: 0x7f080002 + public const int fastscroll_minimum_range = 2131230722; + + // aapt resource value: 0x7f08005b + public const int highlight_alpha_material_colored = 2131230811; + + // aapt resource value: 0x7f08005c + public const int highlight_alpha_material_dark = 2131230812; + + // aapt resource value: 0x7f08005d + public const int highlight_alpha_material_light = 2131230813; + + // aapt resource value: 0x7f08005e + public const int hint_alpha_material_dark = 2131230814; + + // aapt resource value: 0x7f08005f + public const int hint_alpha_material_light = 2131230815; + + // aapt resource value: 0x7f080060 + public const int hint_pressed_alpha_material_dark = 2131230816; + + // aapt resource value: 0x7f080061 + public const int hint_pressed_alpha_material_light = 2131230817; + + // aapt resource value: 0x7f080003 + public const int item_touch_helper_max_drag_scroll_per_frame = 2131230723; + + // aapt resource value: 0x7f080004 + public const int item_touch_helper_swipe_escape_max_velocity = 2131230724; + + // aapt resource value: 0x7f080005 + public const int item_touch_helper_swipe_escape_velocity = 2131230725; + + // aapt resource value: 0x7f080006 + public const int mr_controller_volume_group_list_item_height = 2131230726; + + // aapt resource value: 0x7f080007 + public const int mr_controller_volume_group_list_item_icon_size = 2131230727; + + // aapt resource value: 0x7f080008 + public const int mr_controller_volume_group_list_max_height = 2131230728; + + // aapt resource value: 0x7f08000b + public const int mr_controller_volume_group_list_padding_top = 2131230731; + + // aapt resource value: 0x7f080009 + public const int mr_dialog_fixed_width_major = 2131230729; + + // aapt resource value: 0x7f08000a + public const int mr_dialog_fixed_width_minor = 2131230730; + + // aapt resource value: 0x7f080099 + public const int notification_action_icon_size = 2131230873; + + // aapt resource value: 0x7f08009a + public const int notification_action_text_size = 2131230874; + + // aapt resource value: 0x7f08009b + public const int notification_big_circle_margin = 2131230875; + + // aapt resource value: 0x7f080091 + public const int notification_content_margin_start = 2131230865; + + // aapt resource value: 0x7f08009c + public const int notification_large_icon_height = 2131230876; + + // aapt resource value: 0x7f08009d + public const int notification_large_icon_width = 2131230877; + + // aapt resource value: 0x7f080092 + public const int notification_main_column_padding_top = 2131230866; + + // aapt resource value: 0x7f080093 + public const int notification_media_narrow_margin = 2131230867; + + // aapt resource value: 0x7f08009e + public const int notification_right_icon_size = 2131230878; + + // aapt resource value: 0x7f080090 + public const int notification_right_side_padding_top = 2131230864; + + // aapt resource value: 0x7f08009f + public const int notification_small_icon_background_padding = 2131230879; + + // aapt resource value: 0x7f0800a0 + public const int notification_small_icon_size_as_large = 2131230880; + + // aapt resource value: 0x7f0800a1 + public const int notification_subtext_size = 2131230881; + + // aapt resource value: 0x7f0800a2 + public const int notification_top_pad = 2131230882; + + // aapt resource value: 0x7f0800a3 + public const int notification_top_pad_large_text = 2131230883; + + // aapt resource value: 0x7f080062 + public const int tooltip_corner_radius = 2131230818; + + // aapt resource value: 0x7f080063 + public const int tooltip_horizontal_padding = 2131230819; + + // aapt resource value: 0x7f080064 + public const int tooltip_margin = 2131230820; + + // aapt resource value: 0x7f080065 + public const int tooltip_precise_anchor_extra_offset = 2131230821; + + // aapt resource value: 0x7f080066 + public const int tooltip_precise_anchor_threshold = 2131230822; + + // aapt resource value: 0x7f080067 + public const int tooltip_vertical_padding = 2131230823; + + // aapt resource value: 0x7f080068 + public const int tooltip_y_offset_non_touch = 2131230824; + + // aapt resource value: 0x7f080069 + public const int tooltip_y_offset_touch = 2131230825; + + static Dimension() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Dimension() + { + } + } + + public partial class Drawable + { + + // aapt resource value: 0x7f020000 + public const int abc_ab_share_pack_mtrl_alpha = 2130837504; + + // aapt resource value: 0x7f020001 + public const int abc_action_bar_item_background_material = 2130837505; + + // aapt resource value: 0x7f020002 + public const int abc_btn_borderless_material = 2130837506; + + // aapt resource value: 0x7f020003 + public const int abc_btn_check_material = 2130837507; + + // aapt resource value: 0x7f020004 + public const int abc_btn_check_to_on_mtrl_000 = 2130837508; + + // aapt resource value: 0x7f020005 + public const int abc_btn_check_to_on_mtrl_015 = 2130837509; + + // aapt resource value: 0x7f020006 + public const int abc_btn_colored_material = 2130837510; + + // aapt resource value: 0x7f020007 + public const int abc_btn_default_mtrl_shape = 2130837511; + + // aapt resource value: 0x7f020008 + public const int abc_btn_radio_material = 2130837512; + + // aapt resource value: 0x7f020009 + public const int abc_btn_radio_to_on_mtrl_000 = 2130837513; + + // aapt resource value: 0x7f02000a + public const int abc_btn_radio_to_on_mtrl_015 = 2130837514; + + // aapt resource value: 0x7f02000b + public const int abc_btn_switch_to_on_mtrl_00001 = 2130837515; + + // aapt resource value: 0x7f02000c + public const int abc_btn_switch_to_on_mtrl_00012 = 2130837516; + + // aapt resource value: 0x7f02000d + public const int abc_cab_background_internal_bg = 2130837517; + + // aapt resource value: 0x7f02000e + public const int abc_cab_background_top_material = 2130837518; + + // aapt resource value: 0x7f02000f + public const int abc_cab_background_top_mtrl_alpha = 2130837519; + + // aapt resource value: 0x7f020010 + public const int abc_control_background_material = 2130837520; + + // aapt resource value: 0x7f020011 + public const int abc_dialog_material_background = 2130837521; + + // aapt resource value: 0x7f020012 + public const int abc_edit_text_material = 2130837522; + + // aapt resource value: 0x7f020013 + public const int abc_ic_ab_back_material = 2130837523; + + // aapt resource value: 0x7f020014 + public const int abc_ic_arrow_drop_right_black_24dp = 2130837524; + + // aapt resource value: 0x7f020015 + public const int abc_ic_clear_material = 2130837525; + + // aapt resource value: 0x7f020016 + public const int abc_ic_commit_search_api_mtrl_alpha = 2130837526; + + // aapt resource value: 0x7f020017 + public const int abc_ic_go_search_api_material = 2130837527; + + // aapt resource value: 0x7f020018 + public const int abc_ic_menu_copy_mtrl_am_alpha = 2130837528; + + // aapt resource value: 0x7f020019 + public const int abc_ic_menu_cut_mtrl_alpha = 2130837529; + + // aapt resource value: 0x7f02001a + public const int abc_ic_menu_overflow_material = 2130837530; + + // aapt resource value: 0x7f02001b + public const int abc_ic_menu_paste_mtrl_am_alpha = 2130837531; + + // aapt resource value: 0x7f02001c + public const int abc_ic_menu_selectall_mtrl_alpha = 2130837532; + + // aapt resource value: 0x7f02001d + public const int abc_ic_menu_share_mtrl_alpha = 2130837533; + + // aapt resource value: 0x7f02001e + public const int abc_ic_search_api_material = 2130837534; + + // aapt resource value: 0x7f02001f + public const int abc_ic_star_black_16dp = 2130837535; + + // aapt resource value: 0x7f020020 + public const int abc_ic_star_black_36dp = 2130837536; + + // aapt resource value: 0x7f020021 + public const int abc_ic_star_black_48dp = 2130837537; + + // aapt resource value: 0x7f020022 + public const int abc_ic_star_half_black_16dp = 2130837538; + + // aapt resource value: 0x7f020023 + public const int abc_ic_star_half_black_36dp = 2130837539; + + // aapt resource value: 0x7f020024 + public const int abc_ic_star_half_black_48dp = 2130837540; + + // aapt resource value: 0x7f020025 + public const int abc_ic_voice_search_api_material = 2130837541; + + // aapt resource value: 0x7f020026 + public const int abc_item_background_holo_dark = 2130837542; + + // aapt resource value: 0x7f020027 + public const int abc_item_background_holo_light = 2130837543; + + // aapt resource value: 0x7f020028 + public const int abc_list_divider_mtrl_alpha = 2130837544; + + // aapt resource value: 0x7f020029 + public const int abc_list_focused_holo = 2130837545; + + // aapt resource value: 0x7f02002a + public const int abc_list_longpressed_holo = 2130837546; + + // aapt resource value: 0x7f02002b + public const int abc_list_pressed_holo_dark = 2130837547; + + // aapt resource value: 0x7f02002c + public const int abc_list_pressed_holo_light = 2130837548; + + // aapt resource value: 0x7f02002d + public const int abc_list_selector_background_transition_holo_dark = 2130837549; + + // aapt resource value: 0x7f02002e + public const int abc_list_selector_background_transition_holo_light = 2130837550; + + // aapt resource value: 0x7f02002f + public const int abc_list_selector_disabled_holo_dark = 2130837551; + + // aapt resource value: 0x7f020030 + public const int abc_list_selector_disabled_holo_light = 2130837552; + + // aapt resource value: 0x7f020031 + public const int abc_list_selector_holo_dark = 2130837553; + + // aapt resource value: 0x7f020032 + public const int abc_list_selector_holo_light = 2130837554; + + // aapt resource value: 0x7f020033 + public const int abc_menu_hardkey_panel_mtrl_mult = 2130837555; + + // aapt resource value: 0x7f020034 + public const int abc_popup_background_mtrl_mult = 2130837556; + + // aapt resource value: 0x7f020035 + public const int abc_ratingbar_indicator_material = 2130837557; + + // aapt resource value: 0x7f020036 + public const int abc_ratingbar_material = 2130837558; + + // aapt resource value: 0x7f020037 + public const int abc_ratingbar_small_material = 2130837559; + + // aapt resource value: 0x7f020038 + public const int abc_scrubber_control_off_mtrl_alpha = 2130837560; + + // aapt resource value: 0x7f020039 + public const int abc_scrubber_control_to_pressed_mtrl_000 = 2130837561; + + // aapt resource value: 0x7f02003a + public const int abc_scrubber_control_to_pressed_mtrl_005 = 2130837562; + + // aapt resource value: 0x7f02003b + public const int abc_scrubber_primary_mtrl_alpha = 2130837563; + + // aapt resource value: 0x7f02003c + public const int abc_scrubber_track_mtrl_alpha = 2130837564; + + // aapt resource value: 0x7f02003d + public const int abc_seekbar_thumb_material = 2130837565; + + // aapt resource value: 0x7f02003e + public const int abc_seekbar_tick_mark_material = 2130837566; + + // aapt resource value: 0x7f02003f + public const int abc_seekbar_track_material = 2130837567; + + // aapt resource value: 0x7f020040 + public const int abc_spinner_mtrl_am_alpha = 2130837568; + + // aapt resource value: 0x7f020041 + public const int abc_spinner_textfield_background_material = 2130837569; + + // aapt resource value: 0x7f020042 + public const int abc_switch_thumb_material = 2130837570; + + // aapt resource value: 0x7f020043 + public const int abc_switch_track_mtrl_alpha = 2130837571; + + // aapt resource value: 0x7f020044 + public const int abc_tab_indicator_material = 2130837572; + + // aapt resource value: 0x7f020045 + public const int abc_tab_indicator_mtrl_alpha = 2130837573; + + // aapt resource value: 0x7f020046 + public const int abc_text_cursor_material = 2130837574; + + // aapt resource value: 0x7f020047 + public const int abc_text_select_handle_left_mtrl_dark = 2130837575; + + // aapt resource value: 0x7f020048 + public const int abc_text_select_handle_left_mtrl_light = 2130837576; + + // aapt resource value: 0x7f020049 + public const int abc_text_select_handle_middle_mtrl_dark = 2130837577; + + // aapt resource value: 0x7f02004a + public const int abc_text_select_handle_middle_mtrl_light = 2130837578; + + // aapt resource value: 0x7f02004b + public const int abc_text_select_handle_right_mtrl_dark = 2130837579; + + // aapt resource value: 0x7f02004c + public const int abc_text_select_handle_right_mtrl_light = 2130837580; + + // aapt resource value: 0x7f02004d + public const int abc_textfield_activated_mtrl_alpha = 2130837581; + + // aapt resource value: 0x7f02004e + public const int abc_textfield_default_mtrl_alpha = 2130837582; + + // aapt resource value: 0x7f02004f + public const int abc_textfield_search_activated_mtrl_alpha = 2130837583; + + // aapt resource value: 0x7f020050 + public const int abc_textfield_search_default_mtrl_alpha = 2130837584; + + // aapt resource value: 0x7f020051 + public const int abc_textfield_search_material = 2130837585; + + // aapt resource value: 0x7f020052 + public const int abc_vector_test = 2130837586; + + // aapt resource value: 0x7f020053 + public const int avd_hide_password = 2130837587; + + // aapt resource value: 0x7f02012f + public const int avd_hide_password_1 = 2130837807; + + // aapt resource value: 0x7f020130 + public const int avd_hide_password_2 = 2130837808; + + // aapt resource value: 0x7f020131 + public const int avd_hide_password_3 = 2130837809; + + // aapt resource value: 0x7f020054 + public const int avd_show_password = 2130837588; + + // aapt resource value: 0x7f020132 + public const int avd_show_password_1 = 2130837810; + + // aapt resource value: 0x7f020133 + public const int avd_show_password_2 = 2130837811; + + // aapt resource value: 0x7f020134 + public const int avd_show_password_3 = 2130837812; + + // aapt resource value: 0x7f020055 + public const int design_bottom_navigation_item_background = 2130837589; + + // aapt resource value: 0x7f020056 + public const int design_fab_background = 2130837590; + + // aapt resource value: 0x7f020057 + public const int design_ic_visibility = 2130837591; + + // aapt resource value: 0x7f020058 + public const int design_ic_visibility_off = 2130837592; + + // aapt resource value: 0x7f020059 + public const int design_password_eye = 2130837593; + + // aapt resource value: 0x7f02005a + public const int design_snackbar_background = 2130837594; + + // aapt resource value: 0x7f02005b + public const int ic_audiotrack_dark = 2130837595; + + // aapt resource value: 0x7f02005c + public const int ic_audiotrack_light = 2130837596; + + // aapt resource value: 0x7f02005d + public const int ic_dialog_close_dark = 2130837597; + + // aapt resource value: 0x7f02005e + public const int ic_dialog_close_light = 2130837598; + + // aapt resource value: 0x7f02005f + public const int ic_group_collapse_00 = 2130837599; + + // aapt resource value: 0x7f020060 + public const int ic_group_collapse_01 = 2130837600; + + // aapt resource value: 0x7f020061 + public const int ic_group_collapse_02 = 2130837601; + + // aapt resource value: 0x7f020062 + public const int ic_group_collapse_03 = 2130837602; + + // aapt resource value: 0x7f020063 + public const int ic_group_collapse_04 = 2130837603; + + // aapt resource value: 0x7f020064 + public const int ic_group_collapse_05 = 2130837604; + + // aapt resource value: 0x7f020065 + public const int ic_group_collapse_06 = 2130837605; + + // aapt resource value: 0x7f020066 + public const int ic_group_collapse_07 = 2130837606; + + // aapt resource value: 0x7f020067 + public const int ic_group_collapse_08 = 2130837607; + + // aapt resource value: 0x7f020068 + public const int ic_group_collapse_09 = 2130837608; + + // aapt resource value: 0x7f020069 + public const int ic_group_collapse_10 = 2130837609; + + // aapt resource value: 0x7f02006a + public const int ic_group_collapse_11 = 2130837610; + + // aapt resource value: 0x7f02006b + public const int ic_group_collapse_12 = 2130837611; + + // aapt resource value: 0x7f02006c + public const int ic_group_collapse_13 = 2130837612; + + // aapt resource value: 0x7f02006d + public const int ic_group_collapse_14 = 2130837613; + + // aapt resource value: 0x7f02006e + public const int ic_group_collapse_15 = 2130837614; + + // aapt resource value: 0x7f02006f + public const int ic_group_expand_00 = 2130837615; + + // aapt resource value: 0x7f020070 + public const int ic_group_expand_01 = 2130837616; + + // aapt resource value: 0x7f020071 + public const int ic_group_expand_02 = 2130837617; + + // aapt resource value: 0x7f020072 + public const int ic_group_expand_03 = 2130837618; + + // aapt resource value: 0x7f020073 + public const int ic_group_expand_04 = 2130837619; + + // aapt resource value: 0x7f020074 + public const int ic_group_expand_05 = 2130837620; + + // aapt resource value: 0x7f020075 + public const int ic_group_expand_06 = 2130837621; + + // aapt resource value: 0x7f020076 + public const int ic_group_expand_07 = 2130837622; + + // aapt resource value: 0x7f020077 + public const int ic_group_expand_08 = 2130837623; + + // aapt resource value: 0x7f020078 + public const int ic_group_expand_09 = 2130837624; + + // aapt resource value: 0x7f020079 + public const int ic_group_expand_10 = 2130837625; + + // aapt resource value: 0x7f02007a + public const int ic_group_expand_11 = 2130837626; + + // aapt resource value: 0x7f02007b + public const int ic_group_expand_12 = 2130837627; + + // aapt resource value: 0x7f02007c + public const int ic_group_expand_13 = 2130837628; + + // aapt resource value: 0x7f02007d + public const int ic_group_expand_14 = 2130837629; + + // aapt resource value: 0x7f02007e + public const int ic_group_expand_15 = 2130837630; + + // aapt resource value: 0x7f02007f + public const int ic_media_pause_dark = 2130837631; + + // aapt resource value: 0x7f020080 + public const int ic_media_pause_light = 2130837632; + + // aapt resource value: 0x7f020081 + public const int ic_media_play_dark = 2130837633; + + // aapt resource value: 0x7f020082 + public const int ic_media_play_light = 2130837634; + + // aapt resource value: 0x7f020083 + public const int ic_media_stop_dark = 2130837635; + + // aapt resource value: 0x7f020084 + public const int ic_media_stop_light = 2130837636; + + // aapt resource value: 0x7f020085 + public const int ic_mr_button_connected_00_dark = 2130837637; + + // aapt resource value: 0x7f020086 + public const int ic_mr_button_connected_00_light = 2130837638; + + // aapt resource value: 0x7f020087 + public const int ic_mr_button_connected_01_dark = 2130837639; + + // aapt resource value: 0x7f020088 + public const int ic_mr_button_connected_01_light = 2130837640; + + // aapt resource value: 0x7f020089 + public const int ic_mr_button_connected_02_dark = 2130837641; + + // aapt resource value: 0x7f02008a + public const int ic_mr_button_connected_02_light = 2130837642; + + // aapt resource value: 0x7f02008b + public const int ic_mr_button_connected_03_dark = 2130837643; + + // aapt resource value: 0x7f02008c + public const int ic_mr_button_connected_03_light = 2130837644; + + // aapt resource value: 0x7f02008d + public const int ic_mr_button_connected_04_dark = 2130837645; + + // aapt resource value: 0x7f02008e + public const int ic_mr_button_connected_04_light = 2130837646; + + // aapt resource value: 0x7f02008f + public const int ic_mr_button_connected_05_dark = 2130837647; + + // aapt resource value: 0x7f020090 + public const int ic_mr_button_connected_05_light = 2130837648; + + // aapt resource value: 0x7f020091 + public const int ic_mr_button_connected_06_dark = 2130837649; + + // aapt resource value: 0x7f020092 + public const int ic_mr_button_connected_06_light = 2130837650; + + // aapt resource value: 0x7f020093 + public const int ic_mr_button_connected_07_dark = 2130837651; + + // aapt resource value: 0x7f020094 + public const int ic_mr_button_connected_07_light = 2130837652; + + // aapt resource value: 0x7f020095 + public const int ic_mr_button_connected_08_dark = 2130837653; + + // aapt resource value: 0x7f020096 + public const int ic_mr_button_connected_08_light = 2130837654; + + // aapt resource value: 0x7f020097 + public const int ic_mr_button_connected_09_dark = 2130837655; + + // aapt resource value: 0x7f020098 + public const int ic_mr_button_connected_09_light = 2130837656; + + // aapt resource value: 0x7f020099 + public const int ic_mr_button_connected_10_dark = 2130837657; + + // aapt resource value: 0x7f02009a + public const int ic_mr_button_connected_10_light = 2130837658; + + // aapt resource value: 0x7f02009b + public const int ic_mr_button_connected_11_dark = 2130837659; + + // aapt resource value: 0x7f02009c + public const int ic_mr_button_connected_11_light = 2130837660; + + // aapt resource value: 0x7f02009d + public const int ic_mr_button_connected_12_dark = 2130837661; + + // aapt resource value: 0x7f02009e + public const int ic_mr_button_connected_12_light = 2130837662; + + // aapt resource value: 0x7f02009f + public const int ic_mr_button_connected_13_dark = 2130837663; + + // aapt resource value: 0x7f0200a0 + public const int ic_mr_button_connected_13_light = 2130837664; + + // aapt resource value: 0x7f0200a1 + public const int ic_mr_button_connected_14_dark = 2130837665; + + // aapt resource value: 0x7f0200a2 + public const int ic_mr_button_connected_14_light = 2130837666; + + // aapt resource value: 0x7f0200a3 + public const int ic_mr_button_connected_15_dark = 2130837667; + + // aapt resource value: 0x7f0200a4 + public const int ic_mr_button_connected_15_light = 2130837668; + + // aapt resource value: 0x7f0200a5 + public const int ic_mr_button_connected_16_dark = 2130837669; + + // aapt resource value: 0x7f0200a6 + public const int ic_mr_button_connected_16_light = 2130837670; + + // aapt resource value: 0x7f0200a7 + public const int ic_mr_button_connected_17_dark = 2130837671; + + // aapt resource value: 0x7f0200a8 + public const int ic_mr_button_connected_17_light = 2130837672; + + // aapt resource value: 0x7f0200a9 + public const int ic_mr_button_connected_18_dark = 2130837673; + + // aapt resource value: 0x7f0200aa + public const int ic_mr_button_connected_18_light = 2130837674; + + // aapt resource value: 0x7f0200ab + public const int ic_mr_button_connected_19_dark = 2130837675; + + // aapt resource value: 0x7f0200ac + public const int ic_mr_button_connected_19_light = 2130837676; + + // aapt resource value: 0x7f0200ad + public const int ic_mr_button_connected_20_dark = 2130837677; + + // aapt resource value: 0x7f0200ae + public const int ic_mr_button_connected_20_light = 2130837678; + + // aapt resource value: 0x7f0200af + public const int ic_mr_button_connected_21_dark = 2130837679; + + // aapt resource value: 0x7f0200b0 + public const int ic_mr_button_connected_21_light = 2130837680; + + // aapt resource value: 0x7f0200b1 + public const int ic_mr_button_connected_22_dark = 2130837681; + + // aapt resource value: 0x7f0200b2 + public const int ic_mr_button_connected_22_light = 2130837682; + + // aapt resource value: 0x7f0200b3 + public const int ic_mr_button_connected_23_dark = 2130837683; + + // aapt resource value: 0x7f0200b4 + public const int ic_mr_button_connected_23_light = 2130837684; + + // aapt resource value: 0x7f0200b5 + public const int ic_mr_button_connected_24_dark = 2130837685; + + // aapt resource value: 0x7f0200b6 + public const int ic_mr_button_connected_24_light = 2130837686; + + // aapt resource value: 0x7f0200b7 + public const int ic_mr_button_connected_25_dark = 2130837687; + + // aapt resource value: 0x7f0200b8 + public const int ic_mr_button_connected_25_light = 2130837688; + + // aapt resource value: 0x7f0200b9 + public const int ic_mr_button_connected_26_dark = 2130837689; + + // aapt resource value: 0x7f0200ba + public const int ic_mr_button_connected_26_light = 2130837690; + + // aapt resource value: 0x7f0200bb + public const int ic_mr_button_connected_27_dark = 2130837691; + + // aapt resource value: 0x7f0200bc + public const int ic_mr_button_connected_27_light = 2130837692; + + // aapt resource value: 0x7f0200bd + public const int ic_mr_button_connected_28_dark = 2130837693; + + // aapt resource value: 0x7f0200be + public const int ic_mr_button_connected_28_light = 2130837694; + + // aapt resource value: 0x7f0200bf + public const int ic_mr_button_connected_29_dark = 2130837695; + + // aapt resource value: 0x7f0200c0 + public const int ic_mr_button_connected_29_light = 2130837696; + + // aapt resource value: 0x7f0200c1 + public const int ic_mr_button_connected_30_dark = 2130837697; + + // aapt resource value: 0x7f0200c2 + public const int ic_mr_button_connected_30_light = 2130837698; + + // aapt resource value: 0x7f0200c3 + public const int ic_mr_button_connecting_00_dark = 2130837699; + + // aapt resource value: 0x7f0200c4 + public const int ic_mr_button_connecting_00_light = 2130837700; + + // aapt resource value: 0x7f0200c5 + public const int ic_mr_button_connecting_01_dark = 2130837701; + + // aapt resource value: 0x7f0200c6 + public const int ic_mr_button_connecting_01_light = 2130837702; + + // aapt resource value: 0x7f0200c7 + public const int ic_mr_button_connecting_02_dark = 2130837703; + + // aapt resource value: 0x7f0200c8 + public const int ic_mr_button_connecting_02_light = 2130837704; + + // aapt resource value: 0x7f0200c9 + public const int ic_mr_button_connecting_03_dark = 2130837705; + + // aapt resource value: 0x7f0200ca + public const int ic_mr_button_connecting_03_light = 2130837706; + + // aapt resource value: 0x7f0200cb + public const int ic_mr_button_connecting_04_dark = 2130837707; + + // aapt resource value: 0x7f0200cc + public const int ic_mr_button_connecting_04_light = 2130837708; + + // aapt resource value: 0x7f0200cd + public const int ic_mr_button_connecting_05_dark = 2130837709; + + // aapt resource value: 0x7f0200ce + public const int ic_mr_button_connecting_05_light = 2130837710; + + // aapt resource value: 0x7f0200cf + public const int ic_mr_button_connecting_06_dark = 2130837711; + + // aapt resource value: 0x7f0200d0 + public const int ic_mr_button_connecting_06_light = 2130837712; + + // aapt resource value: 0x7f0200d1 + public const int ic_mr_button_connecting_07_dark = 2130837713; + + // aapt resource value: 0x7f0200d2 + public const int ic_mr_button_connecting_07_light = 2130837714; + + // aapt resource value: 0x7f0200d3 + public const int ic_mr_button_connecting_08_dark = 2130837715; + + // aapt resource value: 0x7f0200d4 + public const int ic_mr_button_connecting_08_light = 2130837716; + + // aapt resource value: 0x7f0200d5 + public const int ic_mr_button_connecting_09_dark = 2130837717; + + // aapt resource value: 0x7f0200d6 + public const int ic_mr_button_connecting_09_light = 2130837718; + + // aapt resource value: 0x7f0200d7 + public const int ic_mr_button_connecting_10_dark = 2130837719; + + // aapt resource value: 0x7f0200d8 + public const int ic_mr_button_connecting_10_light = 2130837720; + + // aapt resource value: 0x7f0200d9 + public const int ic_mr_button_connecting_11_dark = 2130837721; + + // aapt resource value: 0x7f0200da + public const int ic_mr_button_connecting_11_light = 2130837722; + + // aapt resource value: 0x7f0200db + public const int ic_mr_button_connecting_12_dark = 2130837723; + + // aapt resource value: 0x7f0200dc + public const int ic_mr_button_connecting_12_light = 2130837724; + + // aapt resource value: 0x7f0200dd + public const int ic_mr_button_connecting_13_dark = 2130837725; + + // aapt resource value: 0x7f0200de + public const int ic_mr_button_connecting_13_light = 2130837726; + + // aapt resource value: 0x7f0200df + public const int ic_mr_button_connecting_14_dark = 2130837727; + + // aapt resource value: 0x7f0200e0 + public const int ic_mr_button_connecting_14_light = 2130837728; + + // aapt resource value: 0x7f0200e1 + public const int ic_mr_button_connecting_15_dark = 2130837729; + + // aapt resource value: 0x7f0200e2 + public const int ic_mr_button_connecting_15_light = 2130837730; + + // aapt resource value: 0x7f0200e3 + public const int ic_mr_button_connecting_16_dark = 2130837731; + + // aapt resource value: 0x7f0200e4 + public const int ic_mr_button_connecting_16_light = 2130837732; + + // aapt resource value: 0x7f0200e5 + public const int ic_mr_button_connecting_17_dark = 2130837733; + + // aapt resource value: 0x7f0200e6 + public const int ic_mr_button_connecting_17_light = 2130837734; + + // aapt resource value: 0x7f0200e7 + public const int ic_mr_button_connecting_18_dark = 2130837735; + + // aapt resource value: 0x7f0200e8 + public const int ic_mr_button_connecting_18_light = 2130837736; + + // aapt resource value: 0x7f0200e9 + public const int ic_mr_button_connecting_19_dark = 2130837737; + + // aapt resource value: 0x7f0200ea + public const int ic_mr_button_connecting_19_light = 2130837738; + + // aapt resource value: 0x7f0200eb + public const int ic_mr_button_connecting_20_dark = 2130837739; + + // aapt resource value: 0x7f0200ec + public const int ic_mr_button_connecting_20_light = 2130837740; + + // aapt resource value: 0x7f0200ed + public const int ic_mr_button_connecting_21_dark = 2130837741; + + // aapt resource value: 0x7f0200ee + public const int ic_mr_button_connecting_21_light = 2130837742; + + // aapt resource value: 0x7f0200ef + public const int ic_mr_button_connecting_22_dark = 2130837743; + + // aapt resource value: 0x7f0200f0 + public const int ic_mr_button_connecting_22_light = 2130837744; + + // aapt resource value: 0x7f0200f1 + public const int ic_mr_button_connecting_23_dark = 2130837745; + + // aapt resource value: 0x7f0200f2 + public const int ic_mr_button_connecting_23_light = 2130837746; + + // aapt resource value: 0x7f0200f3 + public const int ic_mr_button_connecting_24_dark = 2130837747; + + // aapt resource value: 0x7f0200f4 + public const int ic_mr_button_connecting_24_light = 2130837748; + + // aapt resource value: 0x7f0200f5 + public const int ic_mr_button_connecting_25_dark = 2130837749; + + // aapt resource value: 0x7f0200f6 + public const int ic_mr_button_connecting_25_light = 2130837750; + + // aapt resource value: 0x7f0200f7 + public const int ic_mr_button_connecting_26_dark = 2130837751; + + // aapt resource value: 0x7f0200f8 + public const int ic_mr_button_connecting_26_light = 2130837752; + + // aapt resource value: 0x7f0200f9 + public const int ic_mr_button_connecting_27_dark = 2130837753; + + // aapt resource value: 0x7f0200fa + public const int ic_mr_button_connecting_27_light = 2130837754; + + // aapt resource value: 0x7f0200fb + public const int ic_mr_button_connecting_28_dark = 2130837755; + + // aapt resource value: 0x7f0200fc + public const int ic_mr_button_connecting_28_light = 2130837756; + + // aapt resource value: 0x7f0200fd + public const int ic_mr_button_connecting_29_dark = 2130837757; + + // aapt resource value: 0x7f0200fe + public const int ic_mr_button_connecting_29_light = 2130837758; + + // aapt resource value: 0x7f0200ff + public const int ic_mr_button_connecting_30_dark = 2130837759; + + // aapt resource value: 0x7f020100 + public const int ic_mr_button_connecting_30_light = 2130837760; + + // aapt resource value: 0x7f020101 + public const int ic_mr_button_disabled_dark = 2130837761; + + // aapt resource value: 0x7f020102 + public const int ic_mr_button_disabled_light = 2130837762; + + // aapt resource value: 0x7f020103 + public const int ic_mr_button_disconnected_dark = 2130837763; + + // aapt resource value: 0x7f020104 + public const int ic_mr_button_disconnected_light = 2130837764; + + // aapt resource value: 0x7f020105 + public const int ic_mr_button_grey = 2130837765; + + // aapt resource value: 0x7f020106 + public const int ic_vol_type_speaker_dark = 2130837766; + + // aapt resource value: 0x7f020107 + public const int ic_vol_type_speaker_group_dark = 2130837767; + + // aapt resource value: 0x7f020108 + public const int ic_vol_type_speaker_group_light = 2130837768; + + // aapt resource value: 0x7f020109 + public const int ic_vol_type_speaker_light = 2130837769; + + // aapt resource value: 0x7f02010a + public const int ic_vol_type_tv_dark = 2130837770; + + // aapt resource value: 0x7f02010b + public const int ic_vol_type_tv_light = 2130837771; + + // aapt resource value: 0x7f02010c + public const int mr_button_connected_dark = 2130837772; + + // aapt resource value: 0x7f02010d + public const int mr_button_connected_light = 2130837773; + + // aapt resource value: 0x7f02010e + public const int mr_button_connecting_dark = 2130837774; + + // aapt resource value: 0x7f02010f + public const int mr_button_connecting_light = 2130837775; + + // aapt resource value: 0x7f020110 + public const int mr_button_dark = 2130837776; + + // aapt resource value: 0x7f020111 + public const int mr_button_light = 2130837777; + + // aapt resource value: 0x7f020112 + public const int mr_dialog_close_dark = 2130837778; + + // aapt resource value: 0x7f020113 + public const int mr_dialog_close_light = 2130837779; + + // aapt resource value: 0x7f020114 + public const int mr_dialog_material_background_dark = 2130837780; + + // aapt resource value: 0x7f020115 + public const int mr_dialog_material_background_light = 2130837781; + + // aapt resource value: 0x7f020116 + public const int mr_group_collapse = 2130837782; + + // aapt resource value: 0x7f020117 + public const int mr_group_expand = 2130837783; + + // aapt resource value: 0x7f020118 + public const int mr_media_pause_dark = 2130837784; + + // aapt resource value: 0x7f020119 + public const int mr_media_pause_light = 2130837785; + + // aapt resource value: 0x7f02011a + public const int mr_media_play_dark = 2130837786; + + // aapt resource value: 0x7f02011b + public const int mr_media_play_light = 2130837787; + + // aapt resource value: 0x7f02011c + public const int mr_media_stop_dark = 2130837788; + + // aapt resource value: 0x7f02011d + public const int mr_media_stop_light = 2130837789; + + // aapt resource value: 0x7f02011e + public const int mr_vol_type_audiotrack_dark = 2130837790; + + // aapt resource value: 0x7f02011f + public const int mr_vol_type_audiotrack_light = 2130837791; + + // aapt resource value: 0x7f020120 + public const int navigation_empty_icon = 2130837792; + + // aapt resource value: 0x7f020121 + public const int notification_action_background = 2130837793; + + // aapt resource value: 0x7f020122 + public const int notification_bg = 2130837794; + + // aapt resource value: 0x7f020123 + public const int notification_bg_low = 2130837795; + + // aapt resource value: 0x7f020124 + public const int notification_bg_low_normal = 2130837796; + + // aapt resource value: 0x7f020125 + public const int notification_bg_low_pressed = 2130837797; + + // aapt resource value: 0x7f020126 + public const int notification_bg_normal = 2130837798; + + // aapt resource value: 0x7f020127 + public const int notification_bg_normal_pressed = 2130837799; + + // aapt resource value: 0x7f020128 + public const int notification_icon_background = 2130837800; + + // aapt resource value: 0x7f02012d + public const int notification_template_icon_bg = 2130837805; + + // aapt resource value: 0x7f02012e + public const int notification_template_icon_low_bg = 2130837806; + + // aapt resource value: 0x7f020129 + public const int notification_tile_bg = 2130837801; + + // aapt resource value: 0x7f02012a + public const int notify_panel_notification_icon_bg = 2130837802; + + // aapt resource value: 0x7f02012b + public const int tooltip_frame_dark = 2130837803; + + // aapt resource value: 0x7f02012c + public const int tooltip_frame_light = 2130837804; + + static Drawable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Drawable() + { + } + } + + public partial class Id + { + + // aapt resource value: 0x7f090032 + public const int ALT = 2131296306; + + // aapt resource value: 0x7f090033 + public const int CTRL = 2131296307; + + // aapt resource value: 0x7f090034 + public const int FUNCTION = 2131296308; + + // aapt resource value: 0x7f090035 + public const int META = 2131296309; + + // aapt resource value: 0x7f090036 + public const int SHIFT = 2131296310; + + // aapt resource value: 0x7f090037 + public const int SYM = 2131296311; + + // aapt resource value: 0x7f0900ba + public const int action0 = 2131296442; + + // aapt resource value: 0x7f09007c + public const int action_bar = 2131296380; + + // aapt resource value: 0x7f090001 + public const int action_bar_activity_content = 2131296257; + + // aapt resource value: 0x7f09007b + public const int action_bar_container = 2131296379; + + // aapt resource value: 0x7f090077 + public const int action_bar_root = 2131296375; + + // aapt resource value: 0x7f090002 + public const int action_bar_spinner = 2131296258; + + // aapt resource value: 0x7f09005b + public const int action_bar_subtitle = 2131296347; + + // aapt resource value: 0x7f09005a + public const int action_bar_title = 2131296346; + + // aapt resource value: 0x7f0900b7 + public const int action_container = 2131296439; + + // aapt resource value: 0x7f09007d + public const int action_context_bar = 2131296381; + + // aapt resource value: 0x7f0900be + public const int action_divider = 2131296446; + + // aapt resource value: 0x7f0900b8 + public const int action_image = 2131296440; + + // aapt resource value: 0x7f090003 + public const int action_menu_divider = 2131296259; + + // aapt resource value: 0x7f090004 + public const int action_menu_presenter = 2131296260; + + // aapt resource value: 0x7f090079 + public const int action_mode_bar = 2131296377; + + // aapt resource value: 0x7f090078 + public const int action_mode_bar_stub = 2131296376; + + // aapt resource value: 0x7f09005c + public const int action_mode_close_button = 2131296348; + + // aapt resource value: 0x7f0900b9 + public const int action_text = 2131296441; + + // aapt resource value: 0x7f0900c7 + public const int actions = 2131296455; + + // aapt resource value: 0x7f09005d + public const int activity_chooser_view_content = 2131296349; + + // aapt resource value: 0x7f090027 + public const int add = 2131296295; + + // aapt resource value: 0x7f090070 + public const int alertTitle = 2131296368; + + // aapt resource value: 0x7f090052 + public const int all = 2131296338; + + // aapt resource value: 0x7f090038 + public const int always = 2131296312; + + // aapt resource value: 0x7f090056 + public const int async = 2131296342; + + // aapt resource value: 0x7f090044 + public const int auto = 2131296324; + + // aapt resource value: 0x7f09002f + public const int beginning = 2131296303; + + // aapt resource value: 0x7f090057 + public const int blocking = 2131296343; + + // aapt resource value: 0x7f09003d + public const int bottom = 2131296317; + + // aapt resource value: 0x7f09008b + public const int bottomtab_navarea = 2131296395; + + // aapt resource value: 0x7f09008c + public const int bottomtab_tabbar = 2131296396; + + // aapt resource value: 0x7f090063 + public const int buttonPanel = 2131296355; + + // aapt resource value: 0x7f0900bb + public const int cancel_action = 2131296443; + + // aapt resource value: 0x7f090045 + public const int center = 2131296325; + + // aapt resource value: 0x7f090046 + public const int center_horizontal = 2131296326; + + // aapt resource value: 0x7f090047 + public const int center_vertical = 2131296327; + + // aapt resource value: 0x7f090073 + public const int checkbox = 2131296371; + + // aapt resource value: 0x7f0900c3 + public const int chronometer = 2131296451; + + // aapt resource value: 0x7f09004e + public const int clip_horizontal = 2131296334; + + // aapt resource value: 0x7f09004f + public const int clip_vertical = 2131296335; + + // aapt resource value: 0x7f090039 + public const int collapseActionView = 2131296313; + + // aapt resource value: 0x7f09008f + public const int container = 2131296399; + + // aapt resource value: 0x7f090066 + public const int contentPanel = 2131296358; + + // aapt resource value: 0x7f090090 + public const int coordinator = 2131296400; + + // aapt resource value: 0x7f09006d + public const int custom = 2131296365; + + // aapt resource value: 0x7f09006c + public const int customPanel = 2131296364; + + // aapt resource value: 0x7f09007a + public const int decor_content_parent = 2131296378; + + // aapt resource value: 0x7f090060 + public const int default_activity_button = 2131296352; + + // aapt resource value: 0x7f090092 + public const int design_bottom_sheet = 2131296402; + + // aapt resource value: 0x7f090099 + public const int design_menu_item_action_area = 2131296409; + + // aapt resource value: 0x7f090098 + public const int design_menu_item_action_area_stub = 2131296408; + + // aapt resource value: 0x7f090097 + public const int design_menu_item_text = 2131296407; + + // aapt resource value: 0x7f090096 + public const int design_navigation_view = 2131296406; + + // aapt resource value: 0x7f090020 + public const int disableHome = 2131296288; + + // aapt resource value: 0x7f09007e + public const int edit_query = 2131296382; + + // aapt resource value: 0x7f090030 + public const int end = 2131296304; + + // aapt resource value: 0x7f0900c9 + public const int end_padder = 2131296457; + + // aapt resource value: 0x7f09003f + public const int enterAlways = 2131296319; + + // aapt resource value: 0x7f090040 + public const int enterAlwaysCollapsed = 2131296320; + + // aapt resource value: 0x7f090041 + public const int exitUntilCollapsed = 2131296321; + + // aapt resource value: 0x7f09005e + public const int expand_activities_button = 2131296350; + + // aapt resource value: 0x7f090072 + public const int expanded_menu = 2131296370; + + // aapt resource value: 0x7f090050 + public const int fill = 2131296336; + + // aapt resource value: 0x7f090051 + public const int fill_horizontal = 2131296337; + + // aapt resource value: 0x7f090048 + public const int fill_vertical = 2131296328; + + // aapt resource value: 0x7f090054 + public const int @fixed = 2131296340; + + // aapt resource value: 0x7f09009b + public const int flyoutcontent_appbar = 2131296411; + + // aapt resource value: 0x7f09009c + public const int flyoutcontent_recycler = 2131296412; + + // aapt resource value: 0x7f090058 + public const int forever = 2131296344; + + // aapt resource value: 0x7f09000a + public const int ghost_view = 2131296266; + + // aapt resource value: 0x7f090005 + public const int home = 2131296261; + + // aapt resource value: 0x7f090021 + public const int homeAsUp = 2131296289; + + // aapt resource value: 0x7f090062 + public const int icon = 2131296354; + + // aapt resource value: 0x7f0900c8 + public const int icon_group = 2131296456; + + // aapt resource value: 0x7f09003a + public const int ifRoom = 2131296314; + + // aapt resource value: 0x7f09005f + public const int image = 2131296351; + + // aapt resource value: 0x7f0900c4 + public const int info = 2131296452; + + // aapt resource value: 0x7f090059 + public const int italic = 2131296345; + + // aapt resource value: 0x7f090000 + public const int item_touch_helper_previous_elevation = 2131296256; + + // aapt resource value: 0x7f09008e + public const int largeLabel = 2131296398; + + // aapt resource value: 0x7f090049 + public const int left = 2131296329; + + // aapt resource value: 0x7f090017 + public const int line1 = 2131296279; + + // aapt resource value: 0x7f090018 + public const int line3 = 2131296280; + + // aapt resource value: 0x7f09001d + public const int listMode = 2131296285; + + // aapt resource value: 0x7f090061 + public const int list_item = 2131296353; + + // aapt resource value: 0x7f0900ca + public const int main_appbar = 2131296458; + + // aapt resource value: 0x7f0900cd + public const int main_scrollview = 2131296461; + + // aapt resource value: 0x7f0900cc + public const int main_tablayout = 2131296460; + + // aapt resource value: 0x7f0900cb + public const int main_toolbar = 2131296459; + + // aapt resource value: 0x7f0900d3 + public const int masked = 2131296467; + + // aapt resource value: 0x7f0900bd + public const int media_actions = 2131296445; + + // aapt resource value: 0x7f0900d1 + public const int message = 2131296465; + + // aapt resource value: 0x7f090031 + public const int middle = 2131296305; + + // aapt resource value: 0x7f090053 + public const int mini = 2131296339; + + // aapt resource value: 0x7f0900a9 + public const int mr_art = 2131296425; + + // aapt resource value: 0x7f09009e + public const int mr_chooser_list = 2131296414; + + // aapt resource value: 0x7f0900a1 + public const int mr_chooser_route_desc = 2131296417; + + // aapt resource value: 0x7f09009f + public const int mr_chooser_route_icon = 2131296415; + + // aapt resource value: 0x7f0900a0 + public const int mr_chooser_route_name = 2131296416; + + // aapt resource value: 0x7f09009d + public const int mr_chooser_title = 2131296413; + + // aapt resource value: 0x7f0900a6 + public const int mr_close = 2131296422; + + // aapt resource value: 0x7f0900ac + public const int mr_control_divider = 2131296428; + + // aapt resource value: 0x7f0900b2 + public const int mr_control_playback_ctrl = 2131296434; + + // aapt resource value: 0x7f0900b5 + public const int mr_control_subtitle = 2131296437; + + // aapt resource value: 0x7f0900b4 + public const int mr_control_title = 2131296436; + + // aapt resource value: 0x7f0900b3 + public const int mr_control_title_container = 2131296435; + + // aapt resource value: 0x7f0900a7 + public const int mr_custom_control = 2131296423; + + // aapt resource value: 0x7f0900a8 + public const int mr_default_control = 2131296424; + + // aapt resource value: 0x7f0900a3 + public const int mr_dialog_area = 2131296419; + + // aapt resource value: 0x7f0900a2 + public const int mr_expandable_area = 2131296418; + + // aapt resource value: 0x7f0900b6 + public const int mr_group_expand_collapse = 2131296438; + + // aapt resource value: 0x7f0900aa + public const int mr_media_main_control = 2131296426; + + // aapt resource value: 0x7f0900a5 + public const int mr_name = 2131296421; + + // aapt resource value: 0x7f0900ab + public const int mr_playback_control = 2131296427; + + // aapt resource value: 0x7f0900a4 + public const int mr_title_bar = 2131296420; + + // aapt resource value: 0x7f0900ad + public const int mr_volume_control = 2131296429; + + // aapt resource value: 0x7f0900ae + public const int mr_volume_group_list = 2131296430; + + // aapt resource value: 0x7f0900b0 + public const int mr_volume_item_icon = 2131296432; + + // aapt resource value: 0x7f0900b1 + public const int mr_volume_slider = 2131296433; + + // aapt resource value: 0x7f090028 + public const int multiply = 2131296296; + + // aapt resource value: 0x7f090095 + public const int navigation_header_container = 2131296405; + + // aapt resource value: 0x7f09003b + public const int never = 2131296315; + + // aapt resource value: 0x7f090022 + public const int none = 2131296290; + + // aapt resource value: 0x7f09001e + public const int normal = 2131296286; + + // aapt resource value: 0x7f0900c6 + public const int notification_background = 2131296454; + + // aapt resource value: 0x7f0900c0 + public const int notification_main_column = 2131296448; + + // aapt resource value: 0x7f0900bf + public const int notification_main_column_container = 2131296447; + + // aapt resource value: 0x7f09004c + public const int parallax = 2131296332; + + // aapt resource value: 0x7f090065 + public const int parentPanel = 2131296357; + + // aapt resource value: 0x7f09000b + public const int parent_matrix = 2131296267; + + // aapt resource value: 0x7f09004d + public const int pin = 2131296333; + + // aapt resource value: 0x7f090006 + public const int progress_circular = 2131296262; + + // aapt resource value: 0x7f090007 + public const int progress_horizontal = 2131296263; + + // aapt resource value: 0x7f090075 + public const int radio = 2131296373; + + // aapt resource value: 0x7f09004a + public const int right = 2131296330; + + // aapt resource value: 0x7f0900c5 + public const int right_icon = 2131296453; + + // aapt resource value: 0x7f0900c1 + public const int right_side = 2131296449; + + // aapt resource value: 0x7f09000c + public const int save_image_matrix = 2131296268; + + // aapt resource value: 0x7f09000d + public const int save_non_transition_alpha = 2131296269; + + // aapt resource value: 0x7f09000e + public const int save_scale_type = 2131296270; + + // aapt resource value: 0x7f090029 + public const int screen = 2131296297; + + // aapt resource value: 0x7f090042 + public const int scroll = 2131296322; + + // aapt resource value: 0x7f09006b + public const int scrollIndicatorDown = 2131296363; + + // aapt resource value: 0x7f090067 + public const int scrollIndicatorUp = 2131296359; + + // aapt resource value: 0x7f090068 + public const int scrollView = 2131296360; + + // aapt resource value: 0x7f090055 + public const int scrollable = 2131296341; + + // aapt resource value: 0x7f090080 + public const int search_badge = 2131296384; + + // aapt resource value: 0x7f09007f + public const int search_bar = 2131296383; + + // aapt resource value: 0x7f090081 + public const int search_button = 2131296385; + + // aapt resource value: 0x7f090086 + public const int search_close_btn = 2131296390; + + // aapt resource value: 0x7f090082 + public const int search_edit_frame = 2131296386; + + // aapt resource value: 0x7f090088 + public const int search_go_btn = 2131296392; + + // aapt resource value: 0x7f090083 + public const int search_mag_icon = 2131296387; + + // aapt resource value: 0x7f090084 + public const int search_plate = 2131296388; + + // aapt resource value: 0x7f090085 + public const int search_src_text = 2131296389; + + // aapt resource value: 0x7f090089 + public const int search_voice_btn = 2131296393; + + // aapt resource value: 0x7f09008a + public const int select_dialog_listview = 2131296394; + + // aapt resource value: 0x7f0900ce + public const int shellcontent_appbar = 2131296462; + + // aapt resource value: 0x7f0900d0 + public const int shellcontent_scrollview = 2131296464; + + // aapt resource value: 0x7f0900cf + public const int shellcontent_toolbar = 2131296463; + + // aapt resource value: 0x7f090074 + public const int shortcut = 2131296372; + + // aapt resource value: 0x7f090023 + public const int showCustom = 2131296291; + + // aapt resource value: 0x7f090024 + public const int showHome = 2131296292; + + // aapt resource value: 0x7f090025 + public const int showTitle = 2131296293; + + // aapt resource value: 0x7f09008d + public const int smallLabel = 2131296397; + + // aapt resource value: 0x7f090094 + public const int snackbar_action = 2131296404; + + // aapt resource value: 0x7f090093 + public const int snackbar_text = 2131296403; + + // aapt resource value: 0x7f090043 + public const int snap = 2131296323; + + // aapt resource value: 0x7f090064 + public const int spacer = 2131296356; + + // aapt resource value: 0x7f090008 + public const int split_action_bar = 2131296264; + + // aapt resource value: 0x7f09002a + public const int src_atop = 2131296298; + + // aapt resource value: 0x7f09002b + public const int src_in = 2131296299; + + // aapt resource value: 0x7f09002c + public const int src_over = 2131296300; + + // aapt resource value: 0x7f09004b + public const int start = 2131296331; + + // aapt resource value: 0x7f0900bc + public const int status_bar_latest_event_content = 2131296444; + + // aapt resource value: 0x7f090076 + public const int submenuarrow = 2131296374; + + // aapt resource value: 0x7f090087 + public const int submit_area = 2131296391; + + // aapt resource value: 0x7f09001f + public const int tabMode = 2131296287; + + // aapt resource value: 0x7f090019 + public const int tag_transition_group = 2131296281; + + // aapt resource value: 0x7f09001a + public const int text = 2131296282; + + // aapt resource value: 0x7f09001b + public const int text2 = 2131296283; + + // aapt resource value: 0x7f09006a + public const int textSpacerNoButtons = 2131296362; + + // aapt resource value: 0x7f090069 + public const int textSpacerNoTitle = 2131296361; + + // aapt resource value: 0x7f09009a + public const int text_input_password_toggle = 2131296410; + + // aapt resource value: 0x7f090014 + public const int textinput_counter = 2131296276; + + // aapt resource value: 0x7f090015 + public const int textinput_error = 2131296277; + + // aapt resource value: 0x7f0900c2 + public const int time = 2131296450; + + // aapt resource value: 0x7f09001c + public const int title = 2131296284; + + // aapt resource value: 0x7f090071 + public const int titleDividerNoCustom = 2131296369; + + // aapt resource value: 0x7f09006f + public const int title_template = 2131296367; + + // aapt resource value: 0x7f09003e + public const int top = 2131296318; + + // aapt resource value: 0x7f09006e + public const int topPanel = 2131296366; + + // aapt resource value: 0x7f090091 + public const int touch_outside = 2131296401; + + // aapt resource value: 0x7f09000f + public const int transition_current_scene = 2131296271; + + // aapt resource value: 0x7f090010 + public const int transition_layout_save = 2131296272; + + // aapt resource value: 0x7f090011 + public const int transition_position = 2131296273; + + // aapt resource value: 0x7f090012 + public const int transition_scene_layoutid_cache = 2131296274; + + // aapt resource value: 0x7f090013 + public const int transition_transform = 2131296275; + + // aapt resource value: 0x7f09002d + public const int uniform = 2131296301; + + // aapt resource value: 0x7f090009 + public const int up = 2131296265; + + // aapt resource value: 0x7f090026 + public const int useLogo = 2131296294; + + // aapt resource value: 0x7f090016 + public const int view_offset_helper = 2131296278; + + // aapt resource value: 0x7f0900d2 + public const int visible = 2131296466; + + // aapt resource value: 0x7f0900af + public const int volume_item_container = 2131296431; + + // aapt resource value: 0x7f09003c + public const int withText = 2131296316; + + // aapt resource value: 0x7f09002e + public const int wrap_content = 2131296302; + + static Id() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Id() + { + } + } + + public partial class Integer + { + + // aapt resource value: 0x7f0b0003 + public const int abc_config_activityDefaultDur = 2131427331; + + // aapt resource value: 0x7f0b0004 + public const int abc_config_activityShortDur = 2131427332; + + // aapt resource value: 0x7f0b0008 + public const int app_bar_elevation_anim_duration = 2131427336; + + // aapt resource value: 0x7f0b0009 + public const int bottom_sheet_slide_duration = 2131427337; + + // aapt resource value: 0x7f0b0005 + public const int cancel_button_image_alpha = 2131427333; + + // aapt resource value: 0x7f0b0006 + public const int config_tooltipAnimTime = 2131427334; + + // aapt resource value: 0x7f0b0007 + public const int design_snackbar_text_max_lines = 2131427335; + + // aapt resource value: 0x7f0b000a + public const int hide_password_duration = 2131427338; + + // aapt resource value: 0x7f0b0000 + public const int mr_controller_volume_group_list_animation_duration_ms = 2131427328; + + // aapt resource value: 0x7f0b0001 + public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131427329; + + // aapt resource value: 0x7f0b0002 + public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131427330; + + // aapt resource value: 0x7f0b000b + public const int show_password_duration = 2131427339; + + // aapt resource value: 0x7f0b000c + public const int status_bar_notification_info_maxnum = 2131427340; + + static Integer() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Integer() + { + } + } + + public partial class Interpolator + { + + // aapt resource value: 0x7f070000 + public const int mr_fast_out_slow_in = 2131165184; + + // aapt resource value: 0x7f070001 + public const int mr_linear_out_slow_in = 2131165185; + + static Interpolator() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Interpolator() + { + } + } + + public partial class Layout + { + + // aapt resource value: 0x7f040000 + public const int abc_action_bar_title_item = 2130968576; + + // aapt resource value: 0x7f040001 + public const int abc_action_bar_up_container = 2130968577; + + // aapt resource value: 0x7f040002 + public const int abc_action_menu_item_layout = 2130968578; + + // aapt resource value: 0x7f040003 + public const int abc_action_menu_layout = 2130968579; + + // aapt resource value: 0x7f040004 + public const int abc_action_mode_bar = 2130968580; + + // aapt resource value: 0x7f040005 + public const int abc_action_mode_close_item_material = 2130968581; + + // aapt resource value: 0x7f040006 + public const int abc_activity_chooser_view = 2130968582; + + // aapt resource value: 0x7f040007 + public const int abc_activity_chooser_view_list_item = 2130968583; + + // aapt resource value: 0x7f040008 + public const int abc_alert_dialog_button_bar_material = 2130968584; + + // aapt resource value: 0x7f040009 + public const int abc_alert_dialog_material = 2130968585; + + // aapt resource value: 0x7f04000a + public const int abc_alert_dialog_title_material = 2130968586; + + // aapt resource value: 0x7f04000b + public const int abc_dialog_title_material = 2130968587; + + // aapt resource value: 0x7f04000c + public const int abc_expanded_menu_layout = 2130968588; + + // aapt resource value: 0x7f04000d + public const int abc_list_menu_item_checkbox = 2130968589; + + // aapt resource value: 0x7f04000e + public const int abc_list_menu_item_icon = 2130968590; + + // aapt resource value: 0x7f04000f + public const int abc_list_menu_item_layout = 2130968591; + + // aapt resource value: 0x7f040010 + public const int abc_list_menu_item_radio = 2130968592; + + // aapt resource value: 0x7f040011 + public const int abc_popup_menu_header_item_layout = 2130968593; + + // aapt resource value: 0x7f040012 + public const int abc_popup_menu_item_layout = 2130968594; + + // aapt resource value: 0x7f040013 + public const int abc_screen_content_include = 2130968595; + + // aapt resource value: 0x7f040014 + public const int abc_screen_simple = 2130968596; + + // aapt resource value: 0x7f040015 + public const int abc_screen_simple_overlay_action_mode = 2130968597; + + // aapt resource value: 0x7f040016 + public const int abc_screen_toolbar = 2130968598; + + // aapt resource value: 0x7f040017 + public const int abc_search_dropdown_item_icons_2line = 2130968599; + + // aapt resource value: 0x7f040018 + public const int abc_search_view = 2130968600; + + // aapt resource value: 0x7f040019 + public const int abc_select_dialog_material = 2130968601; + + // aapt resource value: 0x7f04001a + public const int activity_main = 2130968602; + + // aapt resource value: 0x7f04001b + public const int BottomTabLayout = 2130968603; + + // aapt resource value: 0x7f04001c + public const int design_bottom_navigation_item = 2130968604; + + // aapt resource value: 0x7f04001d + public const int design_bottom_sheet_dialog = 2130968605; + + // aapt resource value: 0x7f04001e + public const int design_layout_snackbar = 2130968606; + + // aapt resource value: 0x7f04001f + public const int design_layout_snackbar_include = 2130968607; + + // aapt resource value: 0x7f040020 + public const int design_layout_tab_icon = 2130968608; + + // aapt resource value: 0x7f040021 + public const int design_layout_tab_text = 2130968609; + + // aapt resource value: 0x7f040022 + public const int design_menu_item_action_area = 2130968610; + + // aapt resource value: 0x7f040023 + public const int design_navigation_item = 2130968611; + + // aapt resource value: 0x7f040024 + public const int design_navigation_item_header = 2130968612; + + // aapt resource value: 0x7f040025 + public const int design_navigation_item_separator = 2130968613; + + // aapt resource value: 0x7f040026 + public const int design_navigation_item_subheader = 2130968614; + + // aapt resource value: 0x7f040027 + public const int design_navigation_menu = 2130968615; + + // aapt resource value: 0x7f040028 + public const int design_navigation_menu_item = 2130968616; + + // aapt resource value: 0x7f040029 + public const int design_text_input_password_icon = 2130968617; + + // aapt resource value: 0x7f04002a + public const int FlyoutContent = 2130968618; + + // aapt resource value: 0x7f04002b + public const int mr_chooser_dialog = 2130968619; + + // aapt resource value: 0x7f04002c + public const int mr_chooser_list_item = 2130968620; + + // aapt resource value: 0x7f04002d + public const int mr_controller_material_dialog_b = 2130968621; + + // aapt resource value: 0x7f04002e + public const int mr_controller_volume_item = 2130968622; + + // aapt resource value: 0x7f04002f + public const int mr_playback_control = 2130968623; + + // aapt resource value: 0x7f040030 + public const int mr_volume_control = 2130968624; + + // aapt resource value: 0x7f040031 + public const int notification_action = 2130968625; + + // aapt resource value: 0x7f040032 + public const int notification_action_tombstone = 2130968626; + + // aapt resource value: 0x7f040033 + public const int notification_media_action = 2130968627; + + // aapt resource value: 0x7f040034 + public const int notification_media_cancel_action = 2130968628; + + // aapt resource value: 0x7f040035 + public const int notification_template_big_media = 2130968629; + + // aapt resource value: 0x7f040036 + public const int notification_template_big_media_custom = 2130968630; + + // aapt resource value: 0x7f040037 + public const int notification_template_big_media_narrow = 2130968631; + + // aapt resource value: 0x7f040038 + public const int notification_template_big_media_narrow_custom = 2130968632; + + // aapt resource value: 0x7f040039 + public const int notification_template_custom_big = 2130968633; + + // aapt resource value: 0x7f04003a + public const int notification_template_icon_group = 2130968634; + + // aapt resource value: 0x7f04003b + public const int notification_template_lines_media = 2130968635; + + // aapt resource value: 0x7f04003c + public const int notification_template_media = 2130968636; + + // aapt resource value: 0x7f04003d + public const int notification_template_media_custom = 2130968637; + + // aapt resource value: 0x7f04003e + public const int notification_template_part_chronometer = 2130968638; + + // aapt resource value: 0x7f04003f + public const int notification_template_part_time = 2130968639; + + // aapt resource value: 0x7f040040 + public const int RootLayout = 2130968640; + + // aapt resource value: 0x7f040041 + public const int select_dialog_item_material = 2130968641; + + // aapt resource value: 0x7f040042 + public const int select_dialog_multichoice_material = 2130968642; + + // aapt resource value: 0x7f040043 + public const int select_dialog_singlechoice_material = 2130968643; + + // aapt resource value: 0x7f040044 + public const int ShellContent = 2130968644; + + // aapt resource value: 0x7f040045 + public const int support_simple_spinner_dropdown_item = 2130968645; + + // aapt resource value: 0x7f040046 + public const int tooltip = 2130968646; + + static Layout() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Layout() + { + } + } + + public partial class Mipmap + { + + // aapt resource value: 0x7f030000 + public const int ic_launcher = 2130903040; + + // aapt resource value: 0x7f030001 + public const int ic_launcher_foreground = 2130903041; + + // aapt resource value: 0x7f030002 + public const int ic_launcher_round = 2130903042; + + static Mipmap() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Mipmap() + { + } + } + + public partial class String + { + + // aapt resource value: 0x7f0a0015 + public const int abc_action_bar_home_description = 2131361813; + + // aapt resource value: 0x7f0a0016 + public const int abc_action_bar_up_description = 2131361814; + + // aapt resource value: 0x7f0a0017 + public const int abc_action_menu_overflow_description = 2131361815; + + // aapt resource value: 0x7f0a0018 + public const int abc_action_mode_done = 2131361816; + + // aapt resource value: 0x7f0a0019 + public const int abc_activity_chooser_view_see_all = 2131361817; + + // aapt resource value: 0x7f0a001a + public const int abc_activitychooserview_choose_application = 2131361818; + + // aapt resource value: 0x7f0a001b + public const int abc_capital_off = 2131361819; + + // aapt resource value: 0x7f0a001c + public const int abc_capital_on = 2131361820; + + // aapt resource value: 0x7f0a0027 + public const int abc_font_family_body_1_material = 2131361831; + + // aapt resource value: 0x7f0a0028 + public const int abc_font_family_body_2_material = 2131361832; + + // aapt resource value: 0x7f0a0029 + public const int abc_font_family_button_material = 2131361833; + + // aapt resource value: 0x7f0a002a + public const int abc_font_family_caption_material = 2131361834; + + // aapt resource value: 0x7f0a002b + public const int abc_font_family_display_1_material = 2131361835; + + // aapt resource value: 0x7f0a002c + public const int abc_font_family_display_2_material = 2131361836; + + // aapt resource value: 0x7f0a002d + public const int abc_font_family_display_3_material = 2131361837; + + // aapt resource value: 0x7f0a002e + public const int abc_font_family_display_4_material = 2131361838; + + // aapt resource value: 0x7f0a002f + public const int abc_font_family_headline_material = 2131361839; + + // aapt resource value: 0x7f0a0030 + public const int abc_font_family_menu_material = 2131361840; + + // aapt resource value: 0x7f0a0031 + public const int abc_font_family_subhead_material = 2131361841; + + // aapt resource value: 0x7f0a0032 + public const int abc_font_family_title_material = 2131361842; + + // aapt resource value: 0x7f0a001d + public const int abc_search_hint = 2131361821; + + // aapt resource value: 0x7f0a001e + public const int abc_searchview_description_clear = 2131361822; + + // aapt resource value: 0x7f0a001f + public const int abc_searchview_description_query = 2131361823; + + // aapt resource value: 0x7f0a0020 + public const int abc_searchview_description_search = 2131361824; + + // aapt resource value: 0x7f0a0021 + public const int abc_searchview_description_submit = 2131361825; + + // aapt resource value: 0x7f0a0022 + public const int abc_searchview_description_voice = 2131361826; + + // aapt resource value: 0x7f0a0023 + public const int abc_shareactionprovider_share_with = 2131361827; + + // aapt resource value: 0x7f0a0024 + public const int abc_shareactionprovider_share_with_application = 2131361828; + + // aapt resource value: 0x7f0a0025 + public const int abc_toolbar_collapse_description = 2131361829; + + // aapt resource value: 0x7f0a003d + public const int action_settings = 2131361853; + + // aapt resource value: 0x7f0a003c + public const int app_name = 2131361852; + + // aapt resource value: 0x7f0a0033 + public const int appbar_scrolling_view_behavior = 2131361843; + + // aapt resource value: 0x7f0a0034 + public const int bottom_sheet_behavior = 2131361844; + + // aapt resource value: 0x7f0a0035 + public const int character_counter_pattern = 2131361845; + + // aapt resource value: 0x7f0a0000 + public const int mr_button_content_description = 2131361792; + + // aapt resource value: 0x7f0a0001 + public const int mr_cast_button_connected = 2131361793; + + // aapt resource value: 0x7f0a0002 + public const int mr_cast_button_connecting = 2131361794; + + // aapt resource value: 0x7f0a0003 + public const int mr_cast_button_disconnected = 2131361795; + + // aapt resource value: 0x7f0a0004 + public const int mr_chooser_searching = 2131361796; + + // aapt resource value: 0x7f0a0005 + public const int mr_chooser_title = 2131361797; + + // aapt resource value: 0x7f0a0006 + public const int mr_controller_album_art = 2131361798; + + // aapt resource value: 0x7f0a0007 + public const int mr_controller_casting_screen = 2131361799; + + // aapt resource value: 0x7f0a0008 + public const int mr_controller_close_description = 2131361800; + + // aapt resource value: 0x7f0a0009 + public const int mr_controller_collapse_group = 2131361801; + + // aapt resource value: 0x7f0a000a + public const int mr_controller_disconnect = 2131361802; + + // aapt resource value: 0x7f0a000b + public const int mr_controller_expand_group = 2131361803; + + // aapt resource value: 0x7f0a000c + public const int mr_controller_no_info_available = 2131361804; + + // aapt resource value: 0x7f0a000d + public const int mr_controller_no_media_selected = 2131361805; + + // aapt resource value: 0x7f0a000e + public const int mr_controller_pause = 2131361806; + + // aapt resource value: 0x7f0a000f + public const int mr_controller_play = 2131361807; + + // aapt resource value: 0x7f0a0010 + public const int mr_controller_stop = 2131361808; + + // aapt resource value: 0x7f0a0011 + public const int mr_controller_stop_casting = 2131361809; + + // aapt resource value: 0x7f0a0012 + public const int mr_controller_volume_slider = 2131361810; + + // aapt resource value: 0x7f0a0013 + public const int mr_system_route_name = 2131361811; + + // aapt resource value: 0x7f0a0014 + public const int mr_user_route_category_name = 2131361812; + + // aapt resource value: 0x7f0a0036 + public const int password_toggle_content_description = 2131361846; + + // aapt resource value: 0x7f0a0037 + public const int path_password_eye = 2131361847; + + // aapt resource value: 0x7f0a0038 + public const int path_password_eye_mask_strike_through = 2131361848; + + // aapt resource value: 0x7f0a0039 + public const int path_password_eye_mask_visible = 2131361849; + + // aapt resource value: 0x7f0a003a + public const int path_password_strike_through = 2131361850; + + // aapt resource value: 0x7f0a0026 + public const int search_menu_title = 2131361830; + + // aapt resource value: 0x7f0a003b + public const int status_bar_notification_info_overflow = 2131361851; + + static String() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private String() + { + } + } + + public partial class Style + { + + // aapt resource value: 0x7f0c00a4 + public const int AlertDialog_AppCompat = 2131493028; + + // aapt resource value: 0x7f0c00a5 + public const int AlertDialog_AppCompat_Light = 2131493029; + + // aapt resource value: 0x7f0c00a6 + public const int Animation_AppCompat_Dialog = 2131493030; + + // aapt resource value: 0x7f0c00a7 + public const int Animation_AppCompat_DropDownUp = 2131493031; + + // aapt resource value: 0x7f0c00a8 + public const int Animation_AppCompat_Tooltip = 2131493032; + + // aapt resource value: 0x7f0c016e + public const int Animation_Design_BottomSheetDialog = 2131493230; + + // aapt resource value: 0x7f0c018f + public const int AppTheme = 2131493263; + + // aapt resource value: 0x7f0c00a9 + public const int Base_AlertDialog_AppCompat = 2131493033; + + // aapt resource value: 0x7f0c00aa + public const int Base_AlertDialog_AppCompat_Light = 2131493034; + + // aapt resource value: 0x7f0c00ab + public const int Base_Animation_AppCompat_Dialog = 2131493035; + + // aapt resource value: 0x7f0c00ac + public const int Base_Animation_AppCompat_DropDownUp = 2131493036; + + // aapt resource value: 0x7f0c00ad + public const int Base_Animation_AppCompat_Tooltip = 2131493037; + + // aapt resource value: 0x7f0c000c + public const int Base_CardView = 2131492876; + + // aapt resource value: 0x7f0c00ae + public const int Base_DialogWindowTitle_AppCompat = 2131493038; + + // aapt resource value: 0x7f0c00af + public const int Base_DialogWindowTitleBackground_AppCompat = 2131493039; + + // aapt resource value: 0x7f0c0048 + public const int Base_TextAppearance_AppCompat = 2131492936; + + // aapt resource value: 0x7f0c0049 + public const int Base_TextAppearance_AppCompat_Body1 = 2131492937; + + // aapt resource value: 0x7f0c004a + public const int Base_TextAppearance_AppCompat_Body2 = 2131492938; + + // aapt resource value: 0x7f0c0036 + public const int Base_TextAppearance_AppCompat_Button = 2131492918; + + // aapt resource value: 0x7f0c004b + public const int Base_TextAppearance_AppCompat_Caption = 2131492939; + + // aapt resource value: 0x7f0c004c + public const int Base_TextAppearance_AppCompat_Display1 = 2131492940; + + // aapt resource value: 0x7f0c004d + public const int Base_TextAppearance_AppCompat_Display2 = 2131492941; + + // aapt resource value: 0x7f0c004e + public const int Base_TextAppearance_AppCompat_Display3 = 2131492942; + + // aapt resource value: 0x7f0c004f + public const int Base_TextAppearance_AppCompat_Display4 = 2131492943; + + // aapt resource value: 0x7f0c0050 + public const int Base_TextAppearance_AppCompat_Headline = 2131492944; + + // aapt resource value: 0x7f0c001a + public const int Base_TextAppearance_AppCompat_Inverse = 2131492890; + + // aapt resource value: 0x7f0c0051 + public const int Base_TextAppearance_AppCompat_Large = 2131492945; + + // aapt resource value: 0x7f0c001b + public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131492891; + + // aapt resource value: 0x7f0c0052 + public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131492946; + + // aapt resource value: 0x7f0c0053 + public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131492947; + + // aapt resource value: 0x7f0c0054 + public const int Base_TextAppearance_AppCompat_Medium = 2131492948; + + // aapt resource value: 0x7f0c001c + public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131492892; + + // aapt resource value: 0x7f0c0055 + public const int Base_TextAppearance_AppCompat_Menu = 2131492949; + + // aapt resource value: 0x7f0c00b0 + public const int Base_TextAppearance_AppCompat_SearchResult = 2131493040; + + // aapt resource value: 0x7f0c0056 + public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131492950; + + // aapt resource value: 0x7f0c0057 + public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131492951; + + // aapt resource value: 0x7f0c0058 + public const int Base_TextAppearance_AppCompat_Small = 2131492952; + + // aapt resource value: 0x7f0c001d + public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131492893; + + // aapt resource value: 0x7f0c0059 + public const int Base_TextAppearance_AppCompat_Subhead = 2131492953; + + // aapt resource value: 0x7f0c001e + public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131492894; + + // aapt resource value: 0x7f0c005a + public const int Base_TextAppearance_AppCompat_Title = 2131492954; + + // aapt resource value: 0x7f0c001f + public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131492895; + + // aapt resource value: 0x7f0c00b1 + public const int Base_TextAppearance_AppCompat_Tooltip = 2131493041; + + // aapt resource value: 0x7f0c0095 + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131493013; + + // aapt resource value: 0x7f0c005b + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131492955; + + // aapt resource value: 0x7f0c005c + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131492956; + + // aapt resource value: 0x7f0c005d + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131492957; + + // aapt resource value: 0x7f0c005e + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131492958; + + // aapt resource value: 0x7f0c005f + public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131492959; + + // aapt resource value: 0x7f0c0060 + public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131492960; + + // aapt resource value: 0x7f0c0061 + public const int Base_TextAppearance_AppCompat_Widget_Button = 2131492961; + + // aapt resource value: 0x7f0c009c + public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131493020; + + // aapt resource value: 0x7f0c009d + public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131493021; + + // aapt resource value: 0x7f0c0096 + public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131493014; + + // aapt resource value: 0x7f0c00b2 + public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131493042; + + // aapt resource value: 0x7f0c0062 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131492962; + + // aapt resource value: 0x7f0c0063 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131492963; + + // aapt resource value: 0x7f0c0064 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131492964; + + // aapt resource value: 0x7f0c0065 + public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131492965; + + // aapt resource value: 0x7f0c0066 + public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131492966; + + // aapt resource value: 0x7f0c00b3 + public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131493043; + + // aapt resource value: 0x7f0c0067 + public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131492967; + + // aapt resource value: 0x7f0c0068 + public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131492968; + + // aapt resource value: 0x7f0c0069 + public const int Base_Theme_AppCompat = 2131492969; + + // aapt resource value: 0x7f0c00b4 + public const int Base_Theme_AppCompat_CompactMenu = 2131493044; + + // aapt resource value: 0x7f0c0020 + public const int Base_Theme_AppCompat_Dialog = 2131492896; + + // aapt resource value: 0x7f0c0021 + public const int Base_Theme_AppCompat_Dialog_Alert = 2131492897; + + // aapt resource value: 0x7f0c00b5 + public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131493045; + + // aapt resource value: 0x7f0c0022 + public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131492898; + + // aapt resource value: 0x7f0c0010 + public const int Base_Theme_AppCompat_DialogWhenLarge = 2131492880; + + // aapt resource value: 0x7f0c006a + public const int Base_Theme_AppCompat_Light = 2131492970; + + // aapt resource value: 0x7f0c00b6 + public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131493046; + + // aapt resource value: 0x7f0c0023 + public const int Base_Theme_AppCompat_Light_Dialog = 2131492899; + + // aapt resource value: 0x7f0c0024 + public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131492900; + + // aapt resource value: 0x7f0c00b7 + public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131493047; + + // aapt resource value: 0x7f0c0025 + public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131492901; + + // aapt resource value: 0x7f0c0011 + public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131492881; + + // aapt resource value: 0x7f0c00b8 + public const int Base_ThemeOverlay_AppCompat = 2131493048; + + // aapt resource value: 0x7f0c00b9 + public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131493049; + + // aapt resource value: 0x7f0c00ba + public const int Base_ThemeOverlay_AppCompat_Dark = 2131493050; + + // aapt resource value: 0x7f0c00bb + public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131493051; + + // aapt resource value: 0x7f0c0026 + public const int Base_ThemeOverlay_AppCompat_Dialog = 2131492902; + + // aapt resource value: 0x7f0c0027 + public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131492903; + + // aapt resource value: 0x7f0c00bc + public const int Base_ThemeOverlay_AppCompat_Light = 2131493052; + + // aapt resource value: 0x7f0c0028 + public const int Base_V11_Theme_AppCompat_Dialog = 2131492904; + + // aapt resource value: 0x7f0c0029 + public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131492905; + + // aapt resource value: 0x7f0c002a + public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131492906; + + // aapt resource value: 0x7f0c0032 + public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131492914; + + // aapt resource value: 0x7f0c0033 + public const int Base_V12_Widget_AppCompat_EditText = 2131492915; + + // aapt resource value: 0x7f0c016f + public const int Base_V14_Widget_Design_AppBarLayout = 2131493231; + + // aapt resource value: 0x7f0c006b + public const int Base_V21_Theme_AppCompat = 2131492971; + + // aapt resource value: 0x7f0c006c + public const int Base_V21_Theme_AppCompat_Dialog = 2131492972; + + // aapt resource value: 0x7f0c006d + public const int Base_V21_Theme_AppCompat_Light = 2131492973; + + // aapt resource value: 0x7f0c006e + public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131492974; + + // aapt resource value: 0x7f0c006f + public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131492975; + + // aapt resource value: 0x7f0c016b + public const int Base_V21_Widget_Design_AppBarLayout = 2131493227; + + // aapt resource value: 0x7f0c0093 + public const int Base_V22_Theme_AppCompat = 2131493011; + + // aapt resource value: 0x7f0c0094 + public const int Base_V22_Theme_AppCompat_Light = 2131493012; + + // aapt resource value: 0x7f0c0097 + public const int Base_V23_Theme_AppCompat = 2131493015; + + // aapt resource value: 0x7f0c0098 + public const int Base_V23_Theme_AppCompat_Light = 2131493016; + + // aapt resource value: 0x7f0c00a0 + public const int Base_V26_Theme_AppCompat = 2131493024; + + // aapt resource value: 0x7f0c00a1 + public const int Base_V26_Theme_AppCompat_Light = 2131493025; + + // aapt resource value: 0x7f0c00a2 + public const int Base_V26_Widget_AppCompat_Toolbar = 2131493026; + + // aapt resource value: 0x7f0c016d + public const int Base_V26_Widget_Design_AppBarLayout = 2131493229; + + // aapt resource value: 0x7f0c00bd + public const int Base_V7_Theme_AppCompat = 2131493053; + + // aapt resource value: 0x7f0c00be + public const int Base_V7_Theme_AppCompat_Dialog = 2131493054; + + // aapt resource value: 0x7f0c00bf + public const int Base_V7_Theme_AppCompat_Light = 2131493055; + + // aapt resource value: 0x7f0c00c0 + public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131493056; + + // aapt resource value: 0x7f0c00c1 + public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131493057; + + // aapt resource value: 0x7f0c00c2 + public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131493058; + + // aapt resource value: 0x7f0c00c3 + public const int Base_V7_Widget_AppCompat_EditText = 2131493059; + + // aapt resource value: 0x7f0c00c4 + public const int Base_V7_Widget_AppCompat_Toolbar = 2131493060; + + // aapt resource value: 0x7f0c00c5 + public const int Base_Widget_AppCompat_ActionBar = 2131493061; + + // aapt resource value: 0x7f0c00c6 + public const int Base_Widget_AppCompat_ActionBar_Solid = 2131493062; + + // aapt resource value: 0x7f0c00c7 + public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131493063; + + // aapt resource value: 0x7f0c0070 + public const int Base_Widget_AppCompat_ActionBar_TabText = 2131492976; + + // aapt resource value: 0x7f0c0071 + public const int Base_Widget_AppCompat_ActionBar_TabView = 2131492977; + + // aapt resource value: 0x7f0c0072 + public const int Base_Widget_AppCompat_ActionButton = 2131492978; + + // aapt resource value: 0x7f0c0073 + public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131492979; + + // aapt resource value: 0x7f0c0074 + public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131492980; + + // aapt resource value: 0x7f0c00c8 + public const int Base_Widget_AppCompat_ActionMode = 2131493064; + + // aapt resource value: 0x7f0c00c9 + public const int Base_Widget_AppCompat_ActivityChooserView = 2131493065; + + // aapt resource value: 0x7f0c0034 + public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131492916; + + // aapt resource value: 0x7f0c0075 + public const int Base_Widget_AppCompat_Button = 2131492981; + + // aapt resource value: 0x7f0c0076 + public const int Base_Widget_AppCompat_Button_Borderless = 2131492982; + + // aapt resource value: 0x7f0c0077 + public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131492983; + + // aapt resource value: 0x7f0c00ca + public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131493066; + + // aapt resource value: 0x7f0c0099 + public const int Base_Widget_AppCompat_Button_Colored = 2131493017; + + // aapt resource value: 0x7f0c0078 + public const int Base_Widget_AppCompat_Button_Small = 2131492984; + + // aapt resource value: 0x7f0c0079 + public const int Base_Widget_AppCompat_ButtonBar = 2131492985; + + // aapt resource value: 0x7f0c00cb + public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131493067; + + // aapt resource value: 0x7f0c007a + public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131492986; + + // aapt resource value: 0x7f0c007b + public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131492987; + + // aapt resource value: 0x7f0c00cc + public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131493068; + + // aapt resource value: 0x7f0c000f + public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131492879; + + // aapt resource value: 0x7f0c00cd + public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131493069; + + // aapt resource value: 0x7f0c007c + public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131492988; + + // aapt resource value: 0x7f0c0035 + public const int Base_Widget_AppCompat_EditText = 2131492917; + + // aapt resource value: 0x7f0c007d + public const int Base_Widget_AppCompat_ImageButton = 2131492989; + + // aapt resource value: 0x7f0c00ce + public const int Base_Widget_AppCompat_Light_ActionBar = 2131493070; + + // aapt resource value: 0x7f0c00cf + public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131493071; + + // aapt resource value: 0x7f0c00d0 + public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131493072; + + // aapt resource value: 0x7f0c007e + public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131492990; + + // aapt resource value: 0x7f0c007f + public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131492991; + + // aapt resource value: 0x7f0c0080 + public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131492992; + + // aapt resource value: 0x7f0c0081 + public const int Base_Widget_AppCompat_Light_PopupMenu = 2131492993; + + // aapt resource value: 0x7f0c0082 + public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131492994; + + // aapt resource value: 0x7f0c00d1 + public const int Base_Widget_AppCompat_ListMenuView = 2131493073; + + // aapt resource value: 0x7f0c0083 + public const int Base_Widget_AppCompat_ListPopupWindow = 2131492995; + + // aapt resource value: 0x7f0c0084 + public const int Base_Widget_AppCompat_ListView = 2131492996; + + // aapt resource value: 0x7f0c0085 + public const int Base_Widget_AppCompat_ListView_DropDown = 2131492997; + + // aapt resource value: 0x7f0c0086 + public const int Base_Widget_AppCompat_ListView_Menu = 2131492998; + + // aapt resource value: 0x7f0c0087 + public const int Base_Widget_AppCompat_PopupMenu = 2131492999; + + // aapt resource value: 0x7f0c0088 + public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131493000; + + // aapt resource value: 0x7f0c00d2 + public const int Base_Widget_AppCompat_PopupWindow = 2131493074; + + // aapt resource value: 0x7f0c002b + public const int Base_Widget_AppCompat_ProgressBar = 2131492907; + + // aapt resource value: 0x7f0c002c + public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131492908; + + // aapt resource value: 0x7f0c0089 + public const int Base_Widget_AppCompat_RatingBar = 2131493001; + + // aapt resource value: 0x7f0c009a + public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131493018; + + // aapt resource value: 0x7f0c009b + public const int Base_Widget_AppCompat_RatingBar_Small = 2131493019; + + // aapt resource value: 0x7f0c00d3 + public const int Base_Widget_AppCompat_SearchView = 2131493075; + + // aapt resource value: 0x7f0c00d4 + public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131493076; + + // aapt resource value: 0x7f0c008a + public const int Base_Widget_AppCompat_SeekBar = 2131493002; + + // aapt resource value: 0x7f0c00d5 + public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131493077; + + // aapt resource value: 0x7f0c008b + public const int Base_Widget_AppCompat_Spinner = 2131493003; + + // aapt resource value: 0x7f0c0012 + public const int Base_Widget_AppCompat_Spinner_Underlined = 2131492882; + + // aapt resource value: 0x7f0c008c + public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131493004; + + // aapt resource value: 0x7f0c00a3 + public const int Base_Widget_AppCompat_Toolbar = 2131493027; + + // aapt resource value: 0x7f0c008d + public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131493005; + + // aapt resource value: 0x7f0c016c + public const int Base_Widget_Design_AppBarLayout = 2131493228; + + // aapt resource value: 0x7f0c0170 + public const int Base_Widget_Design_TabLayout = 2131493232; + + // aapt resource value: 0x7f0c000b + public const int CardView = 2131492875; + + // aapt resource value: 0x7f0c000d + public const int CardView_Dark = 2131492877; + + // aapt resource value: 0x7f0c000e + public const int CardView_Light = 2131492878; + + // aapt resource value: 0x7f0c002d + public const int Platform_AppCompat = 2131492909; + + // aapt resource value: 0x7f0c002e + public const int Platform_AppCompat_Light = 2131492910; + + // aapt resource value: 0x7f0c008e + public const int Platform_ThemeOverlay_AppCompat = 2131493006; + + // aapt resource value: 0x7f0c008f + public const int Platform_ThemeOverlay_AppCompat_Dark = 2131493007; + + // aapt resource value: 0x7f0c0090 + public const int Platform_ThemeOverlay_AppCompat_Light = 2131493008; + + // aapt resource value: 0x7f0c002f + public const int Platform_V11_AppCompat = 2131492911; + + // aapt resource value: 0x7f0c0030 + public const int Platform_V11_AppCompat_Light = 2131492912; + + // aapt resource value: 0x7f0c0037 + public const int Platform_V14_AppCompat = 2131492919; + + // aapt resource value: 0x7f0c0038 + public const int Platform_V14_AppCompat_Light = 2131492920; + + // aapt resource value: 0x7f0c0091 + public const int Platform_V21_AppCompat = 2131493009; + + // aapt resource value: 0x7f0c0092 + public const int Platform_V21_AppCompat_Light = 2131493010; + + // aapt resource value: 0x7f0c009e + public const int Platform_V25_AppCompat = 2131493022; + + // aapt resource value: 0x7f0c009f + public const int Platform_V25_AppCompat_Light = 2131493023; + + // aapt resource value: 0x7f0c0031 + public const int Platform_Widget_AppCompat_Spinner = 2131492913; + + // aapt resource value: 0x7f0c003a + public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131492922; + + // aapt resource value: 0x7f0c003b + public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131492923; + + // aapt resource value: 0x7f0c003c + public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131492924; + + // aapt resource value: 0x7f0c003d + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131492925; + + // aapt resource value: 0x7f0c003e + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131492926; + + // aapt resource value: 0x7f0c003f + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131492927; + + // aapt resource value: 0x7f0c0040 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131492928; + + // aapt resource value: 0x7f0c0041 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131492929; + + // aapt resource value: 0x7f0c0042 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131492930; + + // aapt resource value: 0x7f0c0043 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131492931; + + // aapt resource value: 0x7f0c0044 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131492932; + + // aapt resource value: 0x7f0c0045 + public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131492933; + + // aapt resource value: 0x7f0c0046 + public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131492934; + + // aapt resource value: 0x7f0c0047 + public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131492935; + + // aapt resource value: 0x7f0c00d6 + public const int TextAppearance_AppCompat = 2131493078; + + // aapt resource value: 0x7f0c00d7 + public const int TextAppearance_AppCompat_Body1 = 2131493079; + + // aapt resource value: 0x7f0c00d8 + public const int TextAppearance_AppCompat_Body2 = 2131493080; + + // aapt resource value: 0x7f0c00d9 + public const int TextAppearance_AppCompat_Button = 2131493081; + + // aapt resource value: 0x7f0c00da + public const int TextAppearance_AppCompat_Caption = 2131493082; + + // aapt resource value: 0x7f0c00db + public const int TextAppearance_AppCompat_Display1 = 2131493083; + + // aapt resource value: 0x7f0c00dc + public const int TextAppearance_AppCompat_Display2 = 2131493084; + + // aapt resource value: 0x7f0c00dd + public const int TextAppearance_AppCompat_Display3 = 2131493085; + + // aapt resource value: 0x7f0c00de + public const int TextAppearance_AppCompat_Display4 = 2131493086; + + // aapt resource value: 0x7f0c00df + public const int TextAppearance_AppCompat_Headline = 2131493087; + + // aapt resource value: 0x7f0c00e0 + public const int TextAppearance_AppCompat_Inverse = 2131493088; + + // aapt resource value: 0x7f0c00e1 + public const int TextAppearance_AppCompat_Large = 2131493089; + + // aapt resource value: 0x7f0c00e2 + public const int TextAppearance_AppCompat_Large_Inverse = 2131493090; + + // aapt resource value: 0x7f0c00e3 + public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131493091; + + // aapt resource value: 0x7f0c00e4 + public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131493092; + + // aapt resource value: 0x7f0c00e5 + public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131493093; + + // aapt resource value: 0x7f0c00e6 + public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131493094; + + // aapt resource value: 0x7f0c00e7 + public const int TextAppearance_AppCompat_Medium = 2131493095; + + // aapt resource value: 0x7f0c00e8 + public const int TextAppearance_AppCompat_Medium_Inverse = 2131493096; + + // aapt resource value: 0x7f0c00e9 + public const int TextAppearance_AppCompat_Menu = 2131493097; + + // aapt resource value: 0x7f0c00ea + public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131493098; + + // aapt resource value: 0x7f0c00eb + public const int TextAppearance_AppCompat_SearchResult_Title = 2131493099; + + // aapt resource value: 0x7f0c00ec + public const int TextAppearance_AppCompat_Small = 2131493100; + + // aapt resource value: 0x7f0c00ed + public const int TextAppearance_AppCompat_Small_Inverse = 2131493101; + + // aapt resource value: 0x7f0c00ee + public const int TextAppearance_AppCompat_Subhead = 2131493102; + + // aapt resource value: 0x7f0c00ef + public const int TextAppearance_AppCompat_Subhead_Inverse = 2131493103; + + // aapt resource value: 0x7f0c00f0 + public const int TextAppearance_AppCompat_Title = 2131493104; + + // aapt resource value: 0x7f0c00f1 + public const int TextAppearance_AppCompat_Title_Inverse = 2131493105; + + // aapt resource value: 0x7f0c0039 + public const int TextAppearance_AppCompat_Tooltip = 2131492921; + + // aapt resource value: 0x7f0c00f2 + public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131493106; + + // aapt resource value: 0x7f0c00f3 + public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131493107; + + // aapt resource value: 0x7f0c00f4 + public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131493108; + + // aapt resource value: 0x7f0c00f5 + public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131493109; + + // aapt resource value: 0x7f0c00f6 + public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131493110; + + // aapt resource value: 0x7f0c00f7 + public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131493111; + + // aapt resource value: 0x7f0c00f8 + public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131493112; + + // aapt resource value: 0x7f0c00f9 + public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131493113; + + // aapt resource value: 0x7f0c00fa + public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131493114; + + // aapt resource value: 0x7f0c00fb + public const int TextAppearance_AppCompat_Widget_Button = 2131493115; + + // aapt resource value: 0x7f0c00fc + public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131493116; + + // aapt resource value: 0x7f0c00fd + public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131493117; + + // aapt resource value: 0x7f0c00fe + public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131493118; + + // aapt resource value: 0x7f0c00ff + public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131493119; + + // aapt resource value: 0x7f0c0100 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131493120; + + // aapt resource value: 0x7f0c0101 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131493121; + + // aapt resource value: 0x7f0c0102 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131493122; + + // aapt resource value: 0x7f0c0103 + public const int TextAppearance_AppCompat_Widget_Switch = 2131493123; + + // aapt resource value: 0x7f0c0104 + public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131493124; + + // aapt resource value: 0x7f0c0188 + public const int TextAppearance_Compat_Notification = 2131493256; + + // aapt resource value: 0x7f0c0189 + public const int TextAppearance_Compat_Notification_Info = 2131493257; + + // aapt resource value: 0x7f0c0165 + public const int TextAppearance_Compat_Notification_Info_Media = 2131493221; + + // aapt resource value: 0x7f0c018e + public const int TextAppearance_Compat_Notification_Line2 = 2131493262; + + // aapt resource value: 0x7f0c0169 + public const int TextAppearance_Compat_Notification_Line2_Media = 2131493225; + + // aapt resource value: 0x7f0c0166 + public const int TextAppearance_Compat_Notification_Media = 2131493222; + + // aapt resource value: 0x7f0c018a + public const int TextAppearance_Compat_Notification_Time = 2131493258; + + // aapt resource value: 0x7f0c0167 + public const int TextAppearance_Compat_Notification_Time_Media = 2131493223; + + // aapt resource value: 0x7f0c018b + public const int TextAppearance_Compat_Notification_Title = 2131493259; + + // aapt resource value: 0x7f0c0168 + public const int TextAppearance_Compat_Notification_Title_Media = 2131493224; + + // aapt resource value: 0x7f0c0171 + public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131493233; + + // aapt resource value: 0x7f0c0172 + public const int TextAppearance_Design_Counter = 2131493234; + + // aapt resource value: 0x7f0c0173 + public const int TextAppearance_Design_Counter_Overflow = 2131493235; + + // aapt resource value: 0x7f0c0174 + public const int TextAppearance_Design_Error = 2131493236; + + // aapt resource value: 0x7f0c0175 + public const int TextAppearance_Design_Hint = 2131493237; + + // aapt resource value: 0x7f0c0176 + public const int TextAppearance_Design_Snackbar_Message = 2131493238; + + // aapt resource value: 0x7f0c0177 + public const int TextAppearance_Design_Tab = 2131493239; + + // aapt resource value: 0x7f0c0000 + public const int TextAppearance_MediaRouter_PrimaryText = 2131492864; + + // aapt resource value: 0x7f0c0001 + public const int TextAppearance_MediaRouter_SecondaryText = 2131492865; + + // aapt resource value: 0x7f0c0002 + public const int TextAppearance_MediaRouter_Title = 2131492866; + + // aapt resource value: 0x7f0c0105 + public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131493125; + + // aapt resource value: 0x7f0c0106 + public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131493126; + + // aapt resource value: 0x7f0c0107 + public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131493127; + + // aapt resource value: 0x7f0c0108 + public const int Theme_AppCompat = 2131493128; + + // aapt resource value: 0x7f0c0109 + public const int Theme_AppCompat_CompactMenu = 2131493129; + + // aapt resource value: 0x7f0c0013 + public const int Theme_AppCompat_DayNight = 2131492883; + + // aapt resource value: 0x7f0c0014 + public const int Theme_AppCompat_DayNight_DarkActionBar = 2131492884; + + // aapt resource value: 0x7f0c0015 + public const int Theme_AppCompat_DayNight_Dialog = 2131492885; + + // aapt resource value: 0x7f0c0016 + public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131492886; + + // aapt resource value: 0x7f0c0017 + public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131492887; + + // aapt resource value: 0x7f0c0018 + public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131492888; + + // aapt resource value: 0x7f0c0019 + public const int Theme_AppCompat_DayNight_NoActionBar = 2131492889; + + // aapt resource value: 0x7f0c010a + public const int Theme_AppCompat_Dialog = 2131493130; + + // aapt resource value: 0x7f0c010b + public const int Theme_AppCompat_Dialog_Alert = 2131493131; + + // aapt resource value: 0x7f0c010c + public const int Theme_AppCompat_Dialog_MinWidth = 2131493132; + + // aapt resource value: 0x7f0c010d + public const int Theme_AppCompat_DialogWhenLarge = 2131493133; + + // aapt resource value: 0x7f0c010e + public const int Theme_AppCompat_Light = 2131493134; + + // aapt resource value: 0x7f0c010f + public const int Theme_AppCompat_Light_DarkActionBar = 2131493135; + + // aapt resource value: 0x7f0c0110 + public const int Theme_AppCompat_Light_Dialog = 2131493136; + + // aapt resource value: 0x7f0c0111 + public const int Theme_AppCompat_Light_Dialog_Alert = 2131493137; + + // aapt resource value: 0x7f0c0112 + public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131493138; + + // aapt resource value: 0x7f0c0113 + public const int Theme_AppCompat_Light_DialogWhenLarge = 2131493139; + + // aapt resource value: 0x7f0c0114 + public const int Theme_AppCompat_Light_NoActionBar = 2131493140; + + // aapt resource value: 0x7f0c0115 + public const int Theme_AppCompat_NoActionBar = 2131493141; + + // aapt resource value: 0x7f0c0178 + public const int Theme_Design = 2131493240; + + // aapt resource value: 0x7f0c0179 + public const int Theme_Design_BottomSheetDialog = 2131493241; + + // aapt resource value: 0x7f0c017a + public const int Theme_Design_Light = 2131493242; + + // aapt resource value: 0x7f0c017b + public const int Theme_Design_Light_BottomSheetDialog = 2131493243; + + // aapt resource value: 0x7f0c017c + public const int Theme_Design_Light_NoActionBar = 2131493244; + + // aapt resource value: 0x7f0c017d + public const int Theme_Design_NoActionBar = 2131493245; + + // aapt resource value: 0x7f0c0003 + public const int Theme_MediaRouter = 2131492867; + + // aapt resource value: 0x7f0c0004 + public const int Theme_MediaRouter_Light = 2131492868; + + // aapt resource value: 0x7f0c0005 + public const int Theme_MediaRouter_Light_DarkControlPanel = 2131492869; + + // aapt resource value: 0x7f0c0006 + public const int Theme_MediaRouter_LightControlPanel = 2131492870; + + // aapt resource value: 0x7f0c0116 + public const int ThemeOverlay_AppCompat = 2131493142; + + // aapt resource value: 0x7f0c0117 + public const int ThemeOverlay_AppCompat_ActionBar = 2131493143; + + // aapt resource value: 0x7f0c0118 + public const int ThemeOverlay_AppCompat_Dark = 2131493144; + + // aapt resource value: 0x7f0c0119 + public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131493145; + + // aapt resource value: 0x7f0c011a + public const int ThemeOverlay_AppCompat_Dialog = 2131493146; + + // aapt resource value: 0x7f0c011b + public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131493147; + + // aapt resource value: 0x7f0c011c + public const int ThemeOverlay_AppCompat_Light = 2131493148; + + // aapt resource value: 0x7f0c0007 + public const int ThemeOverlay_MediaRouter_Dark = 2131492871; + + // aapt resource value: 0x7f0c0008 + public const int ThemeOverlay_MediaRouter_Light = 2131492872; + + // aapt resource value: 0x7f0c011d + public const int Widget_AppCompat_ActionBar = 2131493149; + + // aapt resource value: 0x7f0c011e + public const int Widget_AppCompat_ActionBar_Solid = 2131493150; + + // aapt resource value: 0x7f0c011f + public const int Widget_AppCompat_ActionBar_TabBar = 2131493151; + + // aapt resource value: 0x7f0c0120 + public const int Widget_AppCompat_ActionBar_TabText = 2131493152; + + // aapt resource value: 0x7f0c0121 + public const int Widget_AppCompat_ActionBar_TabView = 2131493153; + + // aapt resource value: 0x7f0c0122 + public const int Widget_AppCompat_ActionButton = 2131493154; + + // aapt resource value: 0x7f0c0123 + public const int Widget_AppCompat_ActionButton_CloseMode = 2131493155; + + // aapt resource value: 0x7f0c0124 + public const int Widget_AppCompat_ActionButton_Overflow = 2131493156; + + // aapt resource value: 0x7f0c0125 + public const int Widget_AppCompat_ActionMode = 2131493157; + + // aapt resource value: 0x7f0c0126 + public const int Widget_AppCompat_ActivityChooserView = 2131493158; + + // aapt resource value: 0x7f0c0127 + public const int Widget_AppCompat_AutoCompleteTextView = 2131493159; + + // aapt resource value: 0x7f0c0128 + public const int Widget_AppCompat_Button = 2131493160; + + // aapt resource value: 0x7f0c0129 + public const int Widget_AppCompat_Button_Borderless = 2131493161; + + // aapt resource value: 0x7f0c012a + public const int Widget_AppCompat_Button_Borderless_Colored = 2131493162; + + // aapt resource value: 0x7f0c012b + public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131493163; + + // aapt resource value: 0x7f0c012c + public const int Widget_AppCompat_Button_Colored = 2131493164; + + // aapt resource value: 0x7f0c012d + public const int Widget_AppCompat_Button_Small = 2131493165; + + // aapt resource value: 0x7f0c012e + public const int Widget_AppCompat_ButtonBar = 2131493166; + + // aapt resource value: 0x7f0c012f + public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131493167; + + // aapt resource value: 0x7f0c0130 + public const int Widget_AppCompat_CompoundButton_CheckBox = 2131493168; + + // aapt resource value: 0x7f0c0131 + public const int Widget_AppCompat_CompoundButton_RadioButton = 2131493169; + + // aapt resource value: 0x7f0c0132 + public const int Widget_AppCompat_CompoundButton_Switch = 2131493170; + + // aapt resource value: 0x7f0c0133 + public const int Widget_AppCompat_DrawerArrowToggle = 2131493171; + + // aapt resource value: 0x7f0c0134 + public const int Widget_AppCompat_DropDownItem_Spinner = 2131493172; + + // aapt resource value: 0x7f0c0135 + public const int Widget_AppCompat_EditText = 2131493173; + + // aapt resource value: 0x7f0c0136 + public const int Widget_AppCompat_ImageButton = 2131493174; + + // aapt resource value: 0x7f0c0137 + public const int Widget_AppCompat_Light_ActionBar = 2131493175; + + // aapt resource value: 0x7f0c0138 + public const int Widget_AppCompat_Light_ActionBar_Solid = 2131493176; + + // aapt resource value: 0x7f0c0139 + public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131493177; + + // aapt resource value: 0x7f0c013a + public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131493178; + + // aapt resource value: 0x7f0c013b + public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131493179; + + // aapt resource value: 0x7f0c013c + public const int Widget_AppCompat_Light_ActionBar_TabText = 2131493180; + + // aapt resource value: 0x7f0c013d + public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131493181; + + // aapt resource value: 0x7f0c013e + public const int Widget_AppCompat_Light_ActionBar_TabView = 2131493182; + + // aapt resource value: 0x7f0c013f + public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131493183; + + // aapt resource value: 0x7f0c0140 + public const int Widget_AppCompat_Light_ActionButton = 2131493184; + + // aapt resource value: 0x7f0c0141 + public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131493185; + + // aapt resource value: 0x7f0c0142 + public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131493186; + + // aapt resource value: 0x7f0c0143 + public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131493187; + + // aapt resource value: 0x7f0c0144 + public const int Widget_AppCompat_Light_ActivityChooserView = 2131493188; + + // aapt resource value: 0x7f0c0145 + public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131493189; + + // aapt resource value: 0x7f0c0146 + public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131493190; + + // aapt resource value: 0x7f0c0147 + public const int Widget_AppCompat_Light_ListPopupWindow = 2131493191; + + // aapt resource value: 0x7f0c0148 + public const int Widget_AppCompat_Light_ListView_DropDown = 2131493192; + + // aapt resource value: 0x7f0c0149 + public const int Widget_AppCompat_Light_PopupMenu = 2131493193; + + // aapt resource value: 0x7f0c014a + public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131493194; + + // aapt resource value: 0x7f0c014b + public const int Widget_AppCompat_Light_SearchView = 2131493195; + + // aapt resource value: 0x7f0c014c + public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131493196; + + // aapt resource value: 0x7f0c014d + public const int Widget_AppCompat_ListMenuView = 2131493197; + + // aapt resource value: 0x7f0c014e + public const int Widget_AppCompat_ListPopupWindow = 2131493198; + + // aapt resource value: 0x7f0c014f + public const int Widget_AppCompat_ListView = 2131493199; + + // aapt resource value: 0x7f0c0150 + public const int Widget_AppCompat_ListView_DropDown = 2131493200; + + // aapt resource value: 0x7f0c0151 + public const int Widget_AppCompat_ListView_Menu = 2131493201; + + // aapt resource value: 0x7f0c0152 + public const int Widget_AppCompat_PopupMenu = 2131493202; + + // aapt resource value: 0x7f0c0153 + public const int Widget_AppCompat_PopupMenu_Overflow = 2131493203; + + // aapt resource value: 0x7f0c0154 + public const int Widget_AppCompat_PopupWindow = 2131493204; + + // aapt resource value: 0x7f0c0155 + public const int Widget_AppCompat_ProgressBar = 2131493205; + + // aapt resource value: 0x7f0c0156 + public const int Widget_AppCompat_ProgressBar_Horizontal = 2131493206; + + // aapt resource value: 0x7f0c0157 + public const int Widget_AppCompat_RatingBar = 2131493207; + + // aapt resource value: 0x7f0c0158 + public const int Widget_AppCompat_RatingBar_Indicator = 2131493208; + + // aapt resource value: 0x7f0c0159 + public const int Widget_AppCompat_RatingBar_Small = 2131493209; + + // aapt resource value: 0x7f0c015a + public const int Widget_AppCompat_SearchView = 2131493210; + + // aapt resource value: 0x7f0c015b + public const int Widget_AppCompat_SearchView_ActionBar = 2131493211; + + // aapt resource value: 0x7f0c015c + public const int Widget_AppCompat_SeekBar = 2131493212; + + // aapt resource value: 0x7f0c015d + public const int Widget_AppCompat_SeekBar_Discrete = 2131493213; + + // aapt resource value: 0x7f0c015e + public const int Widget_AppCompat_Spinner = 2131493214; + + // aapt resource value: 0x7f0c015f + public const int Widget_AppCompat_Spinner_DropDown = 2131493215; + + // aapt resource value: 0x7f0c0160 + public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131493216; + + // aapt resource value: 0x7f0c0161 + public const int Widget_AppCompat_Spinner_Underlined = 2131493217; + + // aapt resource value: 0x7f0c0162 + public const int Widget_AppCompat_TextView_SpinnerItem = 2131493218; + + // aapt resource value: 0x7f0c0163 + public const int Widget_AppCompat_Toolbar = 2131493219; + + // aapt resource value: 0x7f0c0164 + public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131493220; + + // aapt resource value: 0x7f0c018c + public const int Widget_Compat_NotificationActionContainer = 2131493260; + + // aapt resource value: 0x7f0c018d + public const int Widget_Compat_NotificationActionText = 2131493261; + + // aapt resource value: 0x7f0c017e + public const int Widget_Design_AppBarLayout = 2131493246; + + // aapt resource value: 0x7f0c017f + public const int Widget_Design_BottomNavigationView = 2131493247; + + // aapt resource value: 0x7f0c0180 + public const int Widget_Design_BottomSheet_Modal = 2131493248; + + // aapt resource value: 0x7f0c0181 + public const int Widget_Design_CollapsingToolbar = 2131493249; + + // aapt resource value: 0x7f0c0182 + public const int Widget_Design_CoordinatorLayout = 2131493250; + + // aapt resource value: 0x7f0c0183 + public const int Widget_Design_FloatingActionButton = 2131493251; + + // aapt resource value: 0x7f0c0184 + public const int Widget_Design_NavigationView = 2131493252; + + // aapt resource value: 0x7f0c0185 + public const int Widget_Design_ScrimInsetsFrameLayout = 2131493253; + + // aapt resource value: 0x7f0c0186 + public const int Widget_Design_Snackbar = 2131493254; + + // aapt resource value: 0x7f0c016a + public const int Widget_Design_TabLayout = 2131493226; + + // aapt resource value: 0x7f0c0187 + public const int Widget_Design_TextInputLayout = 2131493255; + + // aapt resource value: 0x7f0c0009 + public const int Widget_MediaRouter_Light_MediaRouteButton = 2131492873; + + // aapt resource value: 0x7f0c000a + public const int Widget_MediaRouter_MediaRouteButton = 2131492874; + + static Style() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Style() + { + } + } + + public partial class Styleable + { + + public static int[] ActionBar = new int[] { + 2130772003, + 2130772005, + 2130772006, + 2130772007, + 2130772008, + 2130772009, + 2130772010, + 2130772011, + 2130772012, + 2130772013, + 2130772014, + 2130772015, + 2130772016, + 2130772017, + 2130772018, + 2130772019, + 2130772020, + 2130772021, + 2130772022, + 2130772023, + 2130772024, + 2130772025, + 2130772026, + 2130772027, + 2130772028, + 2130772029, + 2130772030, + 2130772031, + 2130772101}; + + // aapt resource value: 10 + public const int ActionBar_background = 10; + + // aapt resource value: 12 + public const int ActionBar_backgroundSplit = 12; + + // aapt resource value: 11 + public const int ActionBar_backgroundStacked = 11; + + // aapt resource value: 21 + public const int ActionBar_contentInsetEnd = 21; + + // aapt resource value: 25 + public const int ActionBar_contentInsetEndWithActions = 25; + + // aapt resource value: 22 + public const int ActionBar_contentInsetLeft = 22; + + // aapt resource value: 23 + public const int ActionBar_contentInsetRight = 23; + + // aapt resource value: 20 + public const int ActionBar_contentInsetStart = 20; + + // aapt resource value: 24 + public const int ActionBar_contentInsetStartWithNavigation = 24; + + // aapt resource value: 13 + public const int ActionBar_customNavigationLayout = 13; + + // aapt resource value: 3 + public const int ActionBar_displayOptions = 3; + + // aapt resource value: 9 + public const int ActionBar_divider = 9; + + // aapt resource value: 26 + public const int ActionBar_elevation = 26; + + // aapt resource value: 0 + public const int ActionBar_height = 0; + + // aapt resource value: 19 + public const int ActionBar_hideOnContentScroll = 19; + + // aapt resource value: 28 + public const int ActionBar_homeAsUpIndicator = 28; + + // aapt resource value: 14 + public const int ActionBar_homeLayout = 14; + + // aapt resource value: 7 + public const int ActionBar_icon = 7; + + // aapt resource value: 16 + public const int ActionBar_indeterminateProgressStyle = 16; + + // aapt resource value: 18 + public const int ActionBar_itemPadding = 18; + + // aapt resource value: 8 + public const int ActionBar_logo = 8; + + // aapt resource value: 2 + public const int ActionBar_navigationMode = 2; + + // aapt resource value: 27 + public const int ActionBar_popupTheme = 27; + + // aapt resource value: 17 + public const int ActionBar_progressBarPadding = 17; + + // aapt resource value: 15 + public const int ActionBar_progressBarStyle = 15; + + // aapt resource value: 4 + public const int ActionBar_subtitle = 4; + + // aapt resource value: 6 + public const int ActionBar_subtitleTextStyle = 6; + + // aapt resource value: 1 + public const int ActionBar_title = 1; + + // aapt resource value: 5 + public const int ActionBar_titleTextStyle = 5; + + public static int[] ActionBarLayout = new int[] { + 16842931}; + + // aapt resource value: 0 + public const int ActionBarLayout_android_layout_gravity = 0; + + public static int[] ActionMenuItemView = new int[] { + 16843071}; + + // aapt resource value: 0 + public const int ActionMenuItemView_android_minWidth = 0; + + public static int[] ActionMenuView; + + public static int[] ActionMode = new int[] { + 2130772003, + 2130772009, + 2130772010, + 2130772014, + 2130772016, + 2130772032}; + + // aapt resource value: 3 + public const int ActionMode_background = 3; + + // aapt resource value: 4 + public const int ActionMode_backgroundSplit = 4; + + // aapt resource value: 5 + public const int ActionMode_closeItemLayout = 5; + + // aapt resource value: 0 + public const int ActionMode_height = 0; + + // aapt resource value: 2 + public const int ActionMode_subtitleTextStyle = 2; + + // aapt resource value: 1 + public const int ActionMode_titleTextStyle = 1; + + public static int[] ActivityChooserView = new int[] { + 2130772033, + 2130772034}; + + // aapt resource value: 1 + public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 1; + + // aapt resource value: 0 + public const int ActivityChooserView_initialActivityCount = 0; + + public static int[] AlertDialog = new int[] { + 16842994, + 2130772035, + 2130772036, + 2130772037, + 2130772038, + 2130772039, + 2130772040}; + + // aapt resource value: 0 + public const int AlertDialog_android_layout = 0; + + // aapt resource value: 1 + public const int AlertDialog_buttonPanelSideLayout = 1; + + // aapt resource value: 5 + public const int AlertDialog_listItemLayout = 5; + + // aapt resource value: 2 + public const int AlertDialog_listLayout = 2; + + // aapt resource value: 3 + public const int AlertDialog_multiChoiceItemLayout = 3; + + // aapt resource value: 6 + public const int AlertDialog_showTitle = 6; + + // aapt resource value: 4 + public const int AlertDialog_singleChoiceItemLayout = 4; + + public static int[] AppBarLayout = new int[] { + 16842964, + 16843919, + 16844096, + 2130772030, + 2130772248}; + + // aapt resource value: 0 + public const int AppBarLayout_android_background = 0; + + // aapt resource value: 2 + public const int AppBarLayout_android_keyboardNavigationCluster = 2; + + // aapt resource value: 1 + public const int AppBarLayout_android_touchscreenBlocksFocus = 1; + + // aapt resource value: 3 + public const int AppBarLayout_elevation = 3; + + // aapt resource value: 4 + public const int AppBarLayout_expanded = 4; + + public static int[] AppBarLayoutStates = new int[] { + 2130772249, + 2130772250}; + + // aapt resource value: 0 + public const int AppBarLayoutStates_state_collapsed = 0; + + // aapt resource value: 1 + public const int AppBarLayoutStates_state_collapsible = 1; + + public static int[] AppBarLayout_Layout = new int[] { + 2130772251, + 2130772252}; + + // aapt resource value: 0 + public const int AppBarLayout_Layout_layout_scrollFlags = 0; + + // aapt resource value: 1 + public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; + + public static int[] AppCompatImageView = new int[] { + 16843033, + 2130772041, + 2130772042, + 2130772043}; + + // aapt resource value: 0 + public const int AppCompatImageView_android_src = 0; + + // aapt resource value: 1 + public const int AppCompatImageView_srcCompat = 1; + + // aapt resource value: 2 + public const int AppCompatImageView_tint = 2; + + // aapt resource value: 3 + public const int AppCompatImageView_tintMode = 3; + + public static int[] AppCompatSeekBar = new int[] { + 16843074, + 2130772044, + 2130772045, + 2130772046}; + + // aapt resource value: 0 + public const int AppCompatSeekBar_android_thumb = 0; + + // aapt resource value: 1 + public const int AppCompatSeekBar_tickMark = 1; + + // aapt resource value: 2 + public const int AppCompatSeekBar_tickMarkTint = 2; + + // aapt resource value: 3 + public const int AppCompatSeekBar_tickMarkTintMode = 3; + + public static int[] AppCompatTextHelper = new int[] { + 16842804, + 16843117, + 16843118, + 16843119, + 16843120, + 16843666, + 16843667}; + + // aapt resource value: 2 + public const int AppCompatTextHelper_android_drawableBottom = 2; + + // aapt resource value: 6 + public const int AppCompatTextHelper_android_drawableEnd = 6; + + // aapt resource value: 3 + public const int AppCompatTextHelper_android_drawableLeft = 3; + + // aapt resource value: 4 + public const int AppCompatTextHelper_android_drawableRight = 4; + + // aapt resource value: 5 + public const int AppCompatTextHelper_android_drawableStart = 5; + + // aapt resource value: 1 + public const int AppCompatTextHelper_android_drawableTop = 1; + + // aapt resource value: 0 + public const int AppCompatTextHelper_android_textAppearance = 0; + + public static int[] AppCompatTextView = new int[] { + 16842804, + 2130772047, + 2130772048, + 2130772049, + 2130772050, + 2130772051, + 2130772052, + 2130772053}; + + // aapt resource value: 0 + public const int AppCompatTextView_android_textAppearance = 0; + + // aapt resource value: 6 + public const int AppCompatTextView_autoSizeMaxTextSize = 6; + + // aapt resource value: 5 + public const int AppCompatTextView_autoSizeMinTextSize = 5; + + // aapt resource value: 4 + public const int AppCompatTextView_autoSizePresetSizes = 4; + + // aapt resource value: 3 + public const int AppCompatTextView_autoSizeStepGranularity = 3; + + // aapt resource value: 2 + public const int AppCompatTextView_autoSizeTextType = 2; + + // aapt resource value: 7 + public const int AppCompatTextView_fontFamily = 7; + + // aapt resource value: 1 + public const int AppCompatTextView_textAllCaps = 1; + + public static int[] AppCompatTheme = new int[] { + 16842839, + 16842926, + 2130772054, + 2130772055, + 2130772056, + 2130772057, + 2130772058, + 2130772059, + 2130772060, + 2130772061, + 2130772062, + 2130772063, + 2130772064, + 2130772065, + 2130772066, + 2130772067, + 2130772068, + 2130772069, + 2130772070, + 2130772071, + 2130772072, + 2130772073, + 2130772074, + 2130772075, + 2130772076, + 2130772077, + 2130772078, + 2130772079, + 2130772080, + 2130772081, + 2130772082, + 2130772083, + 2130772084, + 2130772085, + 2130772086, + 2130772087, + 2130772088, + 2130772089, + 2130772090, + 2130772091, + 2130772092, + 2130772093, + 2130772094, + 2130772095, + 2130772096, + 2130772097, + 2130772098, + 2130772099, + 2130772100, + 2130772101, + 2130772102, + 2130772103, + 2130772104, + 2130772105, + 2130772106, + 2130772107, + 2130772108, + 2130772109, + 2130772110, + 2130772111, + 2130772112, + 2130772113, + 2130772114, + 2130772115, + 2130772116, + 2130772117, + 2130772118, + 2130772119, + 2130772120, + 2130772121, + 2130772122, + 2130772123, + 2130772124, + 2130772125, + 2130772126, + 2130772127, + 2130772128, + 2130772129, + 2130772130, + 2130772131, + 2130772132, + 2130772133, + 2130772134, + 2130772135, + 2130772136, + 2130772137, + 2130772138, + 2130772139, + 2130772140, + 2130772141, + 2130772142, + 2130772143, + 2130772144, + 2130772145, + 2130772146, + 2130772147, + 2130772148, + 2130772149, + 2130772150, + 2130772151, + 2130772152, + 2130772153, + 2130772154, + 2130772155, + 2130772156, + 2130772157, + 2130772158, + 2130772159, + 2130772160, + 2130772161, + 2130772162, + 2130772163, + 2130772164, + 2130772165, + 2130772166, + 2130772167, + 2130772168, + 2130772169, + 2130772170}; + + // aapt resource value: 23 + public const int AppCompatTheme_actionBarDivider = 23; + + // aapt resource value: 24 + public const int AppCompatTheme_actionBarItemBackground = 24; + + // aapt resource value: 17 + public const int AppCompatTheme_actionBarPopupTheme = 17; + + // aapt resource value: 22 + public const int AppCompatTheme_actionBarSize = 22; + + // aapt resource value: 19 + public const int AppCompatTheme_actionBarSplitStyle = 19; + + // aapt resource value: 18 + public const int AppCompatTheme_actionBarStyle = 18; + + // aapt resource value: 13 + public const int AppCompatTheme_actionBarTabBarStyle = 13; + + // aapt resource value: 12 + public const int AppCompatTheme_actionBarTabStyle = 12; + + // aapt resource value: 14 + public const int AppCompatTheme_actionBarTabTextStyle = 14; + + // aapt resource value: 20 + public const int AppCompatTheme_actionBarTheme = 20; + + // aapt resource value: 21 + public const int AppCompatTheme_actionBarWidgetTheme = 21; + + // aapt resource value: 50 + public const int AppCompatTheme_actionButtonStyle = 50; + + // aapt resource value: 46 + public const int AppCompatTheme_actionDropDownStyle = 46; + + // aapt resource value: 25 + public const int AppCompatTheme_actionMenuTextAppearance = 25; + + // aapt resource value: 26 + public const int AppCompatTheme_actionMenuTextColor = 26; + + // aapt resource value: 29 + public const int AppCompatTheme_actionModeBackground = 29; + + // aapt resource value: 28 + public const int AppCompatTheme_actionModeCloseButtonStyle = 28; + + // aapt resource value: 31 + public const int AppCompatTheme_actionModeCloseDrawable = 31; + + // aapt resource value: 33 + public const int AppCompatTheme_actionModeCopyDrawable = 33; + + // aapt resource value: 32 + public const int AppCompatTheme_actionModeCutDrawable = 32; + + // aapt resource value: 37 + public const int AppCompatTheme_actionModeFindDrawable = 37; + + // aapt resource value: 34 + public const int AppCompatTheme_actionModePasteDrawable = 34; + + // aapt resource value: 39 + public const int AppCompatTheme_actionModePopupWindowStyle = 39; + + // aapt resource value: 35 + public const int AppCompatTheme_actionModeSelectAllDrawable = 35; + + // aapt resource value: 36 + public const int AppCompatTheme_actionModeShareDrawable = 36; + + // aapt resource value: 30 + public const int AppCompatTheme_actionModeSplitBackground = 30; + + // aapt resource value: 27 + public const int AppCompatTheme_actionModeStyle = 27; + + // aapt resource value: 38 + public const int AppCompatTheme_actionModeWebSearchDrawable = 38; + + // aapt resource value: 15 + public const int AppCompatTheme_actionOverflowButtonStyle = 15; + + // aapt resource value: 16 + public const int AppCompatTheme_actionOverflowMenuStyle = 16; + + // aapt resource value: 58 + public const int AppCompatTheme_activityChooserViewStyle = 58; + + // aapt resource value: 95 + public const int AppCompatTheme_alertDialogButtonGroupStyle = 95; + + // aapt resource value: 96 + public const int AppCompatTheme_alertDialogCenterButtons = 96; + + // aapt resource value: 94 + public const int AppCompatTheme_alertDialogStyle = 94; + + // aapt resource value: 97 + public const int AppCompatTheme_alertDialogTheme = 97; + + // aapt resource value: 1 + public const int AppCompatTheme_android_windowAnimationStyle = 1; + + // aapt resource value: 0 + public const int AppCompatTheme_android_windowIsFloating = 0; + + // aapt resource value: 102 + public const int AppCompatTheme_autoCompleteTextViewStyle = 102; + + // aapt resource value: 55 + public const int AppCompatTheme_borderlessButtonStyle = 55; + + // aapt resource value: 52 + public const int AppCompatTheme_buttonBarButtonStyle = 52; + + // aapt resource value: 100 + public const int AppCompatTheme_buttonBarNegativeButtonStyle = 100; + + // aapt resource value: 101 + public const int AppCompatTheme_buttonBarNeutralButtonStyle = 101; + + // aapt resource value: 99 + public const int AppCompatTheme_buttonBarPositiveButtonStyle = 99; + + // aapt resource value: 51 + public const int AppCompatTheme_buttonBarStyle = 51; + + // aapt resource value: 103 + public const int AppCompatTheme_buttonStyle = 103; + + // aapt resource value: 104 + public const int AppCompatTheme_buttonStyleSmall = 104; + + // aapt resource value: 105 + public const int AppCompatTheme_checkboxStyle = 105; + + // aapt resource value: 106 + public const int AppCompatTheme_checkedTextViewStyle = 106; + + // aapt resource value: 86 + public const int AppCompatTheme_colorAccent = 86; + + // aapt resource value: 93 + public const int AppCompatTheme_colorBackgroundFloating = 93; + + // aapt resource value: 90 + public const int AppCompatTheme_colorButtonNormal = 90; + + // aapt resource value: 88 + public const int AppCompatTheme_colorControlActivated = 88; + + // aapt resource value: 89 + public const int AppCompatTheme_colorControlHighlight = 89; + + // aapt resource value: 87 + public const int AppCompatTheme_colorControlNormal = 87; + + // aapt resource value: 118 + public const int AppCompatTheme_colorError = 118; + + // aapt resource value: 84 + public const int AppCompatTheme_colorPrimary = 84; + + // aapt resource value: 85 + public const int AppCompatTheme_colorPrimaryDark = 85; + + // aapt resource value: 91 + public const int AppCompatTheme_colorSwitchThumbNormal = 91; + + // aapt resource value: 92 + public const int AppCompatTheme_controlBackground = 92; + + // aapt resource value: 44 + public const int AppCompatTheme_dialogPreferredPadding = 44; + + // aapt resource value: 43 + public const int AppCompatTheme_dialogTheme = 43; + + // aapt resource value: 57 + public const int AppCompatTheme_dividerHorizontal = 57; + + // aapt resource value: 56 + public const int AppCompatTheme_dividerVertical = 56; + + // aapt resource value: 75 + public const int AppCompatTheme_dropDownListViewStyle = 75; + + // aapt resource value: 47 + public const int AppCompatTheme_dropdownListPreferredItemHeight = 47; + + // aapt resource value: 64 + public const int AppCompatTheme_editTextBackground = 64; + + // aapt resource value: 63 + public const int AppCompatTheme_editTextColor = 63; + + // aapt resource value: 107 + public const int AppCompatTheme_editTextStyle = 107; + + // aapt resource value: 49 + public const int AppCompatTheme_homeAsUpIndicator = 49; + + // aapt resource value: 65 + public const int AppCompatTheme_imageButtonStyle = 65; + + // aapt resource value: 83 + public const int AppCompatTheme_listChoiceBackgroundIndicator = 83; + + // aapt resource value: 45 + public const int AppCompatTheme_listDividerAlertDialog = 45; + + // aapt resource value: 115 + public const int AppCompatTheme_listMenuViewStyle = 115; + + // aapt resource value: 76 + public const int AppCompatTheme_listPopupWindowStyle = 76; + + // aapt resource value: 70 + public const int AppCompatTheme_listPreferredItemHeight = 70; + + // aapt resource value: 72 + public const int AppCompatTheme_listPreferredItemHeightLarge = 72; + + // aapt resource value: 71 + public const int AppCompatTheme_listPreferredItemHeightSmall = 71; + + // aapt resource value: 73 + public const int AppCompatTheme_listPreferredItemPaddingLeft = 73; + + // aapt resource value: 74 + public const int AppCompatTheme_listPreferredItemPaddingRight = 74; + + // aapt resource value: 80 + public const int AppCompatTheme_panelBackground = 80; + + // aapt resource value: 82 + public const int AppCompatTheme_panelMenuListTheme = 82; + + // aapt resource value: 81 + public const int AppCompatTheme_panelMenuListWidth = 81; + + // aapt resource value: 61 + public const int AppCompatTheme_popupMenuStyle = 61; + + // aapt resource value: 62 + public const int AppCompatTheme_popupWindowStyle = 62; + + // aapt resource value: 108 + public const int AppCompatTheme_radioButtonStyle = 108; + + // aapt resource value: 109 + public const int AppCompatTheme_ratingBarStyle = 109; + + // aapt resource value: 110 + public const int AppCompatTheme_ratingBarStyleIndicator = 110; + + // aapt resource value: 111 + public const int AppCompatTheme_ratingBarStyleSmall = 111; + + // aapt resource value: 69 + public const int AppCompatTheme_searchViewStyle = 69; + + // aapt resource value: 112 + public const int AppCompatTheme_seekBarStyle = 112; + + // aapt resource value: 53 + public const int AppCompatTheme_selectableItemBackground = 53; + + // aapt resource value: 54 + public const int AppCompatTheme_selectableItemBackgroundBorderless = 54; + + // aapt resource value: 48 + public const int AppCompatTheme_spinnerDropDownItemStyle = 48; + + // aapt resource value: 113 + public const int AppCompatTheme_spinnerStyle = 113; + + // aapt resource value: 114 + public const int AppCompatTheme_switchStyle = 114; + + // aapt resource value: 40 + public const int AppCompatTheme_textAppearanceLargePopupMenu = 40; + + // aapt resource value: 77 + public const int AppCompatTheme_textAppearanceListItem = 77; + + // aapt resource value: 78 + public const int AppCompatTheme_textAppearanceListItemSecondary = 78; + + // aapt resource value: 79 + public const int AppCompatTheme_textAppearanceListItemSmall = 79; + + // aapt resource value: 42 + public const int AppCompatTheme_textAppearancePopupMenuHeader = 42; + + // aapt resource value: 67 + public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 67; + + // aapt resource value: 66 + public const int AppCompatTheme_textAppearanceSearchResultTitle = 66; + + // aapt resource value: 41 + public const int AppCompatTheme_textAppearanceSmallPopupMenu = 41; + + // aapt resource value: 98 + public const int AppCompatTheme_textColorAlertDialogListItem = 98; + + // aapt resource value: 68 + public const int AppCompatTheme_textColorSearchUrl = 68; + + // aapt resource value: 60 + public const int AppCompatTheme_toolbarNavigationButtonStyle = 60; + + // aapt resource value: 59 + public const int AppCompatTheme_toolbarStyle = 59; + + // aapt resource value: 117 + public const int AppCompatTheme_tooltipForegroundColor = 117; + + // aapt resource value: 116 + public const int AppCompatTheme_tooltipFrameBackground = 116; + + // aapt resource value: 2 + public const int AppCompatTheme_windowActionBar = 2; + + // aapt resource value: 4 + public const int AppCompatTheme_windowActionBarOverlay = 4; + + // aapt resource value: 5 + public const int AppCompatTheme_windowActionModeOverlay = 5; + + // aapt resource value: 9 + public const int AppCompatTheme_windowFixedHeightMajor = 9; + + // aapt resource value: 7 + public const int AppCompatTheme_windowFixedHeightMinor = 7; + + // aapt resource value: 6 + public const int AppCompatTheme_windowFixedWidthMajor = 6; + + // aapt resource value: 8 + public const int AppCompatTheme_windowFixedWidthMinor = 8; + + // aapt resource value: 10 + public const int AppCompatTheme_windowMinWidthMajor = 10; + + // aapt resource value: 11 + public const int AppCompatTheme_windowMinWidthMinor = 11; + + // aapt resource value: 3 + public const int AppCompatTheme_windowNoTitle = 3; + + public static int[] BottomNavigationView = new int[] { + 2130772030, + 2130772291, + 2130772292, + 2130772293, + 2130772294}; + + // aapt resource value: 0 + public const int BottomNavigationView_elevation = 0; + + // aapt resource value: 4 + public const int BottomNavigationView_itemBackground = 4; + + // aapt resource value: 2 + public const int BottomNavigationView_itemIconTint = 2; + + // aapt resource value: 3 + public const int BottomNavigationView_itemTextColor = 3; + + // aapt resource value: 1 + public const int BottomNavigationView_menu = 1; + + public static int[] BottomSheetBehavior_Layout = new int[] { + 2130772253, + 2130772254, + 2130772255}; + + // aapt resource value: 1 + public const int BottomSheetBehavior_Layout_behavior_hideable = 1; + + // aapt resource value: 0 + public const int BottomSheetBehavior_Layout_behavior_peekHeight = 0; + + // aapt resource value: 2 + public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; + + public static int[] ButtonBarLayout = new int[] { + 2130772171}; + + // aapt resource value: 0 + public const int ButtonBarLayout_allowStacking = 0; + + public static int[] CardView = new int[] { + 16843071, + 16843072, + 2130771991, + 2130771992, + 2130771993, + 2130771994, + 2130771995, + 2130771996, + 2130771997, + 2130771998, + 2130771999, + 2130772000, + 2130772001}; + + // aapt resource value: 1 + public const int CardView_android_minHeight = 1; + + // aapt resource value: 0 + public const int CardView_android_minWidth = 0; + + // aapt resource value: 2 + public const int CardView_cardBackgroundColor = 2; + + // aapt resource value: 3 + public const int CardView_cardCornerRadius = 3; + + // aapt resource value: 4 + public const int CardView_cardElevation = 4; + + // aapt resource value: 5 + public const int CardView_cardMaxElevation = 5; + + // aapt resource value: 7 + public const int CardView_cardPreventCornerOverlap = 7; + + // aapt resource value: 6 + public const int CardView_cardUseCompatPadding = 6; + + // aapt resource value: 8 + public const int CardView_contentPadding = 8; + + // aapt resource value: 12 + public const int CardView_contentPaddingBottom = 12; + + // aapt resource value: 9 + public const int CardView_contentPaddingLeft = 9; + + // aapt resource value: 10 + public const int CardView_contentPaddingRight = 10; + + // aapt resource value: 11 + public const int CardView_contentPaddingTop = 11; + + public static int[] CollapsingToolbarLayout = new int[] { + 2130772005, + 2130772256, + 2130772257, + 2130772258, + 2130772259, + 2130772260, + 2130772261, + 2130772262, + 2130772263, + 2130772264, + 2130772265, + 2130772266, + 2130772267, + 2130772268, + 2130772269, + 2130772270}; + + // aapt resource value: 13 + public const int CollapsingToolbarLayout_collapsedTitleGravity = 13; + + // aapt resource value: 7 + public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 7; + + // aapt resource value: 8 + public const int CollapsingToolbarLayout_contentScrim = 8; + + // aapt resource value: 14 + public const int CollapsingToolbarLayout_expandedTitleGravity = 14; + + // aapt resource value: 1 + public const int CollapsingToolbarLayout_expandedTitleMargin = 1; + + // aapt resource value: 5 + public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; + + // aapt resource value: 4 + public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 4; + + // aapt resource value: 2 + public const int CollapsingToolbarLayout_expandedTitleMarginStart = 2; + + // aapt resource value: 3 + public const int CollapsingToolbarLayout_expandedTitleMarginTop = 3; + + // aapt resource value: 6 + public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 6; + + // aapt resource value: 12 + public const int CollapsingToolbarLayout_scrimAnimationDuration = 12; + + // aapt resource value: 11 + public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; + + // aapt resource value: 9 + public const int CollapsingToolbarLayout_statusBarScrim = 9; + + // aapt resource value: 0 + public const int CollapsingToolbarLayout_title = 0; + + // aapt resource value: 15 + public const int CollapsingToolbarLayout_titleEnabled = 15; + + // aapt resource value: 10 + public const int CollapsingToolbarLayout_toolbarId = 10; + + public static int[] CollapsingToolbarLayout_Layout = new int[] { + 2130772271, + 2130772272}; + + // aapt resource value: 0 + public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; + + // aapt resource value: 1 + public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; + + public static int[] ColorStateListItem = new int[] { + 16843173, + 16843551, + 2130772172}; + + // aapt resource value: 2 + public const int ColorStateListItem_alpha = 2; + + // aapt resource value: 1 + public const int ColorStateListItem_android_alpha = 1; + + // aapt resource value: 0 + public const int ColorStateListItem_android_color = 0; + + public static int[] CompoundButton = new int[] { + 16843015, + 2130772173, + 2130772174}; + + // aapt resource value: 0 + public const int CompoundButton_android_button = 0; + + // aapt resource value: 1 + public const int CompoundButton_buttonTint = 1; + + // aapt resource value: 2 + public const int CompoundButton_buttonTintMode = 2; + + public static int[] CoordinatorLayout = new int[] { + 2130772273, + 2130772274}; + + // aapt resource value: 0 + public const int CoordinatorLayout_keylines = 0; + + // aapt resource value: 1 + public const int CoordinatorLayout_statusBarBackground = 1; + + public static int[] CoordinatorLayout_Layout = new int[] { + 16842931, + 2130772275, + 2130772276, + 2130772277, + 2130772278, + 2130772279, + 2130772280}; + + // aapt resource value: 0 + public const int CoordinatorLayout_Layout_android_layout_gravity = 0; + + // aapt resource value: 2 + public const int CoordinatorLayout_Layout_layout_anchor = 2; + + // aapt resource value: 4 + public const int CoordinatorLayout_Layout_layout_anchorGravity = 4; + + // aapt resource value: 1 + public const int CoordinatorLayout_Layout_layout_behavior = 1; + + // aapt resource value: 6 + public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 6; + + // aapt resource value: 5 + public const int CoordinatorLayout_Layout_layout_insetEdge = 5; + + // aapt resource value: 3 + public const int CoordinatorLayout_Layout_layout_keyline = 3; + + public static int[] DesignTheme = new int[] { + 2130772281, + 2130772282, + 2130772283}; + + // aapt resource value: 0 + public const int DesignTheme_bottomSheetDialogTheme = 0; + + // aapt resource value: 1 + public const int DesignTheme_bottomSheetStyle = 1; + + // aapt resource value: 2 + public const int DesignTheme_textColorError = 2; + + public static int[] DrawerArrowToggle = new int[] { + 2130772175, + 2130772176, + 2130772177, + 2130772178, + 2130772179, + 2130772180, + 2130772181, + 2130772182}; + + // aapt resource value: 4 + public const int DrawerArrowToggle_arrowHeadLength = 4; + + // aapt resource value: 5 + public const int DrawerArrowToggle_arrowShaftLength = 5; + + // aapt resource value: 6 + public const int DrawerArrowToggle_barLength = 6; + + // aapt resource value: 0 + public const int DrawerArrowToggle_color = 0; + + // aapt resource value: 2 + public const int DrawerArrowToggle_drawableSize = 2; + + // aapt resource value: 3 + public const int DrawerArrowToggle_gapBetweenBars = 3; + + // aapt resource value: 1 + public const int DrawerArrowToggle_spinBars = 1; + + // aapt resource value: 7 + public const int DrawerArrowToggle_thickness = 7; + + public static int[] FloatingActionButton = new int[] { + 2130772030, + 2130772246, + 2130772247, + 2130772284, + 2130772285, + 2130772286, + 2130772287, + 2130772288}; + + // aapt resource value: 1 + public const int FloatingActionButton_backgroundTint = 1; + + // aapt resource value: 2 + public const int FloatingActionButton_backgroundTintMode = 2; + + // aapt resource value: 6 + public const int FloatingActionButton_borderWidth = 6; + + // aapt resource value: 0 + public const int FloatingActionButton_elevation = 0; + + // aapt resource value: 4 + public const int FloatingActionButton_fabSize = 4; + + // aapt resource value: 5 + public const int FloatingActionButton_pressedTranslationZ = 5; + + // aapt resource value: 3 + public const int FloatingActionButton_rippleColor = 3; + + // aapt resource value: 7 + public const int FloatingActionButton_useCompatPadding = 7; + + public static int[] FloatingActionButton_Behavior_Layout = new int[] { + 2130772289}; + + // aapt resource value: 0 + public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; + + public static int[] FontFamily = new int[] { + 2130772330, + 2130772331, + 2130772332, + 2130772333, + 2130772334, + 2130772335}; + + // aapt resource value: 0 + public const int FontFamily_fontProviderAuthority = 0; + + // aapt resource value: 3 + public const int FontFamily_fontProviderCerts = 3; + + // aapt resource value: 4 + public const int FontFamily_fontProviderFetchStrategy = 4; + + // aapt resource value: 5 + public const int FontFamily_fontProviderFetchTimeout = 5; + + // aapt resource value: 1 + public const int FontFamily_fontProviderPackage = 1; + + // aapt resource value: 2 + public const int FontFamily_fontProviderQuery = 2; + + public static int[] FontFamilyFont = new int[] { + 16844082, + 16844083, + 16844095, + 2130772336, + 2130772337, + 2130772338}; + + // aapt resource value: 0 + public const int FontFamilyFont_android_font = 0; + + // aapt resource value: 2 + public const int FontFamilyFont_android_fontStyle = 2; + + // aapt resource value: 1 + public const int FontFamilyFont_android_fontWeight = 1; + + // aapt resource value: 4 + public const int FontFamilyFont_font = 4; + + // aapt resource value: 3 + public const int FontFamilyFont_fontStyle = 3; + + // aapt resource value: 5 + public const int FontFamilyFont_fontWeight = 5; + + public static int[] ForegroundLinearLayout = new int[] { + 16843017, + 16843264, + 2130772290}; + + // aapt resource value: 0 + public const int ForegroundLinearLayout_android_foreground = 0; + + // aapt resource value: 1 + public const int ForegroundLinearLayout_android_foregroundGravity = 1; + + // aapt resource value: 2 + public const int ForegroundLinearLayout_foregroundInsidePadding = 2; + + public static int[] LinearLayoutCompat = new int[] { + 16842927, + 16842948, + 16843046, + 16843047, + 16843048, + 2130772013, + 2130772183, + 2130772184, + 2130772185}; + + // aapt resource value: 2 + public const int LinearLayoutCompat_android_baselineAligned = 2; + + // aapt resource value: 3 + public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; + + // aapt resource value: 0 + public const int LinearLayoutCompat_android_gravity = 0; + + // aapt resource value: 1 + public const int LinearLayoutCompat_android_orientation = 1; + + // aapt resource value: 4 + public const int LinearLayoutCompat_android_weightSum = 4; + + // aapt resource value: 5 + public const int LinearLayoutCompat_divider = 5; + + // aapt resource value: 8 + public const int LinearLayoutCompat_dividerPadding = 8; + + // aapt resource value: 6 + public const int LinearLayoutCompat_measureWithLargestChild = 6; + + // aapt resource value: 7 + public const int LinearLayoutCompat_showDividers = 7; + + public static int[] LinearLayoutCompat_Layout = new int[] { + 16842931, + 16842996, + 16842997, + 16843137}; + + // aapt resource value: 0 + public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; + + // aapt resource value: 2 + public const int LinearLayoutCompat_Layout_android_layout_height = 2; + + // aapt resource value: 3 + public const int LinearLayoutCompat_Layout_android_layout_weight = 3; + + // aapt resource value: 1 + public const int LinearLayoutCompat_Layout_android_layout_width = 1; + + public static int[] ListPopupWindow = new int[] { + 16843436, + 16843437}; + + // aapt resource value: 0 + public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; + + // aapt resource value: 1 + public const int ListPopupWindow_android_dropDownVerticalOffset = 1; + + public static int[] MediaRouteButton = new int[] { + 16843071, + 16843072, + 2130771989, + 2130771990}; + + // aapt resource value: 1 + public const int MediaRouteButton_android_minHeight = 1; + + // aapt resource value: 0 + public const int MediaRouteButton_android_minWidth = 0; + + // aapt resource value: 2 + public const int MediaRouteButton_externalRouteEnabledDrawable = 2; + + // aapt resource value: 3 + public const int MediaRouteButton_mediaRouteButtonTint = 3; + + public static int[] MenuGroup = new int[] { + 16842766, + 16842960, + 16843156, + 16843230, + 16843231, + 16843232}; + + // aapt resource value: 5 + public const int MenuGroup_android_checkableBehavior = 5; + + // aapt resource value: 0 + public const int MenuGroup_android_enabled = 0; + + // aapt resource value: 1 + public const int MenuGroup_android_id = 1; + + // aapt resource value: 3 + public const int MenuGroup_android_menuCategory = 3; + + // aapt resource value: 4 + public const int MenuGroup_android_orderInCategory = 4; + + // aapt resource value: 2 + public const int MenuGroup_android_visible = 2; + + public static int[] MenuItem = new int[] { + 16842754, + 16842766, + 16842960, + 16843014, + 16843156, + 16843230, + 16843231, + 16843233, + 16843234, + 16843235, + 16843236, + 16843237, + 16843375, + 2130772186, + 2130772187, + 2130772188, + 2130772189, + 2130772190, + 2130772191, + 2130772192, + 2130772193, + 2130772194, + 2130772195}; + + // aapt resource value: 16 + public const int MenuItem_actionLayout = 16; + + // aapt resource value: 18 + public const int MenuItem_actionProviderClass = 18; + + // aapt resource value: 17 + public const int MenuItem_actionViewClass = 17; + + // aapt resource value: 13 + public const int MenuItem_alphabeticModifiers = 13; + + // aapt resource value: 9 + public const int MenuItem_android_alphabeticShortcut = 9; + + // aapt resource value: 11 + public const int MenuItem_android_checkable = 11; + + // aapt resource value: 3 + public const int MenuItem_android_checked = 3; + + // aapt resource value: 1 + public const int MenuItem_android_enabled = 1; + + // aapt resource value: 0 + public const int MenuItem_android_icon = 0; + + // aapt resource value: 2 + public const int MenuItem_android_id = 2; + + // aapt resource value: 5 + public const int MenuItem_android_menuCategory = 5; + + // aapt resource value: 10 + public const int MenuItem_android_numericShortcut = 10; + + // aapt resource value: 12 + public const int MenuItem_android_onClick = 12; + + // aapt resource value: 6 + public const int MenuItem_android_orderInCategory = 6; + + // aapt resource value: 7 + public const int MenuItem_android_title = 7; + + // aapt resource value: 8 + public const int MenuItem_android_titleCondensed = 8; + + // aapt resource value: 4 + public const int MenuItem_android_visible = 4; + + // aapt resource value: 19 + public const int MenuItem_contentDescription = 19; + + // aapt resource value: 21 + public const int MenuItem_iconTint = 21; + + // aapt resource value: 22 + public const int MenuItem_iconTintMode = 22; + + // aapt resource value: 14 + public const int MenuItem_numericModifiers = 14; + + // aapt resource value: 15 + public const int MenuItem_showAsAction = 15; + + // aapt resource value: 20 + public const int MenuItem_tooltipText = 20; + + public static int[] MenuView = new int[] { + 16842926, + 16843052, + 16843053, + 16843054, + 16843055, + 16843056, + 16843057, + 2130772196, + 2130772197}; + + // aapt resource value: 4 + public const int MenuView_android_headerBackground = 4; + + // aapt resource value: 2 + public const int MenuView_android_horizontalDivider = 2; + + // aapt resource value: 5 + public const int MenuView_android_itemBackground = 5; + + // aapt resource value: 6 + public const int MenuView_android_itemIconDisabledAlpha = 6; + + // aapt resource value: 1 + public const int MenuView_android_itemTextAppearance = 1; + + // aapt resource value: 3 + public const int MenuView_android_verticalDivider = 3; + + // aapt resource value: 0 + public const int MenuView_android_windowAnimationStyle = 0; + + // aapt resource value: 7 + public const int MenuView_preserveIconSpacing = 7; + + // aapt resource value: 8 + public const int MenuView_subMenuArrow = 8; + + public static int[] NavigationView = new int[] { + 16842964, + 16842973, + 16843039, + 2130772030, + 2130772291, + 2130772292, + 2130772293, + 2130772294, + 2130772295, + 2130772296}; + + // aapt resource value: 0 + public const int NavigationView_android_background = 0; + + // aapt resource value: 1 + public const int NavigationView_android_fitsSystemWindows = 1; + + // aapt resource value: 2 + public const int NavigationView_android_maxWidth = 2; + + // aapt resource value: 3 + public const int NavigationView_elevation = 3; + + // aapt resource value: 9 + public const int NavigationView_headerLayout = 9; + + // aapt resource value: 7 + public const int NavigationView_itemBackground = 7; + + // aapt resource value: 5 + public const int NavigationView_itemIconTint = 5; + + // aapt resource value: 8 + public const int NavigationView_itemTextAppearance = 8; + + // aapt resource value: 6 + public const int NavigationView_itemTextColor = 6; + + // aapt resource value: 4 + public const int NavigationView_menu = 4; + + public static int[] PopupWindow = new int[] { + 16843126, + 16843465, + 2130772198}; + + // aapt resource value: 1 + public const int PopupWindow_android_popupAnimationStyle = 1; + + // aapt resource value: 0 + public const int PopupWindow_android_popupBackground = 0; + + // aapt resource value: 2 + public const int PopupWindow_overlapAnchor = 2; + + public static int[] PopupWindowBackgroundState = new int[] { + 2130772199}; + + // aapt resource value: 0 + public const int PopupWindowBackgroundState_state_above_anchor = 0; + + public static int[] RecycleListView = new int[] { + 2130772200, + 2130772201}; + + // aapt resource value: 0 + public const int RecycleListView_paddingBottomNoButtons = 0; + + // aapt resource value: 1 + public const int RecycleListView_paddingTopNoTitle = 1; + + public static int[] RecyclerView = new int[] { + 16842948, + 16842993, + 2130771968, + 2130771969, + 2130771970, + 2130771971, + 2130771972, + 2130771973, + 2130771974, + 2130771975, + 2130771976}; + + // aapt resource value: 1 + public const int RecyclerView_android_descendantFocusability = 1; + + // aapt resource value: 0 + public const int RecyclerView_android_orientation = 0; + + // aapt resource value: 6 + public const int RecyclerView_fastScrollEnabled = 6; + + // aapt resource value: 9 + public const int RecyclerView_fastScrollHorizontalThumbDrawable = 9; + + // aapt resource value: 10 + public const int RecyclerView_fastScrollHorizontalTrackDrawable = 10; + + // aapt resource value: 7 + public const int RecyclerView_fastScrollVerticalThumbDrawable = 7; + + // aapt resource value: 8 + public const int RecyclerView_fastScrollVerticalTrackDrawable = 8; + + // aapt resource value: 2 + public const int RecyclerView_layoutManager = 2; + + // aapt resource value: 4 + public const int RecyclerView_reverseLayout = 4; + + // aapt resource value: 3 + public const int RecyclerView_spanCount = 3; + + // aapt resource value: 5 + public const int RecyclerView_stackFromEnd = 5; + + public static int[] ScrimInsetsFrameLayout = new int[] { + 2130772297}; + + // aapt resource value: 0 + public const int ScrimInsetsFrameLayout_insetForeground = 0; + + public static int[] ScrollingViewBehavior_Layout = new int[] { + 2130772298}; + + // aapt resource value: 0 + public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; + + public static int[] SearchView = new int[] { + 16842970, + 16843039, + 16843296, + 16843364, + 2130772202, + 2130772203, + 2130772204, + 2130772205, + 2130772206, + 2130772207, + 2130772208, + 2130772209, + 2130772210, + 2130772211, + 2130772212, + 2130772213, + 2130772214}; + + // aapt resource value: 0 + public const int SearchView_android_focusable = 0; + + // aapt resource value: 3 + public const int SearchView_android_imeOptions = 3; + + // aapt resource value: 2 + public const int SearchView_android_inputType = 2; + + // aapt resource value: 1 + public const int SearchView_android_maxWidth = 1; + + // aapt resource value: 8 + public const int SearchView_closeIcon = 8; + + // aapt resource value: 13 + public const int SearchView_commitIcon = 13; + + // aapt resource value: 7 + public const int SearchView_defaultQueryHint = 7; + + // aapt resource value: 9 + public const int SearchView_goIcon = 9; + + // aapt resource value: 5 + public const int SearchView_iconifiedByDefault = 5; + + // aapt resource value: 4 + public const int SearchView_layout = 4; + + // aapt resource value: 15 + public const int SearchView_queryBackground = 15; + + // aapt resource value: 6 + public const int SearchView_queryHint = 6; + + // aapt resource value: 11 + public const int SearchView_searchHintIcon = 11; + + // aapt resource value: 10 + public const int SearchView_searchIcon = 10; + + // aapt resource value: 16 + public const int SearchView_submitBackground = 16; + + // aapt resource value: 14 + public const int SearchView_suggestionRowLayout = 14; + + // aapt resource value: 12 + public const int SearchView_voiceIcon = 12; + + public static int[] SnackbarLayout = new int[] { + 16843039, + 2130772030, + 2130772299}; + + // aapt resource value: 0 + public const int SnackbarLayout_android_maxWidth = 0; + + // aapt resource value: 1 + public const int SnackbarLayout_elevation = 1; + + // aapt resource value: 2 + public const int SnackbarLayout_maxActionInlineWidth = 2; + + public static int[] Spinner = new int[] { + 16842930, + 16843126, + 16843131, + 16843362, + 2130772031}; + + // aapt resource value: 3 + public const int Spinner_android_dropDownWidth = 3; + + // aapt resource value: 0 + public const int Spinner_android_entries = 0; + + // aapt resource value: 1 + public const int Spinner_android_popupBackground = 1; + + // aapt resource value: 2 + public const int Spinner_android_prompt = 2; + + // aapt resource value: 4 + public const int Spinner_popupTheme = 4; + + public static int[] SwitchCompat = new int[] { + 16843044, + 16843045, + 16843074, + 2130772215, + 2130772216, + 2130772217, + 2130772218, + 2130772219, + 2130772220, + 2130772221, + 2130772222, + 2130772223, + 2130772224, + 2130772225}; + + // aapt resource value: 1 + public const int SwitchCompat_android_textOff = 1; + + // aapt resource value: 0 + public const int SwitchCompat_android_textOn = 0; + + // aapt resource value: 2 + public const int SwitchCompat_android_thumb = 2; + + // aapt resource value: 13 + public const int SwitchCompat_showText = 13; + + // aapt resource value: 12 + public const int SwitchCompat_splitTrack = 12; + + // aapt resource value: 10 + public const int SwitchCompat_switchMinWidth = 10; + + // aapt resource value: 11 + public const int SwitchCompat_switchPadding = 11; + + // aapt resource value: 9 + public const int SwitchCompat_switchTextAppearance = 9; + + // aapt resource value: 8 + public const int SwitchCompat_thumbTextPadding = 8; + + // aapt resource value: 3 + public const int SwitchCompat_thumbTint = 3; + + // aapt resource value: 4 + public const int SwitchCompat_thumbTintMode = 4; + + // aapt resource value: 5 + public const int SwitchCompat_track = 5; + + // aapt resource value: 6 + public const int SwitchCompat_trackTint = 6; + + // aapt resource value: 7 + public const int SwitchCompat_trackTintMode = 7; + + public static int[] TabItem = new int[] { + 16842754, + 16842994, + 16843087}; + + // aapt resource value: 0 + public const int TabItem_android_icon = 0; + + // aapt resource value: 1 + public const int TabItem_android_layout = 1; + + // aapt resource value: 2 + public const int TabItem_android_text = 2; + + public static int[] TabLayout = new int[] { + 2130772300, + 2130772301, + 2130772302, + 2130772303, + 2130772304, + 2130772305, + 2130772306, + 2130772307, + 2130772308, + 2130772309, + 2130772310, + 2130772311, + 2130772312, + 2130772313, + 2130772314, + 2130772315}; + + // aapt resource value: 3 + public const int TabLayout_tabBackground = 3; + + // aapt resource value: 2 + public const int TabLayout_tabContentStart = 2; + + // aapt resource value: 5 + public const int TabLayout_tabGravity = 5; + + // aapt resource value: 0 + public const int TabLayout_tabIndicatorColor = 0; + + // aapt resource value: 1 + public const int TabLayout_tabIndicatorHeight = 1; + + // aapt resource value: 7 + public const int TabLayout_tabMaxWidth = 7; + + // aapt resource value: 6 + public const int TabLayout_tabMinWidth = 6; + + // aapt resource value: 4 + public const int TabLayout_tabMode = 4; + + // aapt resource value: 15 + public const int TabLayout_tabPadding = 15; + + // aapt resource value: 14 + public const int TabLayout_tabPaddingBottom = 14; + + // aapt resource value: 13 + public const int TabLayout_tabPaddingEnd = 13; + + // aapt resource value: 11 + public const int TabLayout_tabPaddingStart = 11; + + // aapt resource value: 12 + public const int TabLayout_tabPaddingTop = 12; + + // aapt resource value: 10 + public const int TabLayout_tabSelectedTextColor = 10; + + // aapt resource value: 8 + public const int TabLayout_tabTextAppearance = 8; + + // aapt resource value: 9 + public const int TabLayout_tabTextColor = 9; + + public static int[] TextAppearance = new int[] { + 16842901, + 16842902, + 16842903, + 16842904, + 16842906, + 16842907, + 16843105, + 16843106, + 16843107, + 16843108, + 16843692, + 2130772047, + 2130772053}; + + // aapt resource value: 10 + public const int TextAppearance_android_fontFamily = 10; + + // aapt resource value: 6 + public const int TextAppearance_android_shadowColor = 6; + + // aapt resource value: 7 + public const int TextAppearance_android_shadowDx = 7; + + // aapt resource value: 8 + public const int TextAppearance_android_shadowDy = 8; + + // aapt resource value: 9 + public const int TextAppearance_android_shadowRadius = 9; + + // aapt resource value: 3 + public const int TextAppearance_android_textColor = 3; + + // aapt resource value: 4 + public const int TextAppearance_android_textColorHint = 4; + + // aapt resource value: 5 + public const int TextAppearance_android_textColorLink = 5; + + // aapt resource value: 0 + public const int TextAppearance_android_textSize = 0; + + // aapt resource value: 2 + public const int TextAppearance_android_textStyle = 2; + + // aapt resource value: 1 + public const int TextAppearance_android_typeface = 1; + + // aapt resource value: 12 + public const int TextAppearance_fontFamily = 12; + + // aapt resource value: 11 + public const int TextAppearance_textAllCaps = 11; + + public static int[] TextInputLayout = new int[] { + 16842906, + 16843088, + 2130772316, + 2130772317, + 2130772318, + 2130772319, + 2130772320, + 2130772321, + 2130772322, + 2130772323, + 2130772324, + 2130772325, + 2130772326, + 2130772327, + 2130772328, + 2130772329}; + + // aapt resource value: 1 + public const int TextInputLayout_android_hint = 1; + + // aapt resource value: 0 + public const int TextInputLayout_android_textColorHint = 0; + + // aapt resource value: 6 + public const int TextInputLayout_counterEnabled = 6; + + // aapt resource value: 7 + public const int TextInputLayout_counterMaxLength = 7; + + // aapt resource value: 9 + public const int TextInputLayout_counterOverflowTextAppearance = 9; + + // aapt resource value: 8 + public const int TextInputLayout_counterTextAppearance = 8; + + // aapt resource value: 4 + public const int TextInputLayout_errorEnabled = 4; + + // aapt resource value: 5 + public const int TextInputLayout_errorTextAppearance = 5; + + // aapt resource value: 10 + public const int TextInputLayout_hintAnimationEnabled = 10; + + // aapt resource value: 3 + public const int TextInputLayout_hintEnabled = 3; + + // aapt resource value: 2 + public const int TextInputLayout_hintTextAppearance = 2; + + // aapt resource value: 13 + public const int TextInputLayout_passwordToggleContentDescription = 13; + + // aapt resource value: 12 + public const int TextInputLayout_passwordToggleDrawable = 12; + + // aapt resource value: 11 + public const int TextInputLayout_passwordToggleEnabled = 11; + + // aapt resource value: 14 + public const int TextInputLayout_passwordToggleTint = 14; + + // aapt resource value: 15 + public const int TextInputLayout_passwordToggleTintMode = 15; + + public static int[] Toolbar = new int[] { + 16842927, + 16843072, + 2130772005, + 2130772008, + 2130772012, + 2130772024, + 2130772025, + 2130772026, + 2130772027, + 2130772028, + 2130772029, + 2130772031, + 2130772226, + 2130772227, + 2130772228, + 2130772229, + 2130772230, + 2130772231, + 2130772232, + 2130772233, + 2130772234, + 2130772235, + 2130772236, + 2130772237, + 2130772238, + 2130772239, + 2130772240, + 2130772241, + 2130772242}; + + // aapt resource value: 0 + public const int Toolbar_android_gravity = 0; + + // aapt resource value: 1 + public const int Toolbar_android_minHeight = 1; + + // aapt resource value: 21 + public const int Toolbar_buttonGravity = 21; + + // aapt resource value: 23 + public const int Toolbar_collapseContentDescription = 23; + + // aapt resource value: 22 + public const int Toolbar_collapseIcon = 22; + + // aapt resource value: 6 + public const int Toolbar_contentInsetEnd = 6; + + // aapt resource value: 10 + public const int Toolbar_contentInsetEndWithActions = 10; + + // aapt resource value: 7 + public const int Toolbar_contentInsetLeft = 7; + + // aapt resource value: 8 + public const int Toolbar_contentInsetRight = 8; + + // aapt resource value: 5 + public const int Toolbar_contentInsetStart = 5; + + // aapt resource value: 9 + public const int Toolbar_contentInsetStartWithNavigation = 9; + + // aapt resource value: 4 + public const int Toolbar_logo = 4; + + // aapt resource value: 26 + public const int Toolbar_logoDescription = 26; + + // aapt resource value: 20 + public const int Toolbar_maxButtonHeight = 20; + + // aapt resource value: 25 + public const int Toolbar_navigationContentDescription = 25; + + // aapt resource value: 24 + public const int Toolbar_navigationIcon = 24; + + // aapt resource value: 11 + public const int Toolbar_popupTheme = 11; + + // aapt resource value: 3 + public const int Toolbar_subtitle = 3; + + // aapt resource value: 13 + public const int Toolbar_subtitleTextAppearance = 13; + + // aapt resource value: 28 + public const int Toolbar_subtitleTextColor = 28; + + // aapt resource value: 2 + public const int Toolbar_title = 2; + + // aapt resource value: 14 + public const int Toolbar_titleMargin = 14; + + // aapt resource value: 18 + public const int Toolbar_titleMarginBottom = 18; + + // aapt resource value: 16 + public const int Toolbar_titleMarginEnd = 16; + + // aapt resource value: 15 + public const int Toolbar_titleMarginStart = 15; + + // aapt resource value: 17 + public const int Toolbar_titleMarginTop = 17; + + // aapt resource value: 19 + public const int Toolbar_titleMargins = 19; + + // aapt resource value: 12 + public const int Toolbar_titleTextAppearance = 12; + + // aapt resource value: 27 + public const int Toolbar_titleTextColor = 27; + + public static int[] View = new int[] { + 16842752, + 16842970, + 2130772243, + 2130772244, + 2130772245}; + + // aapt resource value: 1 + public const int View_android_focusable = 1; + + // aapt resource value: 0 + public const int View_android_theme = 0; + + // aapt resource value: 3 + public const int View_paddingEnd = 3; + + // aapt resource value: 2 + public const int View_paddingStart = 2; + + // aapt resource value: 4 + public const int View_theme = 4; + + public static int[] ViewBackgroundHelper = new int[] { + 16842964, + 2130772246, + 2130772247}; + + // aapt resource value: 0 + public const int ViewBackgroundHelper_android_background = 0; + + // aapt resource value: 1 + public const int ViewBackgroundHelper_backgroundTint = 1; + + // aapt resource value: 2 + public const int ViewBackgroundHelper_backgroundTintMode = 2; + + public static int[] ViewStubCompat = new int[] { + 16842960, + 16842994, + 16842995}; + + // aapt resource value: 0 + public const int ViewStubCompat_android_id = 0; + + // aapt resource value: 2 + public const int ViewStubCompat_android_inflatedId = 2; + + // aapt resource value: 1 + public const int ViewStubCompat_android_layout = 1; + + static Styleable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Styleable() + { + } + } + } +} +#pragma warning restore 1591 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/layout/activity_main.axml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/layout/activity_main.axml new file mode 100644 index 00000000..7447d516 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/layout/activity_main.axml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..c9ad5f98 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..c9ad5f98 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2531cb31efc3a0a3de6113ab9c7845dc1d9654e4 GIT binary patch literal 1634 zcmV-o2A%ndP)B+Z3$1(8#|f~9B42Y^N-3=o2YCq0YUY$Zu=pM;#hG{lHi%n~Vh z1d1vN#EDO19X?u$`cV z!a}AKG@Bb*#1cdYg8af_;jP69b`k%G1n?0=F^8bI^o>wg-vEliK^U}y^!D|^p|ax; zC|pK=f+FHp!RUAhtlpGGUxJb|wm^5! z<1r%$<$TR02wajxKZ4MiR#aAxDLE(##UNyD|ABr4WoGRF*?@e^2|~Hq(gurSSJH*;Q~5lw{J5A_(PCXBWhzZE${qgzv0{dk-F( z1<}>r181tLiEla&f1j&?p2xjbfp2cTt-c1Ox~?9EhK9`cJ9Vatf)loIoQ@#h&}cIGD>Z#QLE}&(bMo@7Ff|7f#Nm^$PJpVcbj+v~K7wfVwF}=) zRQsc+`=A-+C)vrRvaIC-5u>|;3h z*G4-u#RI<_vuSN~vZ6{|I~q5FFk3%de#+*>UFG>&bq6~ zUEMZ~FIOmFO=kA^5rkp-Msw?^63xvdXVZ-rv@{6{iVO}M!}^Je%2BPbi+(L<5<%~h z2v^D+f<|j%7~cJjOzg*!GPQ{%uE{i%YgcZhuZh{yNlQ}RhaU1jd=K+AopVKP+D}&} zZ3y$llqZiln=Z_A$!qzkGbX0D{?l(v5@1|`QyCvCnQ`eKI>|zj_zo%y#fKf85VhQ} zP)y&j4P*nR3q{-o35iV6nx7QDqq<;WDVIt}|N%`!dgv*y3va8eLNj zU9x(?ieweHfQ*yXk8|=ssZ~qJEz^QoKJ|iGa>ge_Vm_8l}S+UvJ{8g4jr+o#aTSFsz1W;PDP zW765JXGU#3JL>SlIl3NRV2{7B2dLO1cIP)a4ZRYL|MBD36O1#oSgAf}APz5@;x=_U-<=y)Py7*}O5(uu7BL_eLe6Ek7pH|G zMq)FrF1EFq&yruS5b=F=w)fVVoPd(oeRyTFym_Uwyn~L=OL(O?cf^2L5R(SmjORx6 z%nmZf^W=3pkvT*>@osUNi>DULH1hL;y`JGQX$onRCr_U0=H~Viodq!<7Q{3rPk~{G gu#IhOV;e2n|1(WJB~7~kivR!s07*qoM6N<$g7lUVaR2}S literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..7a859c25556af7a2e46e22a2220eaded55628e9f GIT binary patch literal 1441 zcma)+`#%#30L6FjMQg%tuA0%p#0??L`*E=rD#U2F4L5n@F+O9Sp;(QwEQy7+?sX?r zCWN(!Hg`+j5k8*H@|yQEtnAi*(D{7M`Tlf1=eKjq)BUsp2nqrK01B=yNUv`!`EH=x zx8$xJQUd^Fuec%|(TT&0V}4orr_==mmCnEuzD+ff8Pg>pJRqsWsD{#?eGPaCu0(sEH_2RG@<6-Nt<8 ztPMUmmAz9Ga$23Y9~p9dqJSgJJ#Jk_r@o13^%d-Xf46i+Lrmz3 zy9(DUDVXj;Zny7nO+yn&W2flEX=C!8&D0zI`G# z8;XmlonoghgRFUY*$+7pPLa}Uy)onw>TT9t(FTV6#BV8&lXWDPRvQW_n~xZ|yLcZjX>m$Eaf1)dwXS`&E^ zkNjO;%;fWywchc=+w4utQ0Vbn%B>b~yy4I#D{?1!P`$P>Wdo+ljCo(tYia04JTc=$$u+IbzDVPFYpm8+AQj+ zGKH zfS{{hN%W)kF+(26oZpkURD5Q_G_z97F6{Jval+TOj-;5y)*Rdo3a$^^k~q5gpTzmp1q@+2X9O z;_VUF>;s~C1~gpFrFoh?{aQ|LlBIYz!z^P~lndX5-ES)p#+9GW*|-WBTzQ*&gKOE` zM##bUaWl`6rZBXw0!~_oUhf+H$tNc@lLZCj0bZT^KSo@C|P?7YR8dP0se1jj z9aA0|7MONf(ZYaLZs$s}r*05fx25-iN6mZe_*Rq%uyz(+^-k;t`!R`?uf~rn#1ZC7 zuv3}UrmMzcBbo4jym@fS5%I+G`GJIC1s$)MQs3Vhld?a2U;w}$@V%dC@%qpO7+3#$ N&GnQ!lI8SQ#{X#Iv!eh2 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b8d35b3a1cfe5308bb099a5a1429555c41417e36 GIT binary patch literal 3552 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8f56909cddfa86f1387074bf43003f36d6e67be1 GIT binary patch literal 2413 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 b;}yxl--Ss53i@>Q4YQuNcebmsMJN0NT!aL! literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9737d79c0492b2c8a811621660eba33e79fab959 GIT binary patch literal 4858 zcmVxlCBHiW_rSgI3_J^MKwHqJEz|i*Sg*YtOHn%!8|O@U|xT*V!1aH) zx9aT)+OT1e6*I^fro))}A|t%nqOC49C*uh}iznRD0RVt(Fkci3aF-cE^~v-{jirSe z8y+KDRrXqA%?3VAUmJ!e`Y4{{Db{MI)J1oI-WfBjRTVY1Q!rK-v!l86id7G;UWZ3x z7~0LnZOuZ2xjo$KBiYmM_`2d z5?SVjnV>hVk!Z_9*%?FywwjSrU-z}DU~qVkNCML#z4GhV z_dS*4ib?_|4A~&o6c6ZDCNLfVt@G)TDg@Pe&InwDu_Y44rH_jqbYt zQQk%w?14PLdL_onhlQI!tDo8~G_ws5=fN6HW6)RMZ1xE78Tw}PR+Lk5El;CNtD@BG z@-c!)0b@`g>cgGvV&(C9t(F;co=4};U+^dfw6xu|4X@RormvOYhELMs z#n0=>EFFekYFvrh+S)vl0br1y$L?uHF?ZLL#>k8mg*7cHSw;nCRmALvD)pwhLaqK` zH{FAdpJ?$&@EJOEIG%e~S}30iDZGsfvTJYqebn^#ei9&%5{a3h)`)uHexhMfx2GC}a7&+PSj;~z&<#NnP097H+5#qe^HCa1jY34dHKXo8 zyY}pNY0`(An$dSZ{AfkZ$4_A9@iVII_BL<*2^~Fl!lh?HY6o9?8_(#NGRALVO#8VI z9n&Hr&MA(;4gAX2_<|07{q29d4A%Yse8#Sg>u#G&F@_8Hz`UC4@30;drblKka481` z?((Z|zQ@@uWsI@Bpz3gpTq7nHw%?y+JiTRw)x(8QKjZG6LV@5aU|(2+QR(aE^IiQA zbbY#Ry<58f_jBjbjM>lIwKaI;ZD{|mhuvbp&fR-a)yVM<(;)5!g71B_7Ufosrv7ZTPIz#p-Luu#-A?Iq&cPX$ zzM1o0ayvrq*fGO)ASt78v{QGK(f{&-ng{so_ts*sjO@u0Q~!L6QwtMIG_TAibnspej~MaY~_~X)&16cA3OA}Uc)}S zZIuHg0l)fIxZO8!t8bb(l>-Cnku0bDbBiIiN=wjhmPbZL24MzlVdpYjrNWx)(Pv+N zBWOAR3??M;Y<>CqF?UmT!q$5#$Hw0_5S%iz0WXT*1g|T5HRZin>UI=?a+d@J@ z!s*q|QbSDkGb%|Ptu~nUaAClGGv)}o`WafkaSJLkjkN=I!IBjnQqbDkiW**Ov@?)k zGq(Qtv*2Socm6z@IOPdFd$xCn2c|3a@PedtiB%Y-T!Ns zB*nm2J}l((;v)h?(g?ET>{yU|?VjUA$|Z5Ar4z zy&(!+?I)a55qI7%Xw>;RW~l8%Ar-Om{WT5^Y~x$+J4{7<@%1J_QxP{h$Tzu?ijZcP zKq?}fVC`eW07@i+F8B>mD^4f z)ZCiSzUcJ1kJo--m#qXTfHz@!FdhAeQdfr()df(n8{lw5hWt__$<&YXgbf+9gAJMc zW<2fEh74^Wt)GRe=bqeL_c`r8F zZ%NkP(2@K3Gurh1b{rks2WKzipslrswj^bFgIglwlMH~dvpP|4vRM$R(A9m*hXM4a z{4CC!@(@?pZpuIQ%!_Vq%1@oy;BZ@V_r3$1Hs$Z-xhbElE&Cp0JBVQHxI|GZmG;L! z!cy}pUl5`!WzA<_x?Ps?(38*EwFT+}D%{)w4WeKG+_o)f-(4r+oe$Td9FAov)Yh)P z4vEusup1UeF!pl7fNJ<-5Wab=5QSObu{0lZy)X+3VhwhMS;IIMX0@RgaIog6Fbk?C zTx|!ur{OpMjaOloqObP-sLfq@n$Z3)UV(sl1(Orr_5onOR78jzqW7(*JljLXv( z@h(qS6x5&?Y5JXjX{Y+%Mhyk@@83TeKfIkwUdT~|ykpm%Uc~^Yq_8a%b~pV1Kc(8z zoqm3P3c4D?#dpPGV`HIoB1)QRoC#7O#GxDz9Gw!NHm6%&QMzz}Dm~%)iV{ zGPeP+B$&E(5j7MN5)+rJ)D3A8;w8Q8Ui6aQr~h3q$V+_zR@JpD!O z6@t8|oswO4Y(T`I62MR_7K=EYk`fUS0Y|&XC1n`qz>CL1NP%Y`Rj{AeQ3cHE2i+g9 z$XNi`5e&JWnnKxva6i8wwX9(94k6-#zI|8+z44N)E#Bqp8<0hBzPP9Rok_u<_*BiE zpx1Fxs=hMmM6B-%{ zA2dja5v#^23aZ50BUK|xXAp(ZNxW`U&_!XEVU zV=I}8Hxwt!nhV$vjJo7JX>U56>IHQz@}zXb3SyKmUA_mmg3DQhUCz8!fC<4Spew($ z;e$P^5VEzFCeakFf!%)Me)ZWyyPbef8C|hjw-#fOPGdr0)8${-=*QRtI6OT$v*@eK zi3wKVrx$(=1tndn_noPttFW$%gmXQxy3=ANthcD6zW40_8=X((d6Lp}-{86D0tN(& zZvEtyH_Ip|VaiO>7(QVPGkrcnp8}qJ7#~Vh7lPV>GV>&s(e3sxEJ25Ufq{YWg(3I~ zU4}R<|4n&8b;l=6`T`RyF%KQ(#w&8b;KGpu5;Awcp8UKO#RMXPAPH&lO6_b}ZskR& zg{195@012Qu|}yJD!-GOQ*kj)rU6$ojja60o(A8hpey)lFE0@=K^2{-xJ8;-yobph z^)_i>uX^gpvCN{qQFM@{qUQ*6_423>yD?RDp(2q8PKHwW2Z!m!s={|bY(W~B4{CZc zBgoh~q*j(U7>QN+?}>s2z^;~p%x!?DfzM_FxM6|*{{Hd!XA1bo10~8y5>4?As19Hv zXJVxP@Fdrg9#hA8pGcxH?u+Cm=y&w<~fq{a`3jA*+9(;bhBKtXM zc3BhSDM86L(XTyXBiK5gjD@OThB3w~vQ@?l6Mli8uULbAMT{ygP>eX7*m2G=arDK$ZBF}Q^?qZJyqqn zs*>=^35vw}6AZKrL^?D)sxnTNIS&VL+rdVVNZLw8F)D#!iaU&9?q|O7!fuc02hQ(- zzF`b;shJHS;gMBD-N@*%QeKXzH>ez!B4=8E21biSp%TJ~G+$re+-R|EVxl_lZE05N zewrCWSdzj1Rt=>p+F4)5ZfAgH|Bktj4K}mVfzc4B;J)@jpU^iRLmpZ2GJ0&3x(V#= z$hNy|1Bh}U=v3lSfND}<5Hf;-29ykx$R{Nza~qR044YE3%a6(Os;LcbSgo`tWz85z zM6Y}k^$a{K&#$=z^*PCz#!b*R^Z|WApR`-)l>%cSdOonz`u#q}hyd`Xv7U{CH=~GD zr~w#EIbjjeb+AI?Q?+vvl=*LnGxVQHGK)8-Xv==V%sG^rS9w&PS9u%={+*grehB`C zwp4sK%tv;}Pv(A9KbA_?6$<gpmV|K5zk3V^6LOr zItEUINek*iBnmPHhK5%JV^9ZN9bXRw|Aya*M8O8Qhuo_nI$cfLl0w_GVWsqY5b3*L zUsE+)7~w;7ZhxW%!r+Bw@V#kOMM+39QCTtqD3F3ha`Lwn`d*O)o`p8Z%h6$^?f#@M zpUWM1R~X_)cHscHP`c6}I0E!FfNDe0@HbM85K5l$Cv98-oF_vVruYz*(T{-2Cg%4( gUP6AytBbGy15leQhEvp{>;M1&07*qoM6N<$g7ZLQy#N3J literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9133e31b43252d00767a6a3806df9ba68de2d265 GIT binary patch literal 3871 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ae5f5ccdecc01a9b17a2a0c2b1bb20602f0151 GIT binary patch literal 8001 zcmV-HAHLv;P)_otvA^2tyUR8VoCfH?7Uf~Y8h zGGvL!9~U1e2+EQ@WE5!2`JeaRb4v*AP1@XhlD4_e^FD<(x#OJQec#_Z&U@V4T!-s$ z9j?Q5xDMCRfsbx(Zj;?X1`i(Golm&WvEOkWT@EAwg5u(04-gg*b^)Q=wdZqzt5X5S z3@E&xRqAU4(t6iMrj`y!NG~3kqBiu;%rFkf27!OW@8ECn8ThO4HTO;#7xy{;~-`#PSee#+yl`$7 zsLK|B`URc=p2hMdam~0$z)>3q=>?G-oqR?n&P@dVyd_S<+u&%Xj+V7fH_Q{po6c#f1Tbw|%*|St=SEuXXwPQvs;F+N*+6v& zkIGS=8;n&;W7y>ag7A-w!kVPC!v1S4JS!J)TIEOFIQ3rxW7krsqtmA#u9&R4Ay`gb z(K=n%T(#4z;juGa*V5Q_dcLDB>_6S5b%fDI*u>4?G*GAIMVyzVRuA^V55I_W&0So_ z?m#5#@*8Uw%Vd?_ozm6kh@LvXJd~7GxJ;G^CQWUu{Z64R4)0XtntK~kATU^H+D^c8 z$u;=`ixI{YgUC>`Lsn3k+$l5>_W&w=jT%4PK^J%^fyih&sMJ+tbZ8JYn=PYBg&*pu z3p}(zRC`R3SDx7+%^8RK)Pkyn^uoFWF7P)0TEDbH=%m>4xeM{1Dq*;BhR7 zR0aLE%d(6S9mK_F16jmX-{=C5qlF!NRYBGF5=p+Vvj-cwP3%~$8xBY7p`fb-9)Y#aFnwpwAl)ydj$3Pl0ek#%w z51>+@mReAKLYiq%I18yZ<2|M|G!vun*52{p6m;a+@eT(ZOF41!6dE_>89JuSh)r33 z`35{^-5t({xYA0jBB#*iJ*5L~K|BBWv%`ajlRbO)V^e%54N~2p($^q)UfEL?rNoXQ z%_@UQN1OM6x_^G|JDmnRAPo%-43En$9Ylo>r502nnWnhdQ6S>fo;$vw?`YTbTtDU^ zbm+*jP6Z&4bLY>ak$3%@nkiH2%D3P-^rUXeu9&X6`)Hf4tkQw#tCj0IBx$xqR(|^( z(qlKDjw$Ph6ghn+P}V|h!z8t#EFRy;3A1h&bcpk~Dd?XwXFDZ$K;YRPe(YIFh5Fc( z{rP(^XJ)J^JN;zjs>jaI){f-zdLwI2BW-GSncYwsaxP zspxKfGjY!Em&bMRq8Bi%L(`s{$B@m=4xmey8qf>#7ox0^fm8@}O0TM>#54m9Ld~c+ z_cWtvF>UQrIrI*+W9RNp4<1eq9y)@mhL53^=1}C8eaXg#L^5NX_EGDrOU%})BU;?& zgC)y4Epcv5KKp7F()J!qgHT^i$*)AxOhZ2rwGgL$>OP~rUcLWK_o5T0PIoErfE+!3 z0*$(V5)A+~GFm97Y=tOV$b$P&4I1johoTj$*LOMaaPs4?+mVJE7pg!BYJG{|T8Q(! z)W+Jmw6)KJlb=Cn&zGwnS);jE(y!@=IfB$9)QGN1`8o z{I$!1hZ6{0^c^yqN?b^(>w8L~%9gQlApt-{RGGWVQ2PLF?K6AcLUi%sr7jO3kOl89 z65EV1bDLUFjij35$uQ?yt=3bBoEL}(cHK$e9y&b<%dZ>VDf3>htLBsDDFFu*Z zK*D7DXFTUVX7g_!_fhC73^d8Jrepw`_s&Ny;8+x&ee~IKW^BYK)0Ie~&aZ&Ew~I^@ z71kY-t7mAMuUqeXlqvhPC!e%y&tGWg?rUY=fkWa(kum9oR76YH27!#bJs=wU&|~70 zX?;JGoK^e^%)LEkj8R_^YPCN`<~Ca7Ij`?^*lpin*CakV<3+{<0`atz>fvKW&E~J( zuo?Bcer$`^2APEK?fm)rcAx*-jXxk`%?MG+G-Jkc%YF-#NJ86f#yIn()HO$*#g8~+ zd1&e^yWRFDpP$EDs6Jxs!|3o);rZ3kV<*tf_e|t{MsUe5UcA`uYh1i^2|YG*j@Vj= zi3!E2^|kFbW8_O7Se;FyWxk4PZxkfo_2=FL%xVX|V*EL8yeGI8dh`8HnR=zxu3K^4 z?Tl%)_d2`(+RtcMvCWuNQ}`lapgjQM)RvdpSi6pf_mx@PA3gQr0)c{Wjp+6NF6Irs zL820t0ST#n`V1b$3tBcTaZ!+L{k*q75;0p3-dHV?<@DZ+G2q({GsfnWwM#`kaZCYc%YN);0tcIqxe~S22_Zd4^oi;xE1y)TF?#>ouYjo{^wp6J+R<)CHpf3u?96tF8RUGgV(bi-!3c zdDjGVQiNZ-uoCj zdR)5-_0QpRkGlU+{2ctxXOD)n>egdY{@AQnuoE&sl;o-+x6i@Q*jNe6gKVf1BC4vp zOk0}Gwr3HKK=&SaEBblcZ=$CG{@AmZ_bmmE^2rw~+swfr;K}Fd0YBNiRs3oK2wU)Z zfOe%dbma{aSyqwFQEBoa52dc}AhRtbMKNEmzV!jaA!yXp%z6DiUbnZ;;MQK@8%U zubLa~M8}Swq?pY7GXf1rV4q zDDOy2*FVX`1Z@Ej`H(mM;!9!?XmG7R`QjVuMe^@0{(|={Egv!(ZToGPb?t*S6=*EJ zXME$mPXviEwMEu#`agjy7uhPsq)g*mj8kQsE6;EsU+lsy5eqy%VPk*szNA#H3k8P;B3WV8iMG zAL^kt)NB&Ngu&|4_1|xGSWV69_22V)EKm*b{nlSvJqKtgcm}@jL*0&}mLNe1FtolA zVy-dJ4}}J*4Yk|F0MNAO=Gs*gBLs-XjGM}PkM}t8}FKMRr@^9KDXTW zAKvc(e>&#`OOPOJ@$RCfcK2Ou29U1riIBMDG`5$JbpUzAD6}c~i)VxkB0?pg*yW^c zk)411#duwO3EsJHf7opHKKS%2-U)%AAx*d4mMA&&6A&VpsMM984UbRJ+6*8`iZ&f< zpn4$zG;YdFr|PT$T4??|A2W4Gt@dFYcq=-5^f=?T4;}p=Z>`VMFD`Jpwfm3Fd_|bD zj$VB)^h`*}2W;>Hhy)S66Vyl(v3 zes{u#pHRRiR5~LjS*f=g3*rEjpvuYW3IJl_CfMWRyKh*F1;uWBpMls?ef@<_3m|1) z`6ZhGMAVbFM46p|zj$6q08M%3Wv6Uhz*mX^=56VUHB55{i0`!OUG^J+R<7OTbkAq4 zO0o?csJ>@{3{03eRx_Sf0Td<6QsFQEBcvBL`d^dL1p(@Tg%a?ppcf&ZX}a<538(>U zsk7(Kq4Ai*wN|zP0v+?~FF2PLx^LnPdjZtMm9~b(DRONFP=quUYN3w`2_R^cuvWp1r77NM)G6)s7O_B`3T0Al^c^ zUw2%amEW;*530U?EU!C1_pJ{d{(PIZ{LIVQ+M3FcX-jrtOhglGbhnlZgRTsrDt*mH zF#vSa-H$l*ErsHJSm4J8f*0q%+hSc1@S(TfU&5<}Du&)J=z6oZ%JGw@(3tU$37Slm zW)*M6n1~?QaJN!Wp9micNiC@QM2vC{i10e9VJ4W*d2fGcwHxdq9)LsP7GGf+WcsJi zp6@VI4LQ6#!HVqJ-ib*W1}NtUCD`BxP)tlr5BxJ&*{kwpvFd@~E#3XsKI(%DM3`?$ zFjN@YvVQB!Z@y)AN9614=!llY!0q_fr?scy6fEsYNY_K#yI_J1-g1s^5{U$sa0I~~ z3SyPCLVN{Q63~20;aWh9`OFWj-#TQ2c|CLHEEAUCU2lfnej!()S`!G7%&`(NZ(m7k z6^c{kJ`I>?3xEQpS%zU^uE>D5lxFyU>(ASHOE{pyur0yBH5)hct_m%{f1_DA2V>cH z$Zf(G)%U7Ev9gRYfC-xbB$LU2X$QolXbOZ*s9MS$k zpR6s}?;Q{TF(5y(x0uz{solwkBUAO&E5u&f3|;8O~Zm}gs8jmZc&?sLfy}ZJH^Pb-rBLkukEGEX2zm!X9k1Z~ZXG;?s)mi>UrdO>Yw!B41@A8A?MzlV><+YT z$1cI255`Q49zh&|R_ZEHbaKW$fCYjHcN@ENFhn{iB1V>lPj;L}k08i137M@2jRt#e z@h#!08F3dndCGng58cW5R)qpkr_P)sIDlrp{Dvr7AaFS_Sx)a$A<=P0zyb*(cC)p; z3y`HiEU~EtRcpi~(&pK3AcH~;F1vnfIByu?lP`r?9Si4JzG^+Msf6o6j!Lkw#4p=X zaotU#%mtIeU?b4b;x3+G!PBh`ZSJ~oBJ0)h2fLM#V{x|~T*y<~OO zMN4bH?5VNl%kYC1dT`Ryf~?4eY&&#&6`K286+q0dLXs5iTyUmBLqh{?CD6@0C^9k< zJhAYYl>3$m>pnTQ5Y|;+t{BGCaai!ltmr(bY{MwMUvH_a_CZ+~zKvvYA*2M^>5@Bhzq3R_;9V4J5SzJXynm~-ra z1+>?EU1i4n{h8h{39{^>*SI_h4FCaIT=M10F1KI&wQXhAGX1PY-|mtj&)WB4uJN4r zw8wl|ly@*hDkegrtWXv7yGV1}Z%9<`bAp~ijuKeZC`7Lxn`(cwC6~gY69&LsySaq~ zwb%P+2f}NR?(97eEtgnp$Y&o&QGX>+3sz(6Igj(@UEM_kk_GW0l$9dCBnHN=P}ghmhLG zA~MY&G`>e*V6IYEegJNSMs%8S>w6DE|6TM&rzX^3y1rh$LG-cYmMtf1iVpb(1n7zO z2^Ye3x4L43AT>EQC1(P#cZgup(n7EYg}vE&XU})RuF@2^Pm?0I4~k4mdjjTCZ0%#g zg_sn79F`P$cJa5YDXVRu1tM_kouN&P81m{{A2M}O;)2K2z-*$Dmj6AT!&EYt!D4Wq zRy{I5Kffr58HB`2`zdu5=V|82p#92bp6v)as{FqDPv+TZq%36F#q~iw8R9Gz%k$#X zLQKuHkB?6x{;5n<>z;%#I4uAHxx8=UbWwLYq%GhaOu=q@hRDPj=17rSh9vTg=V0#0 z9C9_!?rszgP7C?4EkAsq1-?p}S@<<{a-ijvL3_HTD^^q4u#SeTT(?P(rck!zyAo8o zwJ>L7?n232Qqexw5NfRXqFE9akT1{ey&vjHXn_dSJ=8yUbgv9nqrd`3vB9H;y}vYu zgFZg~g>1b~j~E)n*&3k^;!IggqUvTvUPTjaKJ?LNUolbYj--viU58Gw&_cLO#45w9 z)_G}5n|j8{#uC$&#IE-epEz4HWsr0W^Y-?Zfm%#Z{T2X3{>u!4xy|m!J z=;P0qcL;%AiZ_gTNc3?b(dNr?%zI*FnJ>T`k+}+M<96O+n=&XsVs0!gF+KkS*sPUi zl$z^r2#fnVf@F$VnrdmflzDwoTuRQTFgIk5dOFf{wPwl!*g6tsDM)%^rePHjHrgO^ ziDjyy0>!I!>+qaplDUZ`bLBA8)shx+zp{?ZCjo3M7L7F1xP^^Wn;J*}%O%vnV`_jG zI5Dl)&#(;&J15NC1e>KRy16;YVa|s_F+r0;l-f5SAU`>)=yw;08~`3>yY7NN@EjOm zF36mOIs@;q#)lxH8BT~=s()~JiA+{ih(L6BLQ5NochXGG(Ac`bGtW^AAry) z6?UnR%hl&|(cveUthm(N)jt0IMKFe5UjAvMmtnY>x7DFFPivaUlf)t*kr#(Sq=Nhm z@S+&G<|$cr@mb>PU*?LwUBGGX8h;taMye@18!1bl1!D$dM_$A@GNwH`BY0X0HbOKs zgw36KEASwsgBlJFi!;Tmd#!`aF}Gx>tC}@4bJYl%8MIEkI&VX8So8p5veIGfNd7T| zjHyRwGF!G(GzJpFmxu=h)Gz=kD@vL+DOppv58Qn-PwjG701^uvHm*aq+(t>6h67Pa zsZ)uUl}^Sgk&IoSBPt4=1wUG$Gcu36~g<6p#jS)g^iQrNL##*8D&T?#xc@giT6C62PtMw;NBF?CSO zBF`?pz(%n-7q*U6K6ZF*!*Lu&;{eZrXN^zI`8>F1bpIB#P81m{-_Fi=+NzDbN$et= zykWqNGQi!3K@5pZ7%oZ8`64;Hh9nrj5m?`E(04)p87N^SnGNfnx4FotD zWDFE!Ov1?+d3RN0&|r>#v;h2b=t;_{D^lE#SWrZD(iW$8p+q! zS0A06_BgDr8GL(MhT&@Us}qG!F2bR05nRG6sHK znd`Jy8+i~_?N17!qFD~$m11VvG+4BOk#WOf<(gNM()B;dv?cWnm>A7ux(ZO-+s}c@ zUJhk`4sy;Wj?Zv_;WQ0^My4&ThkJy34UCiwhkGaS9Ac^%jgv^8HIzKNx0!qH0*?Sd zA{vR|Nce5_WYj&p!H|g#i;f==Bg=RxA+6W?E)yuEDR}T08@#;#3pNuhw;6vgL?{&ioX%xV=lSZOt^QVRTX9$hXam}3pm09 z$%hPX2&r?Cu=yV^m4#M<3Ci{h3hf&aFTW>7p_v<(n!8G>G48^q<1|bxXesb`7+_(u zazzu>Srta(7;2gCLU%6!s3NZq)-WZfr5T1@ajCjha7}#ed;J1K%ZaARvd}gvlDm?S zX9;m>9C|?VB4PVL;+aH~Tu|~AFg0tYW&o0dW%lJSoTj#=tw0jQ^IDY22NdY1oFf%0}#JFNJg9 zb4`bH!nr*>Jo3r4vdFbLO~ZjEncQnMx%VLQEM6|)&;?R=;*oG#DaZ^=kQ;)Pmr97A zz~q@}C`(Xf6Ah6Ilkel>UxKwpMPNvHbwEgX4G8=jeg}Ue0LcS$Y4&|Hu&^422*hrb zj|K`T5 zvEu&kr?~JYsHgmN0NIn2aTn+aRJ9k!PJ8U-hv4^jUYrdmS}_oGTBmMTI8(8 z03a};B0~PpXcIa4tdx8=ft)LroI8SCE0|onhYK_v7fjvBqPuoO{)9hqzzQR# zC4vyzNCF0Pi6noEAfF9014WI zV2uq3g6f^x2G7c=p@RHqN*TgM%4|`s^UtkutYSaPk<{TxQ5pftG4D{HdAqOLZ#1v_ ze9M+5dsmQgQfV0(U&(S!!AFzvis49pCTa?3*#F3|c3c({E49|qiLo*tWAg7N2r?$H zceChvA3_;lB9B|DgITla;p_)_r>v>z1zcg0vl49vG;Ili>b(32*1hN??A7sM@$nr4 z8!M}P<^@Xi%U%oe11bF}T`A`>43CK-Qz^~WSp-#Hv2Q9-9^X94+}vz@Y^)g{BUOYV z_|+d(CAi?WUj6zyz~}lnkBZ=80;M3*LU zHGMlZ?()$(qVAfc|G0}(d&tSfx)|^Mu2H_=kb4o=Ap3@`Lp&B)cL!~H9PI7w*YctI zQdh5sK=8^5AG8P>#9Vyr+q9%EwH3HQk{XQFUw1_hfFE3734S2!^#qIgdS@@Q{Gn}V z&i9cg|N4u1hekL~)kUtMXQYP=0K1b;zvVq4 zRb1r#*7T38ib@M@JD6D*ec@F^uyytIxz!L&dH3FxrvZWb8BV**eALkmeW5?93@}@n z4gNan2F?-Ie_od^USuAI0%QWj1;%?cUgs$RzY?UxLayXoAPU~f29Th25OmAI z06!5@vgYvOQk6;7bal;{!x-3L@ZzNh{0cx{9p0)g1j+z7i}n8i$po2mA$9%`)fE!Czt%i%kp_d^qH20s4XnQst#a^y8a7?M5z z*L>NT7jYu?ICpgEQUYh_OrrtIc)wKx1p6)`I=;61<0)vR1JCOJwvBjC!)Mv`b#ol9Akg)gKB^lewze1bTfSn@{B`u_A zN)PUeMM_x{I^}mc;UI<%**ErSWv7bWZqZOYaL!Vhe~kgeP$S=_d##+rr~Y2Hh1>Lf zY=aYSLIB5kY+Q46%@wn%6eSeDTv`P&y|-w1o@Q>{3O~TqAV%Mfc7n9fmZEe)q(iKx^n9(NLb73Fz+c+s z!>K-8XvAo7Xl~E$nxjkY=8*HY3k8UR*tK@ktoRk(m_t4G*)CvnEHo5Mv^lI*I$~VT zuH0CQ&e0+^wcyj7d5)_2{MUw8@JEb14uhKmP;dz#w@0mHpB@zWPB$AE8802Ak?aBk z1M!fDJDr>(_(|mFqjVXEY-2j@TGY<*rK|h113ZR$)F9b)LOQJZhEwYNf%4CFbZX7r zL16#j)!2N6%HO@+Vja^$%=71~T?~9Gg$KI>#Wwff2WtS32+6IQEv;R6a?Q?f&t~sy z^?UKhaZ#>^yY+4h*)R!0Fyiwv!ursg*ef5>>?IAD*ns7x&BkByqWr2RWnuEC)*Vud z`9a0}20fROX5f7JsQ%t$N;zJM+&`J&In$Q}u+M=I{b7@g!`prSoyZpQ9TV;3(@D1e z%BI66KJyYBWhq#q@AQ!=m9Nvfnq z-SG?FyKF)enqlGZ8yZrUBOey84zNfN!yy;zjn1@HJvxz3-Fp z@Tz6QUll*eYHc^+v(f|F6?U8_{nr~jaIG0W?B=i6B3RcSto*bvBsbTM=A9BU-3Ah8 zNi`l$9?&GMo=FEwRv_xSgyGZtj9#@e-B5nrpw{?~zkgz73X_}cv)*W^Rr8w)YwNHc z*5Nn6f`7FA!KOwX(rWwMR7CG2XjL0w!d?(-NK_z;CDgW!? zm{={qDnSAQe=8Vg-umXT=L(@JFv-`qNgoa*CdglVGRag)CSpU(wYQsW`&k0q_mT*%_hS-?>#U4EO z2MC~jQ3U6aUEVZn`ZAr-q_#O-3f;~=QSZ=x?WSyg+?f9&^TYDzkb6XdslA>n+|$$Y z#wjomIx&A!XAHF_GVmq|e@koN>Yw2r^&$^Gl_#ddWR=6%jFpj99RV`jcPw{gQUrpP z&}y~JthsyUaj=yQDO|`!1pHEh$z()Rxx-4E66v=_sVbSZ*qEz&S3yM0K3<= zl(AIalVLR~ZN4IX$r$zP!ZB`rtk!neSg;~!`TZzT`@!UHZQV6$;7SKpBW2rrUV6x# zmbf#hIQ8SB>u=fyo$!2K@J^E%%R8%^DUW6^Ebq2+fLvKX@){F7?rY$=jVkSNr#m^S zUpAC=E)0=|)VsRj1l+j|KCG0J1K2@28(?-SzJW8yW`-j@8fz?sRj+*;$DojX-q@wYb}{2W8MP`wCr zpMJgOGt1}UL%B`+e1=bS5ru|!T&(Bpqim_)`YyB+;aZ#ewM>398;>NO39z+)EM@9I zzqa%gS5q)4Ws**y4RgHdAlxy?P#N69EqQ~}t7qX#A{`ZoNn=1A+!}QMkw>!0732x3 z`%S`@brK1YzOF-F&+{yjtW_BZrcDAx(tO-GN;yTY1tuOT<*hG12+Xe>ynLs0qchz{ z`%mg>lPr;0bC~$^CnR=xKR;P3OfpfJ$f|c)lUs?S0JW(^)lwEvC4)e}5}SI^v{!1$ zjqz@CVW6_>%7&F`sY3xz9P-J|lBlF}so2Y{lOpC+^`4$YhDLpp3!lSk@7KlW@%84X z*IvEA!*PC8@8D;8o1-I7vgw9B2}E<;Gq@mSZ&q9x(yG-(0CRJ;r zbr$E?ta2}89WD9k`z^Rc!N4GdALcn;R6#TJ15qv>piYcX@`jjXw~iJvrTm)BH$ zb%K;N2--lOR@QBD`&ZF+4es%d!air^&5bM>hfj5->g#UzXEdTl_hyn zIkQLs>{x-PlSZZM!^euTA~#MxCZTd_Kbjkq`Dn%=#g_vd*TXIuYU@v(d_{kZ;gK)u zziBr#l9lQ0LjnAl*orcD2VJ5{3NMwFco~orS-1~*AxKWOzTLAVmkWPoR%xPGNdu_q zz;1sj4r&=@sDnZO$2EB8H~guAjJd#c{W^O({#pLgMS7mAt2DrusXx<^*a&kdXI-_Y z_9j_9_oo7Ni?ojhH{T{3!6L3yVd(f2Q0Zr`E!UF-##p;v7n$b-e;v^A-o+ab? zlVwJ*Qt6gkF!g%V9M;PT-|U= znQZgx^I%KEj2c)s_Obx$c&fXdCv3`UHn5IUlIGXDmDJu$E7UeYpf5^wf`~WfT87s{$hui5G`USZ+r7zlb|e z{ZrEYyI`t?3$8$w!SQh-JJib09-`-O7ZU4W&ZGTrlS_{>=JI+%v?F3Tq4~1)esPKE zOiQEtW@?$T*;OTKv!Sl$WxW~6_9*!_N!^2IYUo+ypU1@6-e{dt%xSFE+(Fb`n{t+) z$HuFNv2x025j(+st&hXUa}gE1f(XrQ=B;Jhk8HVYcyj)MC0D)AaFV7l_3cKkrp89u z(05Bo#PXm6x=Pa_jB9=7rv$M%r5HsdnqMzLuKQArS-14ABcqZOrYyX~mfY?EWt(fm z(L+_F&V`mRF)}iS^LN5w6g}wbzz9&?o&7$8Y%p%*CHR^I$9f1*yUyH}zB4^i`c9)n z^IWRH4CDIwFT)hq3)>yRq6eP@ro(m*m$s4>KJU-QgKcLrPB2?_UE8C%l~~G<7O(TM zW$LTyd`im-CExf(S*NOi-sw_1p>6i4+&79YR+?)afxX5n4mIp$-P0wan9u#)Ul4SvZ5P^5 z*}dWjId8T<(NSMTCXWyZOnb$5cGAW?f`MWbibU$G>fOxR97aMitp0yYMP)?= z1O$K<=BD-n0)n+a_A!yelXun{$^rsE|6^eacZ`@^o{6gUa>5DRGx2`<)%*{W-(fiE zKNZgd&b|Bnp~hRX`A=CwbJ~tFFaEyeo|pUP4EcicV1wv|i;gmvUVb}SdG@R=&h?^h z3PSUksrkt}uuFf~%EQT?&f}||K|(rx9lY30_TJXsozA%7iJ(FQFNgw*A)ZB;o5OXk z2W9E{7_j|*?Y#`4wVAHYryQ%j!apO!ra!3)N5t{n=S%-`Z&9H|1ggSHaeG=c{YVqE z0nrZ>c$u-m#RjYlJ1__6P(^4W9s;ScgAR=zMOIH2>yAx`HB{r5^EgmL@|bsD=u7Gu zgacoB7^h};0J>#HNEt$s)qtqv*4c|ndX;#H76lzv<;Vxk6@#g{Gq4d5%WWY>Gi3f= zIKV2{dnC-DVoc|KC3NFn1|W?&GD3yrhBQpQn1h|7bczqvxu=CR)Jw7gbC+QwvaIEW zC>4WTKfgc&MmiUJlQ7QQ7}Hg!Ap(tTH@Vv9u#mW7!+x8dHoaYZt4=L{l<%ypU!D4= zAS@TennL1&=;?wmIgrc5%GX_FM5SRm$E04c%mXlGjC)%@wcw!V01?0j7n9{7EPdk=@ym z$AP&CIX2?G3azQ~&F_9DKcX+*Yo?D#h zeA!&ib)-h(S91c||CGiw5S6!M8UOe&d_fPoP1qgv7Ba~8Q*sj)a{=i8HuEbZsa{lu zz-=@kWR7|Y?HSQ%0n!>w;F9us#<{QLC86YcoYnBR1owfTyprh81G;RrC}Esl?1HMv zyb`o29Syq=(7zTFAfx&e4fE$uUZg#Gbh>4=KVyZb+cw~u&Y>qu?u{B68uE``QQG9r zmop-I-|3yLz{~j*d`H3pl^lfgr7-YvghZHlBpOn-tQ_R`!kd!$ea{=!*s5=R#cH z-w1Iv^D>#dtn;Vvc&R1_74NQLpe(P71gUjM=#4Y)q2ZEHM?~zI{U!rX9NTM&AWKD& zRIFnXMQePHcG5+0TeG)#;q}O}4)o5u8|2r*dn4MHKJkvE;lc?nL07p4^g0(ti$qOd z7G<#R+0qe+BXeJs7NmU%6*9-tL`>&b9%g`^JST1Uz_w8UNEKy?+`vpqU{b|pHs`^^ zOy72g#If!7q-y?+iQ`q2vKU=#xG*JW@36RQJ+$r7Kl0zN1}?qeOpvO-=|iob7Q=kZ z&;#HH%r!#0!Y3I8jiWidEi*IP7UD6bbASGI7)sp(zbVzYY8zrxL3tuVe`^QbFHLY! zu#-^Bj5!U65BGn8)`lVC>Y&Zf8rlFtB_ z)|g__N9i>0a%zB+Q*h3cNW}I$Tg3Lki5X{!^g@UdZ2)-J_jP}rAEQ0G?Yy7+Nv*sq z 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 F@PB1o0BisN literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..ef89bd5215ffcc38c68b119a7495a77a7084543b GIT binary patch literal 10893 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 literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/colors.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/colors.xml new file mode 100644 index 00000000..bf1bf200 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #2c3e50 + #1B3147 + #3498db + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/ic_launcher_background.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/ic_launcher_background.xml new file mode 100644 index 00000000..6ec24e64 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #2C3E50 + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/strings.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/strings.xml new file mode 100644 index 00000000..c8ee97ca --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/strings.xml @@ -0,0 +1,5 @@ + + + LaunchDarkly.XamarinSdk.Android.Tests + Settings + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml new file mode 100644 index 00000000..dde52b81 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file From 122cef105322b9c67fa2a8d34c8c9fdec85b7660 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 18:12:53 -0700 Subject: [PATCH 127/499] try building the Android test project --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f2235d1..6ae5f99f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,8 +33,6 @@ jobs: - run: sudo apt install apt-transport-https dirmngr gnupg ca-certificates && sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - run: echo y | sudo apt install mono-devel nuget libzip4 - - run: nuget restore - - restore_cache: key: xamarin-android-cache-v9-2-99-172 - run: ./scripts/check_xamarin_android_cache.sh @@ -52,6 +50,10 @@ jobs: name: Build SDK command: msbuild /restore /p:TargetFramework=MonoAndroid81 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + - run: + name: Build test project + command: msbuild /restore tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + test-ios: macos: xcode: "10.2.1" From fe45d9590c21561655114607fead687b4f4bc9c1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 18:14:20 -0700 Subject: [PATCH 128/499] better organization of commands into labeled groups --- .circleci/config.yml | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ae5f99f..c83cd982 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,24 +27,35 @@ jobs: steps: - checkout - - run: sdkmanager "system-images;android-24;default;armeabi-v7a" || true - - run: sdkmanager --licenses + - run: + name: Install Android SDK tools + command: | + sdkmanager "system-images;android-24;default;armeabi-v7a" || true + sdkmanager --licenses - - run: sudo apt install apt-transport-https dirmngr gnupg ca-certificates && sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - - run: echo y | sudo apt install mono-devel nuget libzip4 + - run: + name: Install Mono tools + command: | + sudo apt install apt-transport-https dirmngr gnupg ca-certificates && sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update + echo y | sudo apt install mono-devel nuget libzip4 - restore_cache: key: xamarin-android-cache-v9-2-99-172 - - run: ./scripts/check_xamarin_android_cache.sh + - run: + name: Download Xamarin Android tools if not already cached + command: ./scripts/check_xamarin_android_cache.sh - save_cache: key: xamarin-android-cache-v9-2-99-172 paths: - ~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release - - run: sudo mkdir "/usr/lib/xamarin.android" && sudo mkdir "/usr/lib/mono/xbuild/Xamarin/" - - run: cd ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release && sudo cp -a "bin/Debug/lib/xamarin.android/." "/usr/lib/xamarin.android/" - - run: rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - - run: sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" && sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" + - run: + name: Move tools to expected locations + command: | + sudo mkdir "/usr/lib/xamarin.android" && sudo mkdir "/usr/lib/mono/xbuild/Xamarin/" + cd ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release && sudo cp -a "bin/Debug/lib/xamarin.android/." "/usr/lib/xamarin.android/" + rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" + sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" && sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - run: name: Build SDK From 7a9282546604185637d5a66d968808add733f1c4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 18:22:27 -0700 Subject: [PATCH 129/499] make commands a little more readable --- .circleci/config.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c83cd982..a8e8ec01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,8 +36,10 @@ jobs: - run: name: Install Mono tools command: | - sudo apt install apt-transport-https dirmngr gnupg ca-certificates && sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - echo y | sudo apt install mono-devel nuget libzip4 + sudo apt -y install apt-transport-https dirmngr gnupg ca-certificates + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF + echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update + sudo apt -y install mono-devel nuget libzip4 - restore_cache: key: xamarin-android-cache-v9-2-99-172 @@ -55,7 +57,8 @@ jobs: sudo mkdir "/usr/lib/xamarin.android" && sudo mkdir "/usr/lib/mono/xbuild/Xamarin/" cd ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release && sudo cp -a "bin/Debug/lib/xamarin.android/." "/usr/lib/xamarin.android/" rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" && sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" + sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" + sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - run: name: Build SDK From 8db5b2a3b6b5150b5680a3dca46bdfa7ddc282d8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 18:40:40 -0700 Subject: [PATCH 130/499] build with xabuild --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a8e8ec01..642a4551 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,9 @@ jobs: - run: name: Build test project - command: msbuild /restore tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an + # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild test-ios: macos: From 4f674eece40605cbc89cb1c556675afae4ec799c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 19:11:28 -0700 Subject: [PATCH 131/499] rm autogenerated UI file that for some reason broke the build --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 1 - .../Properties/AndroidManifest.xml | 2 +- .../Resources/Resource.designer.cs | 3 --- .../Resources/values/styles.xml | 10 ---------- 4 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 0202bfd4..b93b875b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -121,7 +121,6 @@ - diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml index 4e00401d..c5781e4c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs index a119fbb2..f352a1ff 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -6218,9 +6218,6 @@ public partial class Style // aapt resource value: 0x7f0c016e public const int Animation_Design_BottomSheetDialog = 2131493230; - // aapt resource value: 0x7f0c018f - public const int AppTheme = 2131493263; - // aapt resource value: 0x7f0c00a9 public const int Base_AlertDialog_AppCompat = 2131493033; diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml deleted file mode 100644 index dde52b81..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/values/styles.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file From fe262f37635316024a760693ab556ba0a28762b4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 19:20:09 -0700 Subject: [PATCH 132/499] auto-restore --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 642a4551..796d65bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ jobs: - run: name: Build test project - command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild /restore tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild From b41f585183c5769344d843c410e8ea3313ee49ee Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 19:20:33 -0700 Subject: [PATCH 133/499] misc cleanup --- .../Assets/AboutAssets.txt | 19 -------- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 10 +---- .../MainActivity.cs | 3 +- .../Resources/AboutResources.txt | 44 ------------------- .../AppDelegate.cs | 3 +- 5 files changed, 3 insertions(+), 76 deletions(-) delete mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt delete mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt deleted file mode 100644 index b0633374..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Assets/AboutAssets.txt +++ /dev/null @@ -1,19 +0,0 @@ -Any raw assets you want to be deployed with your application can be placed in -this directory (and child directories) and given a Build Action of "AndroidAsset". - -These files will be deployed with you package and will be accessible using Android's -AssetManager, like this: - -public class ReadAsset : Activity -{ - protected override void OnCreate (Bundle bundle) - { - base.OnCreate (bundle); - - InputStream input = Assets.Open ("my_asset.txt"); - } -} - -Additionally, some Android functions will automatically load asset files: - -Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index b93b875b..ad9e5b13 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -110,9 +110,7 @@ - - @@ -161,6 +159,7 @@ + @@ -169,11 +168,4 @@ - \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs index ea291a39..d511c86c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs @@ -15,8 +15,7 @@ protected override void OnCreate(Bundle bundle) AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); AddTestAssembly(Assembly.GetExecutingAssembly()); - AutoStart = true; - //TerminateAfterExecution = true; + AutoStart = true; // this is necessary in order for the CI test job to work base.OnCreate(bundle); } diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt deleted file mode 100644 index c2bca974..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/AboutResources.txt +++ /dev/null @@ -1,44 +0,0 @@ -Images, layout descriptions, binary blobs and string dictionaries can be included -in your application as resource files. Various Android APIs are designed to -operate on the resource IDs instead of dealing with images, strings or binary blobs -directly. - -For example, a sample Android app that contains a user interface layout (main.axml), -an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) -would keep its resources in the "Resources" directory of the application: - -Resources/ - drawable/ - icon.png - - layout/ - main.axml - - values/ - strings.xml - -In order to get the build system to recognize Android resources, set the build action to -"AndroidResource". The native Android APIs do not operate directly with filenames, but -instead operate on resource IDs. When you compile an Android application that uses resources, -the build system will package the resources for distribution and generate a class called "R" -(this is an Android convention) that contains the tokens for each one of the resources -included. For example, for the above Resources layout, this is what the R class would expose: - -public class R { - public class drawable { - public const int icon = 0x123; - } - - public class layout { - public const int main = 0x456; - } - - public class strings { - public const int first_string = 0xabc; - public const int second_string = 0xbcd; - } -} - -You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main -to reference the layout/main.axml file, or R.strings.first_string to reference the first -string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs index fa931387..46ae1823 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AppDelegate.cs @@ -20,8 +20,7 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options) AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); AddTestAssembly(Assembly.GetExecutingAssembly()); - AutoStart = true; - TerminateAfterExecution = true; + AutoStart = true; // this is necessary in order for the CI test job to work return base.FinishedLaunching(app, options); } From fdc1179153871cb5c3bd304cdcedfb6cdbbd2cf0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 19:24:18 -0700 Subject: [PATCH 134/499] actually run the tests --- .circleci/config.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 796d65bb..25d5f256 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,6 +70,31 @@ jobs: # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild + - run: + name: Set up emulator + command: echo no | avdmanager create avd -n xm-android -f -k "system-images;android-24;default;armeabi-v7a" + + - run: + name: Start emulator + command: emulator -avd xm-android -netdelay none -netspeed full -no-audio -no-window -no-snapshot -use-system-libs -no-boot-anim + background: true + timeout: 1200 + no_output_timeout: 20m + + - run: + name: Start capturing log output + command: adb logcat + background: true + no_output_timeout: 5m + + - run: + name: Deploy app to emulator + command: adb install Droid/bin/Debug/com.launchdarkly.xamarin_mobile_restwrapper-Signed.apk + + - run: + name: Start app in emulator + command: adb shell monkey -p com.launchdarkly.xamarinandroidtests 1 + test-ios: macos: xcode: "10.2.1" From 0744bd25b9a7f87a155bcbb7d88030c98c90f0c5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 26 Jun 2019 19:37:49 -0700 Subject: [PATCH 135/499] install libpulse0 --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 25d5f256..2b98ee6f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,8 @@ jobs: sudo apt -y install apt-transport-https dirmngr gnupg ca-certificates sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - sudo apt -y install mono-devel nuget libzip4 + sudo apt -y install mono-devel nuget libzip4 libpulse0 + # libpulse0 is required to run the emulator; the result are required to build the app - restore_cache: key: xamarin-android-cache-v9-2-99-172 From 5c9b1c709c16351cd7ebf53de6270ae4826af105 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 27 Jun 2019 15:42:22 -0700 Subject: [PATCH 136/499] misc Android CI fixes --- .circleci/config.yml | 8 ++++++-- scripts/android-wait-for-boot.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100755 scripts/android-wait-for-boot.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 2b98ee6f..9e892890 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ jobs: - run: name: Build test project - command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild /restore tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild /restore /t:SignAndroidPackage tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild @@ -82,6 +82,10 @@ jobs: timeout: 1200 no_output_timeout: 20m + - run: + name: Wait for emulator to become available + command: ./scripts/android-wait-for-boot.sh + - run: name: Start capturing log output command: adb logcat @@ -90,7 +94,7 @@ jobs: - run: name: Deploy app to emulator - command: adb install Droid/bin/Debug/com.launchdarkly.xamarin_mobile_restwrapper-Signed.apk + command: adb install tests/LaunchDarkly.XamarinSdk.Android.Tests/bin/Debug/com.launchdarkly.xamarinandroidtests-Signed.apk - run: name: Start app in emulator diff --git a/scripts/android-wait-for-boot.sh b/scripts/android-wait-for-boot.sh new file mode 100755 index 00000000..abf11812 --- /dev/null +++ b/scripts/android-wait-for-boot.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# used only for CI build + +# Origin from https://github.com/travis-ci/travis-cookbooks/blob/master/community-cookbooks/android-sdk/files/default/android-wait-for-emulator + +set +e + +bootanim="" +failcounter=0 +timeout_in_sec=360 + +echo -n "Waiting for emulator to start" + +until [[ "$bootanim" =~ "stopped" ]]; do + bootanim=`adb shell getprop init.svc.bootanim 2>&1 &` + if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" + || "$bootanim" =~ "running" ]]; then + let "failcounter += 1" + echo -n "." + if [[ $failcounter -gt $timeout_in_sec ]]; then + echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" + exit 1 + fi + fi + sleep 2 +done + +echo " ready" From 9d6eb2e755027506e4bc8791423c30fa8264e6ea Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 27 Jun 2019 16:14:48 -0700 Subject: [PATCH 137/499] better test log output --- .circleci/config.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e892890..b9f59b3f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: - run: name: Start capturing log output - command: adb logcat + command: adb logcat | grep 'mono-stdout:' >test-run.log background: true no_output_timeout: 5m @@ -100,6 +100,19 @@ jobs: name: Start app in emulator command: adb shell monkey -p com.launchdarkly.xamarinandroidtests 1 + - run: + name: Wait for tests to finish running + # https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found + command: "( tail -f -c+0 test-run.log & ) | grep -q 'Tests run:'" + + - run: + name: Show all test output + command: | + cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,7- + if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi + # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a + # JUnit-compatible test results file; you'll just have to look at the output. + test-ios: macos: xcode: "10.2.1" From 3c4cbc4f0d2bbb6156b0c606ecfc26c9fb424090 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 28 Jun 2019 11:00:35 -0700 Subject: [PATCH 138/499] better log capturing --- .circleci/config.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9f59b3f..681223b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,9 +88,10 @@ jobs: - run: name: Start capturing log output - command: adb logcat | grep 'mono-stdout:' >test-run.log + command: adb logcat mono-stdout:D *:S | tee test-run.log + # mono-stdout is the default tag for standard output from a Xamarin app - that's where our test runner output goes background: true - no_output_timeout: 5m + no_output_timeout: 10m - run: name: Deploy app to emulator @@ -158,7 +159,7 @@ jobs: - run: name: Start capturing log output - command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.XamarinSdk.iOS.Tests"' >test-run.log + command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.XamarinSdk.iOS.Tests"' | tee test-run.log background: true - run: From 72cfdcba822878ce341f3ca567842ca669847e35 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 28 Jun 2019 14:31:33 -0700 Subject: [PATCH 139/499] skip Android tests that use WireMock (for now) + misc test fixes --- .../Properties/AndroidManifest.xml | 1 + .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 53 ++++++++++++++----- .../FeatureFlagRequestorTests.cs | 8 +-- .../LDClientEndToEndTests.cs | 18 +++---- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml index c5781e4c..e6e74d18 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AndroidManifest.xml @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index 36ee2c06..1a1849a6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Common.Logging; using WireMock.Server; @@ -9,6 +10,16 @@ namespace LaunchDarkly.Xamarin.Tests [Collection("serialize all tests")] public class BaseTest : IDisposable { +#if __ANDROID__ + // WireMock.Net currently doesn't work on Android, so we can't run tests that need an embedded HTTP server. + // Mark such tests with [Fact(Skip = SkipIfCannotCreateHttpServer)] or [Theory(Skip = SkipIfCannotCreateHttpServer)] + // and they'll be skipped on Android (but not on other platforms, because "Skip = null" is a no-op). + // https://github.com/WireMock-Net/WireMock.Net/issues/292 + public const string SkipIfCannotCreateHttpServer = "can't run this test because we can't create an embedded HTTP server on this platform; see BaseTest.cs"; +#else + public const string SkipIfCannotCreateHttpServer = null; +#endif + public BaseTest() { LogManager.Adapter = new LogSinkFactoryAdapter(); @@ -22,7 +33,7 @@ public void Dispose() protected void WithServer(Action a) { - var s = FluentMockServer.Start(); + var s = MakeServer(); try { a(s); @@ -33,17 +44,35 @@ protected void WithServer(Action a) } } - protected async Task WithServerAsync(Func a) - { - var s = FluentMockServer.Start(); - try - { - await a(s); - } - finally - { + protected async Task WithServerAsync(Func a) + { + var s = MakeServer(); + try + { + await a(s); + } + finally + { s.Stop(); - } - } + } + } + + protected FluentMockServer MakeServer() + { +#pragma warning disable RECS0110 // Condition is always 'true' or always 'false' + if (SkipIfCannotCreateHttpServer != null) + { + // Until WireMock.Net supports all of our platforms, we'll need to mark any tests that use an embedded server + // with [ConditionalFact(Condition = TestCondition.CanRunHttpServer)] or [ConditionalFact(Condition = TestCondition.CanRunHttpServer)] + // instead of [Fact] or [Theory]; otherwise, you'll see this error. + throw new Exception("tried to create an embedded HTTP server on a platform that doesn't support it; see BaseTest.cs"); + } +#pragma warning restore RECS0110 // Condition is always 'true' or always 'false' + + // currently we don't need to customize any server settings +#pragma warning disable CS0162 // Unreachable code detected + return FluentMockServer.Start(); +#pragma warning restore CS0162 // Unreachable code detected + } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 5c0dd584..9788ef5d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -16,7 +16,7 @@ public class FeatureFlagRequestorTests : BaseTest private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync() { await WithServerAsync(async server => @@ -42,7 +42,7 @@ await WithServerAsync(async server => }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public async Task GetFlagsUsesCorrectUriAndMethodInGetModeWithReasonsAsync() { await WithServerAsync(async server => @@ -68,7 +68,7 @@ await WithServerAsync(async server => }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() { await WithServerAsync(async server => @@ -97,7 +97,7 @@ await WithServerAsync(async server => }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() { await WithServerAsync(async server => diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 37dd9052..0e172ad9 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -39,7 +39,7 @@ public class LdClientEndToEndTests : BaseTest { new object[] { UpdateMode.Streaming } } }; - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public void InitGetsFlagsSync(UpdateMode mode) { @@ -56,7 +56,7 @@ public void InitGetsFlagsSync(UpdateMode mode) }); } - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public async Task InitGetsFlagsAsync(UpdateMode mode) { @@ -72,7 +72,7 @@ await WithServerAsync(async server => }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public void InitCanTimeOutSync() { WithServer(server => @@ -93,7 +93,7 @@ public void InitCanTimeOutSync() }); } - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public void InitFailsOn401Sync(UpdateMode mode) { @@ -121,7 +121,7 @@ public void InitFailsOn401Sync(UpdateMode mode) }); } - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public async Task InitFailsOn401Async(UpdateMode mode) { @@ -144,7 +144,7 @@ await WithServerAsync(async server => }); } - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { @@ -172,7 +172,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) }); } - [Theory] + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) { @@ -200,7 +200,7 @@ await WithServerAsync(async server => }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public void OfflineClientUsesCachedFlagsSync() { WithServer(server => @@ -226,7 +226,7 @@ public void OfflineClientUsesCachedFlagsSync() }); } - [Fact] + [Fact(Skip = SkipIfCannotCreateHttpServer)] public async Task OfflineClientUsesCachedFlagsAsync() { await WithServerAsync(async server => From bbdf0f205cb063120942eef2600bf773efe7bf97 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 28 Jun 2019 15:00:34 -0700 Subject: [PATCH 140/499] rm unused import --- tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index 1a1849a6..e020d44e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Common.Logging; using WireMock.Server; From 47011c1c38ce6e1de8e3f352514e780564f04c07 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 11:58:25 -0700 Subject: [PATCH 141/499] use CircleCI helper script --- .circleci/config.yml | 2 +- scripts/android-wait-for-boot.sh | 29 ----------------------------- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100755 scripts/android-wait-for-boot.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 681223b3..b371d428 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,7 +84,7 @@ jobs: - run: name: Wait for emulator to become available - command: ./scripts/android-wait-for-boot.sh + command: circle-android wait-for-boot - run: name: Start capturing log output diff --git a/scripts/android-wait-for-boot.sh b/scripts/android-wait-for-boot.sh deleted file mode 100755 index abf11812..00000000 --- a/scripts/android-wait-for-boot.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# used only for CI build - -# Origin from https://github.com/travis-ci/travis-cookbooks/blob/master/community-cookbooks/android-sdk/files/default/android-wait-for-emulator - -set +e - -bootanim="" -failcounter=0 -timeout_in_sec=360 - -echo -n "Waiting for emulator to start" - -until [[ "$bootanim" =~ "stopped" ]]; do - bootanim=`adb shell getprop init.svc.bootanim 2>&1 &` - if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" - || "$bootanim" =~ "running" ]]; then - let "failcounter += 1" - echo -n "." - if [[ $failcounter -gt $timeout_in_sec ]]; then - echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" - exit 1 - fi - fi - sleep 2 -done - -echo " ready" From 1f28e35830f9802167ca3673862be5b0abf85030 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 12:38:49 -0700 Subject: [PATCH 142/499] comment --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b371d428..25ade6af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,6 +85,8 @@ jobs: - run: name: Wait for emulator to become available command: circle-android wait-for-boot + # this script is provided in the CircleCI Android images: + # https://raw.githubusercontent.com/circleci/circleci-images/master/android/bin/circle-android - run: name: Start capturing log output From fdef9dd97b4508dcee00b960b12795cc7749bc74 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 14:26:03 -0700 Subject: [PATCH 143/499] Implement unique anon user key in .NET Standard --- .../DefaultDeviceInfo.shared.cs | 14 +++++++ .../DeviceInfo.shared.cs | 13 ------- src/LaunchDarkly.XamarinSdk/Factory.shared.cs | 2 +- .../LaunchDarkly.XamarinSdk.csproj | 18 ++++++--- .../ClientIdentifier.android.cs | 13 +++++++ .../PlatformSpecific/ClientIdentifier.ios.cs | 13 +++++++ .../ClientIdentifier.netstandard.cs | 14 +++++++ .../ClientIdentifier.shared.cs | 37 +++++++++++++++++++ .../LDClientEndToEndTests.cs | 30 +++++++++++++++ 9 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs new file mode 100644 index 00000000..704f5523 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs @@ -0,0 +1,14 @@ +using LaunchDarkly.Xamarin.PlatformSpecific; + +namespace LaunchDarkly.Xamarin +{ + // This just delegates to the conditionally-compiled code in LaunchDarkly.Xamarin.PlatformSpecific. + // The only reason it is a pluggable component is for unit tests; we don't currently expose IDeviceInfo. + public class DefaultDeviceInfo : IDeviceInfo + { + public string UniqueDeviceId() + { + return ClientIdentifier.PlatformGetOrCreateClientId(); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs deleted file mode 100644 index 1d576528..00000000 --- a/src/LaunchDarkly.XamarinSdk/DeviceInfo.shared.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Plugin.DeviceInfo; - -namespace LaunchDarkly.Xamarin -{ - public class DeviceInfo : IDeviceInfo - { - public string UniqueDeviceId() - { - return CrossDeviceInfo.Current.Id; - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Factory.shared.cs b/src/LaunchDarkly.XamarinSdk/Factory.shared.cs index 8815fe43..3e98be0f 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.shared.cs @@ -81,7 +81,7 @@ internal static IPersistentStorage CreatePersistentStorage(Configuration configu internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) { - return configuration.DeviceInfo ?? new DeviceInfo(); + return configuration.DeviceInfo ?? new DefaultDeviceInfo(); } internal static IFeatureFlagListenerManager CreateFeatureFlagListenerManager(Configuration configuration) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 83070170..f9ec405f 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -23,49 +23,57 @@ - - - - - + + + + + + + + + + + + + diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs new file mode 100644 index 00000000..444a32e2 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -0,0 +1,13 @@ +using Plugin.DeviceInfo; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class ClientIdentifier + { + // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. + public static string PlatformGetOrCreateClientId() + { + return CrossDeviceInfo.Current.Id(); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs new file mode 100644 index 00000000..444a32e2 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -0,0 +1,13 @@ +using Plugin.DeviceInfo; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class ClientIdentifier + { + // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. + public static string PlatformGetOrCreateClientId() + { + return CrossDeviceInfo.Current.Id(); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs new file mode 100644 index 00000000..feae56e0 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs @@ -0,0 +1,14 @@ + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class ClientIdentifier + { + // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. + // Instead, we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a + // user key for this (OS) user account, and if not, generate a randomized ID and cache it. + public static string PlatformGetOrCreateClientId() + { + return GetOrCreateRandomizedClientId(); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs new file mode 100644 index 00000000..8f418bb9 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -0,0 +1,37 @@ +using System; +using LaunchDarkly.Xamarin.Preferences; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class ClientIdentifier + { + private const string PreferencesAnonUserIdKey = "anonUserId"; + + public static string GetOrCreateClientId() + { + return PlatformGetOrCreateClientId(); + } + + // Used only for testing, to keep previous calls to GetOrCreateRandomizedClientId from affecting test state. + // On mobile platforms this has no effect. + internal static void ClearCachedClientId() + { + Preferences.Preferences.Clear(PreferencesAnonUserIdKey); + } + + private static string GetOrCreateRandomizedClientId() + { + // On non-mobile platforms, there may not be an OS-based notion of a device identifier. Instead, + // we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a user key + // for this user account (OS user, that is), and if not, generate a randomized ID and cache it. + string cachedKey = Preferences.Preferences.Get(PreferencesAnonUserIdKey, null); + if (cachedKey != null) + { + return cachedKey; + } + string guid = Guid.NewGuid().ToString(); + Preferences.Preferences.Set(PreferencesAnonUserIdKey, guid); + return guid; + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 0e172ad9..183c7dc5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Common.Logging; using LaunchDarkly.Client; +using LaunchDarkly.Xamarin.PlatformSpecific; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WireMock.Server; @@ -144,6 +145,35 @@ await WithServerAsync(async server => }); } + [Fact(Skip = SkipIfCannotCreateHttpServer)] + public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + await WithServerAsync(async server => + { + server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + var anonUser = User.WithKey(null).AndAnonymous(true); + + // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, + // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. + ClientIdentifier.ClearCachedClientId(); + + string generatedKey = null; + using (var client = await TestUtil.CreateClientAsync(config, anonUser)) + { + Assert.NotNull(client.User.Key); + generatedKey = client.User.Key; + } + + using (var client = await TestUtil.CreateClientAsync(config, anonUser)) + { + Assert.Equal(generatedKey, client.User.Key); + } + }); + } + [Theory(Skip = SkipIfCannotCreateHttpServer)] [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) From 18339bd4ad6b06ad2b9e63cca233c022573c9c47 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 14:34:27 -0700 Subject: [PATCH 144/499] syntax error --- .../PlatformSpecific/ClientIdentifier.android.cs | 2 +- .../PlatformSpecific/ClientIdentifier.ios.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs index 444a32e2..7d3d398b 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -7,7 +7,7 @@ internal static partial class ClientIdentifier // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. public static string PlatformGetOrCreateClientId() { - return CrossDeviceInfo.Current.Id(); + return CrossDeviceInfo.Current.Id; } } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs index 444a32e2..7d3d398b 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -7,7 +7,7 @@ internal static partial class ClientIdentifier // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. public static string PlatformGetOrCreateClientId() { - return CrossDeviceInfo.Current.Id(); + return CrossDeviceInfo.Current.Id; } } } From 08e48dd76e59701e62f2b283f7fd2b0c0eb14c6e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 14:38:17 -0700 Subject: [PATCH 145/499] list tests in .NET Standard --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 25ade6af..6432a0c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - checkout - run: dotnet restore - run: dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 - - run: dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 + - run: dotnet test -v=normal tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 test-android: docker: From ee2dd4dce1611930d08ec4ea99fd8e83fa1a915b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 14:56:11 -0700 Subject: [PATCH 146/499] suppress ASP.NET debug output in iOS --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6432a0c7..815ea4b2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -147,7 +147,7 @@ jobs: - run: name: Build test project - command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj -p:MtouchExtraArgs="--setenv:ASPNETCORE_SUPPRESSSTATUSMESSAGES=true" - run: name: Start simulator From b0b991019977ea07a3cdae2e5650d72d823e29c8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 15:07:39 -0700 Subject: [PATCH 147/499] Revert "suppress ASP.NET debug output in iOS" This reverts commit ee2dd4dce1611930d08ec4ea99fd8e83fa1a915b. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 815ea4b2..6432a0c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -147,7 +147,7 @@ jobs: - run: name: Build test project - command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj -p:MtouchExtraArgs="--setenv:ASPNETCORE_SUPPRESSSTATUSMESSAGES=true" + command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - run: name: Start simulator From 6b731901654837328ce31f05378e545731d17379 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 15:15:09 -0700 Subject: [PATCH 148/499] fix test state cleanup --- .../PlatformSpecific/ClientIdentifier.shared.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs index 8f418bb9..81e6b116 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -16,7 +16,7 @@ public static string GetOrCreateClientId() // On mobile platforms this has no effect. internal static void ClearCachedClientId() { - Preferences.Preferences.Clear(PreferencesAnonUserIdKey); + Preferences.Preferences.Remove(PreferencesAnonUserIdKey); } private static string GetOrCreateRandomizedClientId() From 7025940962a4933f248ddab6d0b543281f25d73d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 15:30:37 -0700 Subject: [PATCH 149/499] add lower-level unit test for device ID --- .../DefaultDeviceInfoTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs new file mode 100644 index 00000000..e376326f --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs @@ -0,0 +1,26 @@ +using LaunchDarkly.Xamarin.PlatformSpecific; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + // The DefaultDeviceInfo functionality is also tested by LdClientEndToEndTests.InitWithKeylessAnonUserAddsKeyAndReusesIt(), + // which is a more realistic test since it uses a full client instance. However, currently LdClientEndToEndTests can't be + // run on every platform, so we'll also test the lower-level logic here. + public class DefaultDeviceInfoTests : BaseTest + { + [Fact] + public void UniqueDeviceIdGeneratesStableValue() + { + // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, + // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. + ClientIdentifier.ClearCachedClientId(); + + var ddi = new DefaultDeviceInfo(); + var id0 = ddi.UniqueDeviceId(); + Assert.NotNull(id0); + + var id1 = ddi.UniqueDeviceId(); + Assert.Equal(id0, id1); + } + } +} From 807428d5f19f59fe0bbf8c4133901e8f775bb002 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 18:11:27 -0700 Subject: [PATCH 150/499] clean up structure of conditionally compiled code --- .../{AsyncUtils.shared.cs => AsyncUtils.cs} | 0 ...ientRunMode.shared.cs => ClientRunMode.cs} | 0 ...nfiguration.shared.cs => Configuration.cs} | 0 .../Connectivity/Connectivity.shared.enums.cs | 42 ------------------- .../{Constants.shared.cs => Constants.cs} | 0 ...iceInfo.shared.cs => DefaultDeviceInfo.cs} | 0 ....shared.cs => DefaultPersistentStorage.cs} | 6 +-- .../{Extensions.shared.cs => Extensions.cs} | 0 .../{Factory.shared.cs => Factory.cs} | 0 .../{FeatureFlag.shared.cs => FeatureFlag.cs} | 0 ...hared.cs => FeatureFlagListenerManager.cs} | 0 ...stor.shared.cs => FeatureFlagRequestor.cs} | 0 ...eManager.shared.cs => FlagCacheManager.cs} | 0 ...anager.shared.cs => IConnectionManager.cs} | 0 .../{IDeviceInfo.shared.cs => IDeviceInfo.cs} | 0 ...ener.shared.cs => IFeatureFlagListener.cs} | 0 ...ared.cs => IFeatureFlagListenerManager.cs} | 0 ...Manager.shared.cs => IFlagCacheManager.cs} | 0 ...ileClient.shared.cs => ILdMobileClient.cs} | 0 ...tion.shared.cs => IMobileConfiguration.cs} | 0 ...or.shared.cs => IMobileUpdateProcessor.cs} | 0 ...torage.shared.cs => IPersistentStorage.cs} | 0 ...rFlagCache.shared.cs => IUserFlagCache.cs} | 0 .../LaunchDarkly.XamarinSdk.csproj | 6 +-- .../{LdClient.shared.cs => LdClient.cs} | 9 ++-- ...r.shared.cs => MobileConnectionManager.cs} | 7 ++-- ...or.shared.cs => MobilePollingProcessor.cs} | 0 ...ared.cs => MobileSideClientEnvironment.cs} | 0 ....shared.cs => MobileStreamingProcessor.cs} | 0 .../BackgroundDetection.android.cs | 2 +- .../BackgroundDetection.ios.cs | 2 +- .../BackgroundDetection.netstandard.cs | 2 +- .../BackgroundDetection.shared.cs | 2 +- .../ClientIdentifier.shared.cs | 7 ++-- .../Connectivity.android.cs | 2 +- .../Connectivity.ios.cs | 2 +- .../Connectivity.ios.reachability.cs | 0 .../Connectivity.netstandard.cs | 2 +- .../Connectivity.shared.cs | 20 ++++++++- .../Permissions.android.cs | 2 +- .../Permissions.shared.cs} | 2 +- .../Platform.android.cs | 2 +- .../Platform.shared.cs | 2 +- .../Preferences.android.cs | 2 +- .../Preferences.ios.cs | 2 +- .../Preferences.netstandard.cs | 2 +- .../Preferences.shared.cs | 2 +- .../UserMetadata.android.cs | 2 +- .../UserMetadata.ios.cs | 2 +- .../UserMetadata.netstandard.cs | 2 +- .../UserMetadata.shared.cs | 2 +- ...AssemblyInfo.shared.cs => AssemblyInfo.cs} | 0 ...Cache.shared.cs => UserFlagDeviceCache.cs} | 0 ...che.shared.cs => UserFlagInMemoryCache.cs} | 0 .../{ValueType.shared.cs => ValueType.cs} | 0 55 files changed, 55 insertions(+), 80 deletions(-) rename src/LaunchDarkly.XamarinSdk/{AsyncUtils.shared.cs => AsyncUtils.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{ClientRunMode.shared.cs => ClientRunMode.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{Configuration.shared.cs => Configuration.cs} (100%) delete mode 100644 src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs rename src/LaunchDarkly.XamarinSdk/{Constants.shared.cs => Constants.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{DefaultDeviceInfo.shared.cs => DefaultDeviceInfo.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{DefaultPersistentStorage.shared.cs => DefaultPersistentStorage.cs} (59%) rename src/LaunchDarkly.XamarinSdk/{Extensions.shared.cs => Extensions.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{Factory.shared.cs => Factory.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{FeatureFlag.shared.cs => FeatureFlag.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{FeatureFlagListenerManager.shared.cs => FeatureFlagListenerManager.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{FeatureFlagRequestor.shared.cs => FeatureFlagRequestor.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{FlagCacheManager.shared.cs => FlagCacheManager.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IConnectionManager.shared.cs => IConnectionManager.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IDeviceInfo.shared.cs => IDeviceInfo.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IFeatureFlagListener.shared.cs => IFeatureFlagListener.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IFeatureFlagListenerManager.shared.cs => IFeatureFlagListenerManager.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IFlagCacheManager.shared.cs => IFlagCacheManager.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{ILdMobileClient.shared.cs => ILdMobileClient.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IMobileConfiguration.shared.cs => IMobileConfiguration.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IMobileUpdateProcessor.shared.cs => IMobileUpdateProcessor.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IPersistentStorage.shared.cs => IPersistentStorage.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{IUserFlagCache.shared.cs => IUserFlagCache.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{LdClient.shared.cs => LdClient.cs} (96%) rename src/LaunchDarkly.XamarinSdk/{MobileConnectionManager.shared.cs => MobileConnectionManager.cs} (70%) rename src/LaunchDarkly.XamarinSdk/{MobilePollingProcessor.shared.cs => MobilePollingProcessor.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{MobileSideClientEnvironment.shared.cs => MobileSideClientEnvironment.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{MobileStreamingProcessor.shared.cs => MobileStreamingProcessor.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{BackgroundDetection => PlatformSpecific}/BackgroundDetection.android.cs (96%) rename src/LaunchDarkly.XamarinSdk/{BackgroundDetection => PlatformSpecific}/BackgroundDetection.ios.cs (95%) rename src/LaunchDarkly.XamarinSdk/{BackgroundDetection => PlatformSpecific}/BackgroundDetection.netstandard.cs (90%) rename src/LaunchDarkly.XamarinSdk/{BackgroundDetection => PlatformSpecific}/BackgroundDetection.shared.cs (97%) rename src/LaunchDarkly.XamarinSdk/{Connectivity => PlatformSpecific}/Connectivity.android.cs (99%) rename src/LaunchDarkly.XamarinSdk/{Connectivity => PlatformSpecific}/Connectivity.ios.cs (98%) rename src/LaunchDarkly.XamarinSdk/{Connectivity => PlatformSpecific}/Connectivity.ios.reachability.cs (100%) rename src/LaunchDarkly.XamarinSdk/{Connectivity => PlatformSpecific}/Connectivity.netstandard.cs (96%) rename src/LaunchDarkly.XamarinSdk/{Connectivity => PlatformSpecific}/Connectivity.shared.cs (92%) rename src/LaunchDarkly.XamarinSdk/{Permissions => PlatformSpecific}/Permissions.android.cs (99%) rename src/LaunchDarkly.XamarinSdk/{Permissions/Permissions.shared.enums.cs => PlatformSpecific/Permissions.shared.cs} (97%) rename src/LaunchDarkly.XamarinSdk/{Platform => PlatformSpecific}/Platform.android.cs (99%) rename src/LaunchDarkly.XamarinSdk/{Platform => PlatformSpecific}/Platform.shared.cs (96%) rename src/LaunchDarkly.XamarinSdk/{Preferences => PlatformSpecific}/Preferences.android.cs (98%) rename src/LaunchDarkly.XamarinSdk/{Preferences => PlatformSpecific}/Preferences.ios.cs (98%) rename src/LaunchDarkly.XamarinSdk/{Preferences => PlatformSpecific}/Preferences.netstandard.cs (99%) rename src/LaunchDarkly.XamarinSdk/{Preferences => PlatformSpecific}/Preferences.shared.cs (99%) rename src/LaunchDarkly.XamarinSdk/{UserMetadata => PlatformSpecific}/UserMetadata.android.cs (85%) rename src/LaunchDarkly.XamarinSdk/{UserMetadata => PlatformSpecific}/UserMetadata.ios.cs (94%) rename src/LaunchDarkly.XamarinSdk/{UserMetadata => PlatformSpecific}/UserMetadata.netstandard.cs (78%) rename src/LaunchDarkly.XamarinSdk/{UserMetadata => PlatformSpecific}/UserMetadata.shared.cs (95%) rename src/LaunchDarkly.XamarinSdk/Properties/{AssemblyInfo.shared.cs => AssemblyInfo.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{UserFlagDeviceCache.shared.cs => UserFlagDeviceCache.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{UserFlagInMemoryCache.shared.cs => UserFlagInMemoryCache.cs} (100%) rename src/LaunchDarkly.XamarinSdk/{ValueType.shared.cs => ValueType.cs} (100%) diff --git a/src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs b/src/LaunchDarkly.XamarinSdk/AsyncUtils.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/AsyncUtils.shared.cs rename to src/LaunchDarkly.XamarinSdk/AsyncUtils.cs diff --git a/src/LaunchDarkly.XamarinSdk/ClientRunMode.shared.cs b/src/LaunchDarkly.XamarinSdk/ClientRunMode.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/ClientRunMode.shared.cs rename to src/LaunchDarkly.XamarinSdk/ClientRunMode.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.shared.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Configuration.shared.cs rename to src/LaunchDarkly.XamarinSdk/Configuration.cs diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs b/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs deleted file mode 100644 index 88e37766..00000000 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.enums.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace LaunchDarkly.Xamarin.Connectivity -{ - internal enum ConnectionProfile - { - Unknown = 0, - Bluetooth = 1, - Cellular = 2, - Ethernet = 3, - WiFi = 4 - } - - internal enum NetworkAccess - { - Unknown = 0, - None = 1, - Local = 2, - ConstrainedInternet = 3, - Internet = 4 - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Constants.shared.cs b/src/LaunchDarkly.XamarinSdk/Constants.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Constants.shared.cs rename to src/LaunchDarkly.XamarinSdk/Constants.cs diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.shared.cs rename to src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs diff --git a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs similarity index 59% rename from src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs rename to src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs index 10a69193..37af6cdf 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs @@ -1,4 +1,4 @@ -using System; +using LaunchDarkly.Xamarin.PlatformSpecific; namespace LaunchDarkly.Xamarin { @@ -6,12 +6,12 @@ internal class DefaultPersistentStorage : IPersistentStorage { public void Save(string key, string value) { - LaunchDarkly.Xamarin.Preferences.Preferences.Set(key, value); + Preferences.Set(key, value); } public string GetValue(string key) { - return LaunchDarkly.Xamarin.Preferences.Preferences.Get(key, null); + return Preferences.Get(key, null); } } } diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.shared.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Extensions.shared.cs rename to src/LaunchDarkly.XamarinSdk/Extensions.cs diff --git a/src/LaunchDarkly.XamarinSdk/Factory.shared.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Factory.shared.cs rename to src/LaunchDarkly.XamarinSdk/Factory.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/FeatureFlag.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlag.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.shared.cs rename to src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/FlagCacheManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/IConnectionManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IConnectionManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IConnectionManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IConnectionManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IDeviceInfo.shared.cs rename to src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs diff --git a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.shared.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs diff --git a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.shared.cs b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IFlagCacheManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.shared.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/ILdMobileClient.shared.cs rename to src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs diff --git a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IMobileConfiguration.shared.cs rename to src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs diff --git a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs b/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IPersistentStorage.shared.cs rename to src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/IUserFlagCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index f9ec405f..08362d91 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -25,14 +25,14 @@ + + - - @@ -43,7 +43,6 @@ - @@ -70,7 +69,6 @@ - diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs similarity index 96% rename from src/LaunchDarkly.XamarinSdk/LdClient.shared.cs rename to src/LaunchDarkly.XamarinSdk/LdClient.cs index 9dedb090..e4d5058d 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -6,6 +6,7 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; +using LaunchDarkly.Xamarin.PlatformSpecific; using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin @@ -87,7 +88,7 @@ public sealed class LdClient : ILdMobileClient eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); SetupConnectionManager(); - BackgroundDetection.BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; + BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; } ///

@@ -521,7 +522,7 @@ void Dispose(bool disposing) { Log.InfoFormat("Shutting down the LaunchDarkly client"); - BackgroundDetection.BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; + BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; updateProcessor.Dispose(); eventProcessor.Dispose(); } @@ -548,12 +549,12 @@ public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener l flagListenerManager.UnregisterListener(listener, flagKey); } - internal void OnBackgroundModeChanged(object sender, BackgroundDetection.BackgroundModeChangedEventArgs args) + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); } - internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundDetection.BackgroundModeChangedEventArgs args) + internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) { if (args.IsInBackground) { diff --git a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs similarity index 70% rename from src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs index cf2bb06c..0de11a80 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs @@ -1,4 +1,5 @@ using System; +using LaunchDarkly.Xamarin.PlatformSpecific; namespace LaunchDarkly.Xamarin { @@ -9,7 +10,7 @@ internal class MobileConnectionManager : IConnectionManager internal MobileConnectionManager() { UpdateConnectedStatus(); - LaunchDarkly.Xamarin.Connectivity.Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } bool isConnected; @@ -22,7 +23,7 @@ bool IConnectionManager.IsConnected } } - void Connectivity_ConnectivityChanged(object sender, Connectivity.ConnectivityChangedEventArgs e) + void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e) { UpdateConnectedStatus(); ConnectionChanged?.Invoke(isConnected); @@ -30,7 +31,7 @@ void Connectivity_ConnectivityChanged(object sender, Connectivity.ConnectivityCh private void UpdateConnectedStatus() { - isConnected = LaunchDarkly.Xamarin.Connectivity.Connectivity.NetworkAccess == LaunchDarkly.Xamarin.Connectivity.NetworkAccess.Internet; + isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; } } } diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.shared.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.shared.cs rename to src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs similarity index 96% rename from src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs index ad27eb29..3412324e 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs @@ -3,7 +3,7 @@ using Android.App; using Android.OS; -namespace LaunchDarkly.Xamarin.BackgroundDetection +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs index fcd1a1ba..1e3a8342 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs @@ -3,7 +3,7 @@ using UIKit; using Foundation; -namespace LaunchDarkly.Xamarin.BackgroundDetection +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs similarity index 90% rename from src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs index 0aeef6bf..3d768296 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin.BackgroundDetection +namespace LaunchDarkly.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same abstraction. It is a stub // that does nothing, since in .NET Standard there is no notion of an application being in the diff --git a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs similarity index 97% rename from src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs index 80dfc9c6..996ec6bf 100644 --- a/src/LaunchDarkly.XamarinSdk/BackgroundDetection/BackgroundDetection.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin.BackgroundDetection +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs index 81e6b116..18aacadf 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -1,5 +1,4 @@ using System; -using LaunchDarkly.Xamarin.Preferences; namespace LaunchDarkly.Xamarin.PlatformSpecific { @@ -16,7 +15,7 @@ public static string GetOrCreateClientId() // On mobile platforms this has no effect. internal static void ClearCachedClientId() { - Preferences.Preferences.Remove(PreferencesAnonUserIdKey); + Preferences.Remove(PreferencesAnonUserIdKey); } private static string GetOrCreateRandomizedClientId() @@ -24,13 +23,13 @@ private static string GetOrCreateRandomizedClientId() // On non-mobile platforms, there may not be an OS-based notion of a device identifier. Instead, // we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a user key // for this user account (OS user, that is), and if not, generate a randomized ID and cache it. - string cachedKey = Preferences.Preferences.Get(PreferencesAnonUserIdKey, null); + string cachedKey = Preferences.Get(PreferencesAnonUserIdKey, null); if (cachedKey != null) { return cachedKey; } string guid = Guid.NewGuid().ToString(); - Preferences.Preferences.Set(PreferencesAnonUserIdKey, guid); + Preferences.Set(PreferencesAnonUserIdKey, guid); return guid; } } diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs index d91290db..6db4395d 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs @@ -28,7 +28,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.OS; using Debug = System.Diagnostics.Debug; -namespace LaunchDarkly.Xamarin.Connectivity +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal partial class Connectivity { diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs index 162f2d18..ccde933e 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs @@ -23,7 +23,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; -namespace LaunchDarkly.Xamarin.Connectivity +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Connectivity { diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.reachability.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.ios.reachability.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs similarity index 96% rename from src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs index d0fcf671..614bc3ea 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace LaunchDarkly.Xamarin.Connectivity +namespace LaunchDarkly.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same Connectivity abstraction. // It is a stub that always reports that we do have network connectivity. diff --git a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs similarity index 92% rename from src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs index 1889f9e4..942350d2 100644 --- a/src/LaunchDarkly.XamarinSdk/Connectivity/Connectivity.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs @@ -24,8 +24,26 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Collections.Generic; using System.Linq; -namespace LaunchDarkly.Xamarin.Connectivity +namespace LaunchDarkly.Xamarin.PlatformSpecific { + internal enum ConnectionProfile + { + Unknown = 0, + Bluetooth = 1, + Cellular = 2, + Ethernet = 3, + WiFi = 4 + } + + internal enum NetworkAccess + { + Unknown = 0, + None = 1, + Local = 2, + ConstrainedInternet = 3, + Internet = 4 + } + internal static partial class Connectivity { static event EventHandler ConnectivityChangedInternal; diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs index ab7a9bc8..1b640e32 100644 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs @@ -30,7 +30,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.Support.V4.App; using Android.Support.V4.Content; -namespace LaunchDarkly.Xamarin.Permissions +namespace LaunchDarkly.Xamarin.PlatformSpecific { // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. // Note that we are no longer using the shared Permissions abstraction at all; this Android code is being used directly diff --git a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.enums.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs similarity index 97% rename from src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.enums.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs index ce24091a..1faed91c 100644 --- a/src/LaunchDarkly.XamarinSdk/Permissions/Permissions.shared.enums.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs @@ -20,7 +20,7 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace LaunchDarkly.Xamarin.Permissions +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal enum PermissionStatus { diff --git a/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs index 8ea21d35..7e357723 100644 --- a/src/LaunchDarkly.XamarinSdk/Platform/Platform.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs @@ -34,7 +34,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. -namespace LaunchDarkly.Xamarin.Platform +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Platform { diff --git a/src/LaunchDarkly.XamarinSdk/Platform/Platform.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs similarity index 96% rename from src/LaunchDarkly.XamarinSdk/Platform/Platform.shared.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs index 59395a07..54a3f996 100644 --- a/src/LaunchDarkly.XamarinSdk/Platform/Platform.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs @@ -20,7 +20,7 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace LaunchDarkly.Xamarin.Platform +namespace LaunchDarkly.Xamarin.PlatformSpecific { #if !NETSTANDARD internal static partial class Platform diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs index 1e6f8a7c..c958bb64 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs @@ -26,7 +26,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.Content; using Android.Preferences; -namespace LaunchDarkly.Xamarin.Preferences +namespace LaunchDarkly.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs index 616cd03b..90392ea5 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs @@ -24,7 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Globalization; using Foundation; -namespace LaunchDarkly.Xamarin.Preferences +namespace LaunchDarkly.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs index 71fc87f0..e8225639 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs @@ -9,7 +9,7 @@ using LaunchDarkly.Common; #endif -namespace LaunchDarkly.Xamarin.Preferences +namespace LaunchDarkly.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same Preferences abstraction. // diff --git a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs index ef221963..9314a6b0 100644 --- a/src/LaunchDarkly.XamarinSdk/Preferences/Preferences.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs @@ -22,7 +22,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; -namespace LaunchDarkly.Xamarin.Preferences +namespace LaunchDarkly.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs similarity index 85% rename from src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs index a992e4d0..b6b1bc02 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs @@ -1,6 +1,6 @@ using Android.OS; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs similarity index 94% rename from src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs index 6f66f57f..ec7a1b29 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs @@ -1,6 +1,6 @@ using UIKit; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs similarity index 78% rename from src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs index 013ebf0e..145b7ac4 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs rename to src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs index 3c769c16..7da32ec9 100644 --- a/src/LaunchDarkly.XamarinSdk/UserMetadata/UserMetadata.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Xamarin.PlatformSpecific { // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. diff --git a/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs b/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.shared.cs rename to src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.shared.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.shared.cs rename to src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.shared.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/ValueType.shared.cs rename to src/LaunchDarkly.XamarinSdk/ValueType.cs From 06902cf452c12fc4d44145315beeddd19eb62b40 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 18:21:33 -0700 Subject: [PATCH 151/499] merge some iOS code --- .../PlatformSpecific/Connectivity.ios.cs | 156 +++++++++++++++ .../Connectivity.ios.reachability.cs | 187 ------------------ 2 files changed, 156 insertions(+), 187 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs index ccde933e..949e9ca7 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs @@ -84,4 +84,160 @@ static IEnumerable PlatformConnectionProfiles } } } + + enum NetworkStatus + { + NotReachable, + ReachableViaCarrierDataNetwork, + ReachableViaWiFiNetwork + } + + internal static class Reachability + { + internal const string HostName = "www.microsoft.com"; + + internal static NetworkStatus RemoteHostStatus() + { + using (var remoteHostReachability = new NetworkReachability(HostName)) + { + var reachable = remoteHostReachability.TryGetFlags(out var flags); + + if (!reachable) + return NetworkStatus.NotReachable; + + if (!IsReachableWithoutRequiringConnection(flags)) + return NetworkStatus.NotReachable; + + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + return NetworkStatus.ReachableViaCarrierDataNetwork; + + return NetworkStatus.ReachableViaWiFiNetwork; + } + } + + internal static NetworkStatus InternetConnectionStatus() + { + var status = NetworkStatus.NotReachable; + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + status = NetworkStatus.ReachableViaCarrierDataNetwork; + + // If the connection is reachable and no connection is required, then assume it's WiFi + if (defaultNetworkAvailable) + { + status = NetworkStatus.ReachableViaWiFiNetwork; + } + + // If the connection is on-demand or on-traffic and no user intervention + // is required, then assume WiFi. + if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && + (flags & NetworkReachabilityFlags.InterventionRequired) == 0) + { + status = NetworkStatus.ReachableViaWiFiNetwork; + } + + return status; + } + + internal static IEnumerable GetActiveConnectionType() + { + var status = new List(); + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + { + status.Add(NetworkStatus.ReachableViaCarrierDataNetwork); + } + else if (defaultNetworkAvailable) + { + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + else if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && + (flags & NetworkReachabilityFlags.InterventionRequired) == 0) + { + // If the connection is on-demand or on-traffic and no user intervention + // is required, then assume WiFi. + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + + return status; + } + + internal static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) + { + var ip = new IPAddress(0); + using (var defaultRouteReachability = new NetworkReachability(ip)) + { + if (!defaultRouteReachability.TryGetFlags(out flags)) + return false; + + return IsReachableWithoutRequiringConnection(flags); + } + } + + internal static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags) + { + // Is it reachable with the current network configuration? + var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0; + + // Do we need a connection to reach it? + var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0; + + // Since the network stack will automatically try to get the WAN up, + // probe that + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + noConnectionRequired = true; + + return isReachable && noConnectionRequired; + } + } + + internal class ReachabilityListener : IDisposable + { + NetworkReachability defaultRouteReachability; + NetworkReachability remoteHostReachability; + + internal ReachabilityListener() + { + var ip = new IPAddress(0); + defaultRouteReachability = new NetworkReachability(ip); + defaultRouteReachability.SetNotification(OnChange); + defaultRouteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + + remoteHostReachability = new NetworkReachability(Reachability.HostName); + + // Need to probe before we queue, or we wont get any meaningful values + // this only happens when you create NetworkReachability from a hostname + remoteHostReachability.TryGetFlags(out var flags); + + remoteHostReachability.SetNotification(OnChange); + remoteHostReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + } + + internal event Action ReachabilityChanged; + + void IDisposable.Dispose() => Dispose(); + + internal void Dispose() + { + defaultRouteReachability?.Dispose(); + defaultRouteReachability = null; + remoteHostReachability?.Dispose(); + remoteHostReachability = null; + } + + async void OnChange(NetworkReachabilityFlags flags) + { + // Add in artifical delay so the connection status has time to change + // else it will return true no matter what. + await Task.Delay(100); + + ReachabilityChanged?.Invoke(); + } + } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs deleted file mode 100644 index 0c8f5018..00000000 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.reachability.cs +++ /dev/null @@ -1,187 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using CoreFoundation; -using SystemConfiguration; - -namespace LaunchDarkly.Xamarin.Connectivity -{ - enum NetworkStatus - { - NotReachable, - ReachableViaCarrierDataNetwork, - ReachableViaWiFiNetwork - } - - internal static class Reachability - { - internal const string HostName = "www.microsoft.com"; - - internal static NetworkStatus RemoteHostStatus() - { - using (var remoteHostReachability = new NetworkReachability(HostName)) - { - var reachable = remoteHostReachability.TryGetFlags(out var flags); - - if (!reachable) - return NetworkStatus.NotReachable; - - if (!IsReachableWithoutRequiringConnection(flags)) - return NetworkStatus.NotReachable; - - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - return NetworkStatus.ReachableViaCarrierDataNetwork; - - return NetworkStatus.ReachableViaWiFiNetwork; - } - } - - internal static NetworkStatus InternetConnectionStatus() - { - var status = NetworkStatus.NotReachable; - - var defaultNetworkAvailable = IsNetworkAvailable(out var flags); - - // If it's a WWAN connection.. - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - status = NetworkStatus.ReachableViaCarrierDataNetwork; - - // If the connection is reachable and no connection is required, then assume it's WiFi - if (defaultNetworkAvailable) - { - status = NetworkStatus.ReachableViaWiFiNetwork; - } - - // If the connection is on-demand or on-traffic and no user intervention - // is required, then assume WiFi. - if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && - (flags & NetworkReachabilityFlags.InterventionRequired) == 0) - { - status = NetworkStatus.ReachableViaWiFiNetwork; - } - - return status; - } - - internal static IEnumerable GetActiveConnectionType() - { - var status = new List(); - - var defaultNetworkAvailable = IsNetworkAvailable(out var flags); - - // If it's a WWAN connection.. - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - { - status.Add(NetworkStatus.ReachableViaCarrierDataNetwork); - } - else if (defaultNetworkAvailable) - { - status.Add(NetworkStatus.ReachableViaWiFiNetwork); - } - else if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && - (flags & NetworkReachabilityFlags.InterventionRequired) == 0) - { - // If the connection is on-demand or on-traffic and no user intervention - // is required, then assume WiFi. - status.Add(NetworkStatus.ReachableViaWiFiNetwork); - } - - return status; - } - - internal static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) - { - var ip = new IPAddress(0); - using (var defaultRouteReachability = new NetworkReachability(ip)) - { - if (!defaultRouteReachability.TryGetFlags(out flags)) - return false; - - return IsReachableWithoutRequiringConnection(flags); - } - } - - internal static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags) - { - // Is it reachable with the current network configuration? - var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0; - - // Do we need a connection to reach it? - var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0; - - // Since the network stack will automatically try to get the WAN up, - // probe that - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - noConnectionRequired = true; - - return isReachable && noConnectionRequired; - } - } - - class ReachabilityListener : IDisposable - { - NetworkReachability defaultRouteReachability; - NetworkReachability remoteHostReachability; - - internal ReachabilityListener() - { - var ip = new IPAddress(0); - defaultRouteReachability = new NetworkReachability(ip); - defaultRouteReachability.SetNotification(OnChange); - defaultRouteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); - - remoteHostReachability = new NetworkReachability(Reachability.HostName); - - // Need to probe before we queue, or we wont get any meaningful values - // this only happens when you create NetworkReachability from a hostname - remoteHostReachability.TryGetFlags(out var flags); - - remoteHostReachability.SetNotification(OnChange); - remoteHostReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); - } - - internal event Action ReachabilityChanged; - - void IDisposable.Dispose() => Dispose(); - - internal void Dispose() - { - defaultRouteReachability?.Dispose(); - defaultRouteReachability = null; - remoteHostReachability?.Dispose(); - remoteHostReachability = null; - } - - async void OnChange(NetworkReachabilityFlags flags) - { - // Add in artifical delay so the connection status has time to change - // else it will return true no matter what. - await Task.Delay(100); - - ReachabilityChanged?.Invoke(); - } - } -} From 0a0d21f4af090640e775ea452bcf5cd0a11cb04c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 18:25:38 -0700 Subject: [PATCH 152/499] fix some more broken references --- .../PlatformSpecific/Connectivity.android.cs | 18 +++++++++--------- .../PlatformSpecific/Connectivity.ios.cs | 4 ++++ .../PlatformSpecific/Permissions.android.cs | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs index 6db4395d..a074cc95 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs @@ -36,11 +36,11 @@ internal partial class Connectivity static void StartListeners() { - Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); + Permissions.EnsureDeclared(PermissionType.NetworkState); conectivityReceiver = new ConnectivityBroadcastReceiver(OnConnectivityChanged); - Platform.Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); + Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); } static void StopListeners() @@ -49,7 +49,7 @@ static void StopListeners() return; try { - Platform.Platform.AppContext.UnregisterReceiver(conectivityReceiver); + Platform.AppContext.UnregisterReceiver(conectivityReceiver); } catch (Java.Lang.IllegalArgumentException) { @@ -66,14 +66,14 @@ static NetworkAccess PlatformNetworkAccess { get { - Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); + Permissions.EnsureDeclared(PermissionType.NetworkState); try { var currentAccess = NetworkAccess.None; - var manager = Platform.Platform.ConnectivityManager; + var manager = Platform.ConnectivityManager; - if (Platform.Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) { foreach (var network in manager.GetAllNetworks()) { @@ -140,10 +140,10 @@ static IEnumerable PlatformConnectionProfiles { get { - Permissions.Permissions.EnsureDeclared(Permissions.PermissionType.NetworkState); + Permissions.EnsureDeclared(PermissionType.NetworkState); - var manager = Platform.Platform.ConnectivityManager; - if (Platform.Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + var manager = Platform.ConnectivityManager; + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) { foreach (var network in manager.GetAllNetworks()) { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs index 949e9ca7..c7196f6c 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs @@ -22,6 +22,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using CoreFoundation; +using SystemConfiguration; namespace LaunchDarkly.Xamarin.PlatformSpecific { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs index 1b640e32..a63e2a10 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs @@ -53,7 +53,7 @@ internal static void EnsureDeclared(PermissionType permission) if (androidPermissions == null || !androidPermissions.Any()) return; - var context = Platform.Platform.AppContext; + var context = Platform.AppContext; foreach (var ap in androidPermissions) { From eeca41e5965861dd782c0b1c14ef39ca74c52355 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 20:12:01 -0700 Subject: [PATCH 153/499] improvements to CONTRIBUTING file --- CONTRIBUTING.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 366a1812..72383fcd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,45 +1,48 @@ -Contributing to the LaunchDarkly Client-side SDK for Xamarin -================================================ +# Contributing to the LaunchDarkly Client-side SDK for Xamarin LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK. -Submitting bug reports and feature requests ------------------- +## Submitting bug reports and feature requests The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/xamarin-client-sdk/issues) in the SDK repository. Bug reports and feature requests specific to this SDK should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days. -Submitting pull requests ------------------- +## Submitting pull requests We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days. -Build instructions ------------------- +## Build instructions ### Prerequisites The .NET Standard 1.6 and 2.0 targets require only the .NET Core 2.0 SDK, while the iOS and Android targets require the corresponding Xamarin SDKs. -To set up the project and dependencies, run the following command in the root SDK directory: +### Building + +To build the SDK (for all platforms) without running any tests: ``` -dotnet restore +msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj ``` -### Building - -To build the SDK (for all platforms) without running any tests: +To build the SDK for only one of the supported platforms, add `-f X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: ``` -msbuild src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj -f netstandard2.0 ``` +Note that the main project, `src/LaunchDarkly.XamarinSdk`, contains source files that are built for all platforms (ending in just `.cs`, or `.shared.cs`), and also a smaller amount of code that is conditionally compiled for platform-specific functionality. The latter is all in the `PlatformSpecific` folder. We use `#ifdef` directives only for small sections that differ slightly between platform versions; otherwise the conditional compilation is done according to filename suffix (`.android.cs`, etc.) based on rules in the `.csproj` file. + ### Testing -To build the .NET Standard 2.0 target and run the basic unit tests, which cover all of the non-platform-specific functionality: +The .NET Standard unit tests cover all of the non-platform-specific functionality, as well as behavior specific to .NET Standard (e.g. caching flags in the filesystem). They can be run with only the basic Xamarin framework installed, via the `dotnet` tool: + ``` dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 ``` -Currently the Android and iOS test projects can only be run from Visual Studio in MacOS. +The equivalent test suites in Android or iOS must be run in an Android or iOS emulator. The projects `tests/LaunchDarkly.XamarinSdk.Android.Tests` and `tests/LaunchDarkly.XamarinSdk.iOS.Tests` consist of applications based on the `xunit.runner.devices` tool, which show the test results visually in the emulator and also write the results to the emulator's system log. The actual unit test code is just the same tests from the main `tests/LaunchDarkly.XamarinSdk.Tests` project, but running them in this way exercises the mobile-specific behavior for those platforms (e.g. caching flags in user preferences). + +You can run the mobile test projects from Visual Studio (the iOS tests require MacOS); there is also a somewhat complicated process for running them from the command line, which is what the CI build does (see `.circleci/config.yml`). + +Note that the mobile unit tests currently do not cover background-mode behavior or connectivity detection. From ddff9a1b3a551d67bdab3782cf4ca2d8bb305607 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Jul 2019 20:13:23 -0700 Subject: [PATCH 154/499] capitalization --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72383fcd..b21ac4e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to the LaunchDarkly Client-side SDK for Xamarin +# Contributing to the LaunchDarkly Client-Side SDK for Xamarin LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK. diff --git a/README.md b/README.md index f3ebe7ff..e608de4d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -LaunchDarkly Client-side SDK for Xamarin +LaunchDarkly Client-Side SDK for Xamarin =========================== [![CircleCI](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master) From ec0f5cdfeeb1d9bbd04b38474dcfca08fdb6d938 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 2 Jul 2019 14:13:22 -0700 Subject: [PATCH 155/499] 1.0.0-beta18 --- CHANGELOG.md | 15 +++++++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b78f488..8fa64ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.0.0-beta18] - 2019-07-02 +### Added: +- New `Configuration` property `PersistFlagValues` (default: true) allows you to turn off the SDK's normal behavior of storing flag values locally so they can be used offline. +- Flag values are now stored locally in .NET Standard by default, on the filesystem, using the .NET `IsolatedStorageFile` mechanism. +- Added CI unit test suites that exercise most of the SDK functionality in .NET Standard, Android, and iOS. The tests do not currently cover background-mode detection and network connectivity detection on mobile platforms. + +### Changed: +- `Configuration.WithUpdateProcessor` has been replaced with `Configuration.WithUpdateProcessorFactory`. These methods are for testing purposes and will not normally be used. + +### Fixed: +- In .NET Standard, if you specify a user with `Key == null` and `Anonymous == true`, the SDK now generates a GUID for a user key and caches it in local storage for future reuse. This is consistent with the other client-side SDKs. Previously, it caused an exception. + +### Removed: +- Several low-level component interfaces such as `IDeviceInfo` which had been exposed for testing are now internal. + # Note on future releases The LaunchDarkly SDK repositories are being renamed for consistency. This repository is now `xamarin-client-sdk` rather than `xamarin-client`. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 08362d91..3c13cb95 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -4,7 +4,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta17 + 1.0.0-beta18 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From 7058f3c5e123f99e84e59c7f0d67ae8a48421402 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 2 Jul 2019 21:00:13 -0700 Subject: [PATCH 156/499] add scripts for packaging & releasing --- scripts/package.sh | 26 ++++++++++++++++++++++++++ scripts/release.sh | 17 +++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100755 scripts/package.sh create mode 100755 scripts/release.sh diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 00000000..d11e4b89 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# Usage: ./scripts/package.sh [debug|release] + +# msbuild expects word-capitalization of this parameter +CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` +if [[ -z "$CONFIG" ]]; then + CONFIG=Debug # currently we're releasing debug builds by default +fi + +# Remove any existing build products. + +msbuild /t:clean +rm -f ./src/LaunchDarkly.XamarinSdk/bin/Debug/*.nupkg +rm -f ./src/LaunchDarkly.XamarinSdk/bin/Release/*.nupkg + +# Build the project for all target frameworks. This includes building the .nupkg, because of +# the directive in our project file. + +msbuild /restore /p:Configuration:$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + +# Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by CI jobs in config.yml) + +export ASPNETCORE_SUPPRESSSTATUSMESSAGES=true # suppresses some annoying test output +dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..5808c148 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +# Usage: ./scripts/release.sh [debug|release] + +# msbuild expects word-capitalization of this parameter +CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` +if [[ -z "$CONFIG" ]]; then + CONFIG=Debug # currently we're releasing debug builds by default +fi + +./scripts/package.sh $CONFIG + +# Since package.sh does a clean build, whichever .nupkg file now exists in the output directory +# is the one we want to upload. + +nuget push ./src/LaunchDarkly.XamarinSdk/bin/$CONFIG/LaunchDarkly.XamarinSdk.*.nupkg -Source https://www.nuget.org From 15e58b1cabbc861e86b569bfdc6188399572edde Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 2 Jul 2019 21:02:28 -0700 Subject: [PATCH 157/499] indents --- scripts/package.sh | 2 +- scripts/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/package.sh b/scripts/package.sh index d11e4b89..4fa7f2a2 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -6,7 +6,7 @@ set -e # msbuild expects word-capitalization of this parameter CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` if [[ -z "$CONFIG" ]]; then - CONFIG=Debug # currently we're releasing debug builds by default + CONFIG=Debug # currently we're releasing debug builds by default fi # Remove any existing build products. diff --git a/scripts/release.sh b/scripts/release.sh index 5808c148..1f7fc68f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -6,7 +6,7 @@ set -e # msbuild expects word-capitalization of this parameter CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` if [[ -z "$CONFIG" ]]; then - CONFIG=Debug # currently we're releasing debug builds by default + CONFIG=Debug # currently we're releasing debug builds by default fi ./scripts/package.sh $CONFIG From fa5106becae03bc8d306d15d62b9eafc1df6a286 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 3 Jul 2019 09:50:07 -0700 Subject: [PATCH 158/499] comments --- CONTRIBUTING.md | 6 +++++- scripts/package.sh | 4 ++++ scripts/release.sh | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b21ac4e6..51fbe5eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,12 +18,14 @@ The .NET Standard 1.6 and 2.0 targets require only the .NET Core 2.0 SDK, while ### Building -To build the SDK (for all platforms) without running any tests: +To build the SDK (for all target platforms) without running any tests: ``` msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj ``` +Currently this command can only be run on MacOS, because that is the only platform that allows building for all of the targets (.NET Standard, Android, and iOS). + To build the SDK for only one of the supported platforms, add `-f X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: ``` @@ -46,3 +48,5 @@ The equivalent test suites in Android or iOS must be run in an Android or iOS em You can run the mobile test projects from Visual Studio (the iOS tests require MacOS); there is also a somewhat complicated process for running them from the command line, which is what the CI build does (see `.circleci/config.yml`). Note that the mobile unit tests currently do not cover background-mode behavior or connectivity detection. + +### Packaging/releasing diff --git a/scripts/package.sh b/scripts/package.sh index 4fa7f2a2..1448f731 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -3,6 +3,10 @@ set -e # Usage: ./scripts/package.sh [debug|release] +# This script performs a clean build for all target platforms, which produces both the DLLs and the .nupkg, +# and also runs the .NET Standard unit tests. It is used in the LaunchDarkly release process. It must be run +# on MacOS, since iOS is one of the targets. + # msbuild expects word-capitalization of this parameter CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` if [[ -z "$CONFIG" ]]; then diff --git a/scripts/release.sh b/scripts/release.sh index 1f7fc68f..a1ede956 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -3,6 +3,9 @@ set -e # Usage: ./scripts/release.sh [debug|release] +# This script calls package.sh to create a NuGet package, and then uploads it to NuGet. It is used in +# the LaunchDarkly release process. It must be run on MacOS, since iOS is one of the targets. + # msbuild expects word-capitalization of this parameter CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` if [[ -z "$CONFIG" ]]; then From 6553a7c50715f46d132054a9c116f3897ea32da7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 19:51:01 -0700 Subject: [PATCH 159/499] typo --- scripts/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package.sh b/scripts/package.sh index 1448f731..710d2b4b 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -22,7 +22,7 @@ rm -f ./src/LaunchDarkly.XamarinSdk/bin/Release/*.nupkg # Build the project for all target frameworks. This includes building the .nupkg, because of # the directive in our project file. -msbuild /restore /p:Configuration:$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore /p:Configuration=$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by CI jobs in config.yml) From 56a6ff17e08502e9c35796b5b72f216896c6e7ce Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 19:56:48 -0700 Subject: [PATCH 160/499] call flag change listeners asynchronously --- .../FeatureFlagListenerManager.cs | 60 ++++++++++++++----- .../IFeatureFlagListenerManager.cs | 1 + src/LaunchDarkly.XamarinSdk/LdClient.cs | 6 ++ .../AsyncScheduler.android.cs | 13 ++++ .../PlatformSpecific/AsyncScheduler.ios.cs | 13 ++++ .../AsyncScheduler.netstandard.cs | 13 ++++ .../PlatformSpecific/AsyncScheduler.shared.cs | 18 ++++++ .../FeatureFlagListenerTests.cs | 56 ++++++++++++++--- .../FlagCacheManagerTests.cs | 16 +++-- .../LdClientTests.cs | 13 ++-- 10 files changed, 171 insertions(+), 38 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs index 84cf1016..dfe46710 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs @@ -1,13 +1,17 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; +using Common.Logging; using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin { internal class FeatureFlagListenerManager : IFeatureFlagListenerManager, IFlagListenerUpdater { + private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagListenerManager)); + private readonly IDictionary> _map = new Dictionary>(); @@ -18,12 +22,11 @@ public void RegisterListener(IFeatureFlagListener listener, string flagKey) readWriteLock.EnterWriteLock(); try { - if (!_map.ContainsKey(flagKey)) + if (!_map.TryGetValue(flagKey, out var list)) { - _map[flagKey] = new List(); + list = new List(); + _map[flagKey] = list; } - - var list = _map[flagKey]; list.Add(listener); } finally @@ -37,10 +40,9 @@ public void UnregisterListener(IFeatureFlagListener listener, string flagKey) readWriteLock.EnterWriteLock(); try { - if (_map.ContainsKey(flagKey)) + if (_map.TryGetValue(flagKey, out var list)) { - var listOfListeners = _map[flagKey]; - listOfListeners.Remove(listener); + list.Remove(listener); } } finally @@ -49,16 +51,16 @@ public void UnregisterListener(IFeatureFlagListener listener, string flagKey) } } - public void FlagWasDeleted(string flagKey) + public bool IsListenerRegistered(IFeatureFlagListener listener, string flagKey) { readWriteLock.EnterReadLock(); try { - if (_map.ContainsKey(flagKey)) + if (_map.TryGetValue(flagKey, out var list)) { - var listeners = _map[flagKey]; - listeners.ForEach((listener) => listener.FeatureFlagDeleted(flagKey)); + return list.Contains(listener); } + return false; } finally { @@ -66,21 +68,49 @@ public void FlagWasDeleted(string flagKey) } } + public void FlagWasDeleted(string flagKey) + { + FireAction(flagKey, (listener) => listener.FeatureFlagDeleted(flagKey)); + } + public void FlagWasUpdated(string flagKey, JToken value) { + FireAction(flagKey, (listener) => listener.FeatureFlagChanged(flagKey, value)); + } + + private void FireAction(string flagKey, Action a) + { + IFeatureFlagListener[] listeners = null; readWriteLock.EnterReadLock(); try { - if (_map.ContainsKey(flagKey)) + if (_map.TryGetValue(flagKey, out var mutableListOfListeners)) { - var listeners = _map[flagKey]; - listeners.ForEach((listener) => listener.FeatureFlagChanged(flagKey, value)); + listeners = mutableListOfListeners.ToArray(); // this copies the list } } finally { readWriteLock.ExitReadLock(); } + if (listeners != null) + { + foreach (var l in listeners) + { + // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. + PlatformSpecific.AsyncScheduler.ScheduleAction(() => + { + try + { + a(l); + } + catch (Exception e) + { + Log.Warn("Unexpected exception from feature flag listener", e); + } + }); + } + } } } } diff --git a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs index 792e8145..0e9668af 100644 --- a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs @@ -6,6 +6,7 @@ internal interface IFeatureFlagListenerManager : IFlagListenerUpdater { void RegisterListener(IFeatureFlagListener listener, string flagKey); void UnregisterListener(IFeatureFlagListener listener, string flagKey); + bool IsListenerRegistered(IFeatureFlagListener listener, string flagKey); // used for testing } internal interface IFlagListenerUpdater diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index e4d5058d..5bd1b674 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -549,6 +549,12 @@ public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener l flagListenerManager.UnregisterListener(listener, flagKey); } + // for tests only + internal bool IsFeatureFlagListenerRegistered(string flagKey, IFeatureFlagListener listener) + { + return flagListenerManager.IsListenerRegistered(listener, flagKey); + } + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs new file mode 100644 index 00000000..4a62552f --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -0,0 +1,13 @@ +using System; +using Android.OS; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class AsyncScheduler + { + private static void PlatformScheduleAction(Action a) + { + + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs new file mode 100644 index 00000000..54a9a8d9 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs @@ -0,0 +1,13 @@ +using System; +using Foundation; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class AsyncScheduler + { + private static void PlatformScheduleAction(Action a) + { + NSRunLoop.Main.BeginInvokeOnMainThread(a.Invoke); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs new file mode 100644 index 00000000..ef7411f5 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class AsyncScheduler + { + private static void PlatformScheduleAction(Action a) + { + Task.Run(a); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs new file mode 100644 index 00000000..869201bf --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs @@ -0,0 +1,18 @@ +using System; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. + // It provides a method for asynchronously starting tasks, such as event handlers, using a mechanism + // that may vary by platform. + internal static partial class AsyncScheduler + { + // Queues a task to be executed asynchronously as soon as possible. On platforms that have a notion + // of a "main thread" or "UI thread", the action is guaranteed to run on that thread; otherwise it + // can be any thread. + public static void ScheduleAction(Action a) + { + PlatformScheduleAction(a); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs index d20f2d9b..2eae60a1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using Newtonsoft.Json.Linq; using Xunit; @@ -14,23 +15,27 @@ FeatureFlagListenerManager Manager() return new FeatureFlagListenerManager(); } - TestListener Listener() + TestListener Listener(int expectedTimes) { - return new TestListener(); + return new TestListener(expectedTimes); } [Fact] public void CanRegisterListeners() { var manager = Manager(); - var listener1 = Listener(); - var listener2 = Listener(); + var listener1 = Listener(2); + var listener2 = Listener(2); manager.RegisterListener(listener1, INT_FLAG); manager.RegisterListener(listener1, DOUBLE_FLAG); manager.RegisterListener(listener2, INT_FLAG); manager.RegisterListener(listener2, DOUBLE_FLAG); + manager.FlagWasUpdated(INT_FLAG, 7); manager.FlagWasUpdated(DOUBLE_FLAG, 10.5); + listener1.Countdown.Wait(); + listener2.Countdown.Wait(); + Assert.Equal(7, listener1.FeatureFlags[INT_FLAG]); Assert.Equal(10.5, listener1.FeatureFlags[DOUBLE_FLAG]); Assert.Equal(7, listener2.FeatureFlags[INT_FLAG]); @@ -41,14 +46,18 @@ public void CanRegisterListeners() public void CanUnregisterListeners() { var manager = Manager(); - var listener1 = Listener(); - var listener2 = Listener(); + var listener1 = Listener(2); + var listener2 = Listener(2); manager.RegisterListener(listener1, INT_FLAG); manager.RegisterListener(listener1, DOUBLE_FLAG); manager.RegisterListener(listener2, INT_FLAG); manager.RegisterListener(listener2, DOUBLE_FLAG); + manager.FlagWasUpdated(INT_FLAG, 7); manager.FlagWasUpdated(DOUBLE_FLAG, 10.5); + listener1.Countdown.Wait(); + listener2.Countdown.Wait(); + Assert.Equal(7, listener1.FeatureFlags[INT_FLAG]); Assert.Equal(10.5, listener1.FeatureFlags[DOUBLE_FLAG]); Assert.Equal(7, listener2.FeatureFlags[INT_FLAG]); @@ -58,9 +67,14 @@ public void CanUnregisterListeners() manager.UnregisterListener(listener2, INT_FLAG); manager.UnregisterListener(listener1, DOUBLE_FLAG); manager.UnregisterListener(listener2, DOUBLE_FLAG); + listener1.Reset(); + listener2.Reset(); manager.FlagWasUpdated(INT_FLAG, 2); manager.FlagWasUpdated(DOUBLE_FLAG, 12.5); + // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. + Thread.Sleep(100); + Assert.NotEqual(2, listener1.FeatureFlags[INT_FLAG]); Assert.NotEqual(12.5, listener1.FeatureFlags[DOUBLE_FLAG]); Assert.NotEqual(2, listener2.FeatureFlags[INT_FLAG]); @@ -71,12 +85,14 @@ public void CanUnregisterListeners() public void ListenerGetsUpdatedFlagValues() { var manager = Manager(); - var listener1 = Listener(); - var listener2 = Listener(); + var listener1 = Listener(1); + var listener2 = Listener(1); manager.RegisterListener(listener1, INT_FLAG); manager.RegisterListener(listener2, INT_FLAG); manager.FlagWasUpdated(INT_FLAG, JToken.FromObject(99)); + listener1.Countdown.Wait(); + listener2.Countdown.Wait(); Assert.Equal(99, listener1.FeatureFlags[INT_FLAG].Value()); Assert.Equal(99, listener2.FeatureFlags[INT_FLAG].Value()); @@ -86,17 +102,32 @@ public void ListenerGetsUpdatedFlagValues() public void ListenerGetsUpdatedWhenManagerFlagDeleted() { var manager = Manager(); - var listener = Listener(); + var listener = Listener(1); manager.RegisterListener(listener, INT_FLAG); + manager.FlagWasUpdated(INT_FLAG, 2); + listener.Countdown.Wait(); Assert.True(listener.FeatureFlags.ContainsKey(INT_FLAG)); + + listener.Reset(); manager.FlagWasDeleted(INT_FLAG); + listener.Countdown.Wait(); + Assert.False(listener.FeatureFlags.ContainsKey(INT_FLAG)); } } public class TestListener : IFeatureFlagListener { + private readonly int ExpectedCalls; + public CountdownEvent Countdown; + + public TestListener(int expectedCalls) + { + ExpectedCalls = expectedCalls; + Reset(); + } + IDictionary featureFlags = new Dictionary(); public IDictionary FeatureFlags { @@ -109,11 +140,18 @@ public IDictionary FeatureFlags public void FeatureFlagChanged(string featureFlagKey, JToken value) { featureFlags[featureFlagKey] = value; + Countdown.Signal(); } public void FeatureFlagDeleted(string featureFlagKey) { featureFlags.Remove(featureFlagKey); + Countdown.Signal(); + } + + public void Reset() + { + Countdown = new CountdownEvent(ExpectedCalls); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index fdf00d8d..a7435808 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -70,12 +70,15 @@ public void ShouldBeAbleToUpdateFlagForUser() [Fact] public void UpdateFlagUpdatesTheFlagOnListenerManager() { - var listener = new TestListener(); + var listener = new TestListener(1); listenerManager.RegisterListener(listener, "int-flag"); + var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); updatedFeatureFlag.value = JToken.FromObject(7); + flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); + listener.Countdown.Wait(); Assert.Equal(7, listener.FeatureFlags["int-flag"].ToObject()); } @@ -83,15 +86,15 @@ public void UpdateFlagUpdatesTheFlagOnListenerManager() [Fact] public void RemoveFlagTellsListenerManagerToTellListenersFlagWasDeleted() { - var listener = new TestListener(); - listenerManager.RegisterListener(listener, "int-flag"); + var listener = new TestListener(1); listener.FeatureFlags["int-flag"] = JToken.FromObject(1); - Assert.True(listener.FeatureFlags.ContainsKey("int-flag")); + listenerManager.RegisterListener(listener, "int-flag"); var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); updatedFeatureFlag.value = JToken.FromObject(7); flagCacheManager.RemoveFlagForUser("int-flag", user); + listener.Countdown.Wait(); Assert.False(listener.FeatureFlags.ContainsKey("int-flag")); } @@ -99,12 +102,13 @@ public void RemoveFlagTellsListenerManagerToTellListenersFlagWasDeleted() [Fact] public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() { - var flagCacheManager = ManagerWithCachedFlags(); - var listener = new TestListener(); + var listener = new TestListener(1); listenerManager.RegisterListener(listener, "int-flag"); + var flagCacheManager = ManagerWithCachedFlags(); var newFlagsJson = "{\"int-flag\":{\"value\":5}}"; flagCacheManager.CacheFlagsFromService(TestUtil.DecodeFlagsJson(newFlagsJson), user); + listener.Countdown.Wait(); Assert.Equal(5, listener.FeatureFlags["int-flag"].ToObject()); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 45afe92e..4c18fb94 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -217,10 +217,9 @@ public void CanRegisterListener() using (var client = Client()) { var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(); + var listener = new TestListener(1); client.RegisterFeatureFlagListener("user1-flag", listener); - listenerMgr.FlagWasUpdated("user1-flag", 7); - Assert.Equal(7, listener.FeatureFlags["user1-flag"].ToObject()); + Assert.True(client.IsFeatureFlagListenerRegistered("user1-flag", listener)); } } @@ -230,14 +229,12 @@ public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerMan using (var client = Client()) { var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(); + var listener = new TestListener(1); client.RegisterFeatureFlagListener("user2-flag", listener); - listenerMgr.FlagWasUpdated("user2-flag", 7); - Assert.Equal(7, listener.FeatureFlags["user2-flag"]); + Assert.True(client.IsFeatureFlagListenerRegistered("user2-flag", listener)); client.UnregisterFeatureFlagListener("user2-flag", listener); - listenerMgr.FlagWasUpdated("user2-flag", 12); - Assert.NotEqual(12, listener.FeatureFlags["user2-flag"]); + Assert.False(client.IsFeatureFlagListenerRegistered("user2-flag", listener)); } } From 4c7ee0f738c3d90ce532d4c6981f57a9eea3a3ad Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 20:03:30 -0700 Subject: [PATCH 161/499] copy Android implementation from Xamarin.Essentials --- .../PlatformSpecific/AsyncScheduler.android.cs | 16 ++++++++++++---- .../PlatformSpecific/AsyncScheduler.shared.cs | 3 +-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs index 4a62552f..ccb8dee3 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -5,9 +5,17 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { - private static void PlatformScheduleAction(Action a) + private static Handler handler; + + private static void PlatformScheduleAction(Action a) { - - } - } + // This is based on the Android implementation in Xamarin.Essentials: + // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs + if (handler?.Looper != Looper.MainLooper) + { + handler = new Handler(Looper.MainLooper); + } + handler.Post(a); + } + } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs index 869201bf..b2f9b44a 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs @@ -2,8 +2,7 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { - // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. - // It provides a method for asynchronously starting tasks, such as event handlers, using a mechanism + // This provides a method for asynchronously starting tasks, such as event handlers, using a mechanism // that may vary by platform. internal static partial class AsyncScheduler { From 3a2a2e00fc366cc76c704dbc5a3f3db959d87fbe Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 20:03:59 -0700 Subject: [PATCH 162/499] indents --- .../AsyncScheduler.android.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs index ccb8dee3..ad62cbb5 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -5,17 +5,17 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { - private static Handler handler; + private static Handler handler; - private static void PlatformScheduleAction(Action a) + private static void PlatformScheduleAction(Action a) { - // This is based on the Android implementation in Xamarin.Essentials: - // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs - if (handler?.Looper != Looper.MainLooper) - { - handler = new Handler(Looper.MainLooper); - } - handler.Post(a); - } - } + // This is based on the Android implementation in Xamarin.Essentials: + // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs + if (handler?.Looper != Looper.MainLooper) + { + handler = new Handler(Looper.MainLooper); + } + handler.Post(a); + } + } } From 184a0543e3db4db2a26746961c88eb328ecd8912 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 20:06:26 -0700 Subject: [PATCH 163/499] indents --- .../PlatformSpecific/AsyncScheduler.ios.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs index 54a9a8d9..de4a2a55 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs @@ -3,11 +3,11 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { - internal static partial class AsyncScheduler - { + internal static partial class AsyncScheduler + { private static void PlatformScheduleAction(Action a) - { + { NSRunLoop.Main.BeginInvokeOnMainThread(a.Invoke); } - } + } } From 81aab83db90a949aa864c264c9bf1a86f3d51a65 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 8 Jul 2019 20:38:04 -0700 Subject: [PATCH 164/499] doc comment --- src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index 9f13e1f1..7773eb49 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -152,9 +152,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Registers an instance of for a given flag key to observe /// flag value changes. - /// - /// It is important to note that this callback scheme will need to be handled on the Main UI thread, - /// if you plan on updating UI components with flag values. + /// + /// On platforms that have a main UI thread (such as iOS and Android), the listener is guaranteed to + /// be called on that thread; on other platforms, the SDK uses a thread pool. Either way, the listener + /// is called asynchronously after whichever SDK action triggered the flag change has already completed-- + /// so as to avoid deadlocks, in case the action was also on the main thread, or on a thread that was + /// holding a lock on some application resource that the listener also uses. /// /// /// The flag key you want to observe changes for. From 001b98cd6793326aebf9054c46351cc7c9900a7f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 9 Jul 2019 12:43:51 -0700 Subject: [PATCH 165/499] simpler Android AsyncScheduler implementation --- .../PlatformSpecific/AsyncScheduler.android.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs index ad62cbb5..d36e44ea 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -5,16 +5,14 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { - private static Handler handler; - private static void PlatformScheduleAction(Action a) { - // This is based on the Android implementation in Xamarin.Essentials: - // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs - if (handler?.Looper != Looper.MainLooper) - { - handler = new Handler(Looper.MainLooper); - } + // Note that this logic is different from the implementation of the equivalent method in Xamarin Essentials + // (https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs); + // it creates a new Handler object each time rather than lazily creating a static one. This avoids a potential + // race condition, at the expense of creating more ephemeral objects. However, in our use case we do not + // expect this method to be called very frequently since we are using it for flag change listeners only. + var handler = new Handler(Looper.MainLooper); handler.Post(a); } } From 89897d83a8383b667ba42be82f157f81b6f32c8d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 11 Jul 2019 16:04:42 -0700 Subject: [PATCH 166/499] typo --- scripts/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package.sh b/scripts/package.sh index 1448f731..710d2b4b 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -22,7 +22,7 @@ rm -f ./src/LaunchDarkly.XamarinSdk/bin/Release/*.nupkg # Build the project for all target frameworks. This includes building the .nupkg, because of # the directive in our project file. -msbuild /restore /p:Configuration:$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore /p:Configuration=$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by CI jobs in config.yml) From 99cec42302fe21ccbbaea9d2030cef69c520c81d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 11 Jul 2019 16:11:39 -0700 Subject: [PATCH 167/499] add test for deferring listener calls --- .../FeatureFlagListenerTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs index 2eae60a1..2bc58016 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs @@ -115,6 +115,28 @@ public void ListenerGetsUpdatedWhenManagerFlagDeleted() Assert.False(listener.FeatureFlags.ContainsKey(INT_FLAG)); } + + [Fact] + public void ListenerCallIsDeferred() + { + // This verifies that we are not making synchronous calls to listeners, so they cannot deadlock by trying to + // acquire some resource that is being held by the caller. There are three possible things that can happen: + // 1. We call the listener synchronously; listener.Called gets set to true before FlagWasUpdated returns. Fail. + // 2. The listener is queued somewhere for later execution, so it doesn't even start to run before the end of + // the test. Pass. + // 3. The listener starts executing immediately on another thread; that's OK too, because the lock(locker) block + // ensures it won't set Called until after we have checked it. Pass. + var manager = Manager(); + var locker = new object(); + var listener = new TestDeadlockListener(locker); + + manager.RegisterListener(listener, INT_FLAG); + lock (locker) + { + manager.FlagWasUpdated(INT_FLAG, 2); + Assert.False(listener.Called); + } + } } public class TestListener : IFeatureFlagListener @@ -154,4 +176,31 @@ public void Reset() Countdown = new CountdownEvent(ExpectedCalls); } } + + public class TestDeadlockListener : IFeatureFlagListener + { + private readonly object _locker; + public volatile bool Called; + + public TestDeadlockListener(object locker) + { + _locker = locker; + } + + public void FeatureFlagChanged(string featureFlagKey, JToken value) + { + lock(_locker) + { + Called = true; + } + } + + public void FeatureFlagDeleted(string featureFlagKey) + { + lock(_locker) + { + Called = true; + } + } + } } From aa2794437acd9a1df1a570c553ddad8a6657fcd5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 12 Jul 2019 12:58:20 -0700 Subject: [PATCH 168/499] allow singleton to be cleared on disposal; use better concurrency practices --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 33 ++++--- .../LdClientTests.cs | 24 ++++- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 98 +++++++++++++++---- 3 files changed, 120 insertions(+), 35 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 5bd1b674..7c1c803a 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -19,14 +19,17 @@ public sealed class LdClient : ILdMobileClient { private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); + static volatile LdClient instance; + /// - /// The singleton instance used by your application throughout its lifetime, can only be created once. + /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot + /// create a new client instance unless you first call on this one. /// - /// Use the designated static method - /// to set this LdClient instance. + /// Use the designated static methods or + /// to set this LdClient instance. /// /// The LdClient instance. - public static LdClient Instance { get; internal set; } + public static LdClient Instance => instance; /// /// The Configuration instance used to setup the LdClient. @@ -100,7 +103,7 @@ public sealed class LdClient : ILdMobileClient /// If you would rather this happen in an async fashion you can use . /// /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient + /// to instantiate the single instance of LdClient /// for the lifetime of your application. /// /// The singleton LdClient instance. @@ -164,18 +167,18 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); } - CreateInstance(config, user); + var c = CreateInstance(config, user); - if (Instance.Online) + if (c.Online) { - if (!Instance.StartUpdateProcessor(maxWaitTime)) + if (!c.StartUpdateProcessor(maxWaitTime)) { Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", maxWaitTime.TotalMilliseconds); } } - return Instance; + return c; } /// @@ -215,7 +218,7 @@ static LdClient CreateInstance(Configuration configuration, User user) } var c = new LdClient(configuration, user); - Instance = c; + Interlocked.CompareExchange(ref instance, c, null); Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); return c; } @@ -510,7 +513,7 @@ User DecorateUser(User user) return newUser; } - void IDisposable.Dispose() + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); @@ -525,9 +528,17 @@ void Dispose(bool disposing) BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; updateProcessor.Dispose(); eventProcessor.Dispose(); + + // Reset the static Instance to null *if* it was referring to this instance + DetachInstance(); } } + internal void DetachInstance() // exposed for testing + { + Interlocked.CompareExchange(ref instance, null, this); + } + /// public Version Version { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 4c18fb94..8eabf37e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -79,17 +79,31 @@ public void IdentifyAsyncWithNullUserThrowsException() [Fact] public void SharedClientIsTheOnlyClientAvailable() { - lock (TestUtil.ClientInstanceLock) + TestUtil.WithClientLock(() => { var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); - } - } - TestUtil.ClearClient(); + } + TestUtil.ClearClient(); + }); } - + + [Fact] + public void CanCreateNewClientAfterDisposingOfSharedInstance() + { + TestUtil.WithClientLock(() => + { + TestUtil.ClearClient(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + Assert.Null(LdClient.Instance); + // Dispose() is called automatically at end of "using" block + using (var client1 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + }); + } + [Fact] public void ConnectionManagerShouldKnowIfOnlineOrNot() { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index be5d187b..05b35799 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Client; using Newtonsoft.Json; @@ -11,20 +12,83 @@ public static class TestUtil { // Any tests that are going to access the static LdClient.Instance must hold this lock, // to avoid interfering with tests that use CreateClient. - public static readonly object ClientInstanceLock = new object(); + private static readonly SemaphoreSlim ClientInstanceLock = new SemaphoreSlim(1); + + private static ThreadLocal InClientLock = new ThreadLocal(); + + public static T WithClientLock(Func f) + { + // This cumbersome combination of a ThreadLocal and a SemaphoreSlim is simply because 1. we have to use + // SemaphoreSlim (I think) since there's no way to wait on a regular lock in *async* code, and 2. SemaphoreSlim + // is not reentrant, so we need to make sure a thread can't block itself. + if (InClientLock.Value) + { + return f.Invoke(); + } + ClientInstanceLock.Wait(); + try + { + InClientLock.Value = true; + return f.Invoke(); + } + finally + { + InClientLock.Value = false; + ClientInstanceLock.Release(); + } + } + + public static void WithClientLock(Action a) + { + if (InClientLock.Value) + { + a.Invoke(); + return; + } + ClientInstanceLock.Wait(); + try + { + InClientLock.Value = true; + a.Invoke(); + } + finally + { + InClientLock.Value = false; + ClientInstanceLock.Release(); + } + } + + public static async Task WithClientLockAsync(Func> f) + { + if (InClientLock.Value) + { + return await f.Invoke(); + } + await ClientInstanceLock.WaitAsync(); + try + { + InClientLock.Value = true; + return await f.Invoke(); + } + finally + { + InClientLock.Value = false; + ClientInstanceLock.Release(); + } + } // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can // instantiate their own independent clients. Application code cannot do this because // the LdClient.Instance setter has internal scope. public static LdClient CreateClient(Configuration config, User user, TimeSpan? timeout = null) { - ClearClient(); - lock (ClientInstanceLock) - { + return WithClientLock(() => + { + ClearClient(); LdClient client = LdClient.Init(config, user, timeout ?? TimeSpan.FromSeconds(1)); - LdClient.Instance = null; + client.DetachInstance(); return client; - } + }); } // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can @@ -32,25 +96,21 @@ public static LdClient CreateClient(Configuration config, User user, TimeSpan? t // the LdClient.Instance setter has internal scope. public static async Task CreateClientAsync(Configuration config, User user) { - ClearClient(); - LdClient client = await LdClient.InitAsync(config, user); - lock (ClientInstanceLock) + return await WithClientLockAsync(async () => { - LdClient.Instance = null; - } - return client; + ClearClient(); + LdClient client = await LdClient.InitAsync(config, user); + client.DetachInstance(); + return client; + }); } public static void ClearClient() { - lock (ClientInstanceLock) + WithClientLock(() => { - if (LdClient.Instance != null) - { - (LdClient.Instance as IDisposable).Dispose(); - LdClient.Instance = null; - } - } + LdClient.Instance?.Dispose(); + }); } public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) From 267a0e0abc6585a2ed3326db8424f967f8d23fda Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 12 Jul 2019 13:06:24 -0700 Subject: [PATCH 169/499] fix race condition in CreateInstance --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 7c1c803a..b84688b1 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -20,6 +20,7 @@ public sealed class LdClient : ILdMobileClient private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); static volatile LdClient instance; + static readonly object createInstanceLock = new object(); /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot @@ -212,15 +213,18 @@ public static Task InitAsync(Configuration config, User user) static LdClient CreateInstance(Configuration configuration, User user) { - if (Instance != null) + lock (createInstanceLock) { - throw new Exception("LdClient instance already exists."); - } + if (Instance != null) + { + throw new Exception("LdClient instance already exists."); + } - var c = new LdClient(configuration, user); - Interlocked.CompareExchange(ref instance, c, null); - Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); - return c; + var c = new LdClient(configuration, user); + Interlocked.CompareExchange(ref instance, c, null); + Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); + return c; + } } bool StartUpdateProcessor(TimeSpan maxWaitTime) From 4b014fed232920d41be18c8597762a681edd3974 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 15 Jul 2019 11:01:30 -0700 Subject: [PATCH 170/499] add note about VS 2015 issue --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e608de4d..e37359e7 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Supported platforms This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 1.6, 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. +Note that if you are targeting .NET Framework, there is a [known issue](https://stackoverflow.com/questions/46788323/installing-a-netstandard-2-0-nuget-package-into-a-vs2015-net-4-6-1-project) in Visual Studio 2015 where it does not correctly detect compatibility with .NET Standard packages. This is not a problem in later versions of Visual Studio. + Getting started ----------- From 30a7359c7dc61a4f6ab33339aad3f1933913a11d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Jul 2019 11:15:07 -0700 Subject: [PATCH 171/499] rethink flag change event interface and implementation --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 8 +- src/LaunchDarkly.XamarinSdk/Factory.cs | 8 +- .../FeatureFlagListenerManager.cs | 116 ---------- .../FlagCacheManager.cs | 76 ++++--- .../FlagChangedEvent.cs | 87 ++++++++ .../IFeatureFlagListener.cs | 42 ---- .../IFeatureFlagListenerManager.cs | 17 -- .../ILdMobileClient.cs | 50 +++-- src/LaunchDarkly.XamarinSdk/LdClient.cs | 59 +++-- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 6 +- .../FeatureFlagListenerTests.cs | 206 ------------------ .../FlagCacheManagerTests.cs | 48 ++-- .../FlagChangedEventTests.cs | 138 ++++++++++++ .../LdClientTests.cs | 29 +-- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 3 +- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 6 +- 16 files changed, 380 insertions(+), 519 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs create mode 100644 src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 1d31887e..763ddfb0 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -134,7 +134,7 @@ public class Configuration : IMobileConfiguration internal Func UpdateProcessorFactory { get; set; } internal IPersistentStorage PersistentStorage { get; set; } internal IDeviceInfo DeviceInfo { get; set; } - internal IFeatureFlagListenerManager FeatureFlagListenerManager { get; set; } + internal IFlagChangedEventManager FlagChangedEventManager { get; set; } /// /// Default value for . @@ -207,7 +207,7 @@ public static Configuration Default(string mobileKey) { if (String.IsNullOrEmpty(mobileKey)) { - throw new ArgumentOutOfRangeException("mobileKey", "key is required"); + throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); } var defaultConfiguration = new Configuration { @@ -647,9 +647,9 @@ internal static Configuration WithDeviceInfo(this Configuration configuration, I return configuration; } - internal static Configuration WithFeatureFlagListenerManager(this Configuration configuration, IFeatureFlagListenerManager featureFlagListenerManager) + internal static Configuration WithFlagChangedEventManager(this Configuration configuration, IFlagChangedEventManager flagChangedEventManager) { - configuration.FeatureFlagListenerManager = featureFlagListenerManager; + configuration.FlagChangedEventManager = flagChangedEventManager; return configuration; } diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 3e98be0f..924f59b2 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -12,7 +12,7 @@ internal static class Factory internal static IFlagCacheManager CreateFlagCacheManager(Configuration configuration, IPersistentStorage persister, - IFlagListenerUpdater updater, + IFlagChangedEventManager flagChangedEventManager, User user) { if (configuration.FlagCacheManager != null) @@ -23,7 +23,7 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura { var inMemoryCache = new UserFlagInMemoryCache(); var deviceCache = configuration.PersistFlagValues ? new UserFlagDeviceCache(persister) as IUserFlagCache : new NullUserFlagCache(); - return new FlagCacheManager(inMemoryCache, deviceCache, updater, user); + return new FlagCacheManager(inMemoryCache, deviceCache, flagChangedEventManager, user); } } @@ -84,9 +84,9 @@ internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) return configuration.DeviceInfo ?? new DefaultDeviceInfo(); } - internal static IFeatureFlagListenerManager CreateFeatureFlagListenerManager(Configuration configuration) + internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration) { - return configuration.FeatureFlagListenerManager ?? new FeatureFlagListenerManager(); + return configuration.FlagChangedEventManager ?? new FlagChangedEventManager(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs deleted file mode 100644 index dfe46710..00000000 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagListenerManager.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Common.Logging; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - internal class FeatureFlagListenerManager : IFeatureFlagListenerManager, IFlagListenerUpdater - { - private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagListenerManager)); - - private readonly IDictionary> _map = - new Dictionary>(); - - private ReaderWriterLockSlim readWriteLock = new ReaderWriterLockSlim(); - - public void RegisterListener(IFeatureFlagListener listener, string flagKey) - { - readWriteLock.EnterWriteLock(); - try - { - if (!_map.TryGetValue(flagKey, out var list)) - { - list = new List(); - _map[flagKey] = list; - } - list.Add(listener); - } - finally - { - readWriteLock.ExitWriteLock(); - } - } - - public void UnregisterListener(IFeatureFlagListener listener, string flagKey) - { - readWriteLock.EnterWriteLock(); - try - { - if (_map.TryGetValue(flagKey, out var list)) - { - list.Remove(listener); - } - } - finally - { - readWriteLock.ExitWriteLock(); - } - } - - public bool IsListenerRegistered(IFeatureFlagListener listener, string flagKey) - { - readWriteLock.EnterReadLock(); - try - { - if (_map.TryGetValue(flagKey, out var list)) - { - return list.Contains(listener); - } - return false; - } - finally - { - readWriteLock.ExitReadLock(); - } - } - - public void FlagWasDeleted(string flagKey) - { - FireAction(flagKey, (listener) => listener.FeatureFlagDeleted(flagKey)); - } - - public void FlagWasUpdated(string flagKey, JToken value) - { - FireAction(flagKey, (listener) => listener.FeatureFlagChanged(flagKey, value)); - } - - private void FireAction(string flagKey, Action a) - { - IFeatureFlagListener[] listeners = null; - readWriteLock.EnterReadLock(); - try - { - if (_map.TryGetValue(flagKey, out var mutableListOfListeners)) - { - listeners = mutableListOfListeners.ToArray(); // this copies the list - } - } - finally - { - readWriteLock.ExitReadLock(); - } - if (listeners != null) - { - foreach (var l in listeners) - { - // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. - PlatformSpecific.AsyncScheduler.ScheduleAction(() => - { - try - { - a(l); - } - catch (Exception e) - { - Log.Warn("Unexpected exception from feature flag listener", e); - } - }); - } - } - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index d12680f9..a10d2bbc 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading; using LaunchDarkly.Client; +using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin { @@ -8,18 +10,18 @@ internal class FlagCacheManager : IFlagCacheManager { private readonly IUserFlagCache inMemoryCache; private readonly IUserFlagCache deviceCache; - private readonly IFlagListenerUpdater flagListenerUpdater; + private readonly IFlagChangedEventManager flagChangedEventManager; private ReaderWriterLockSlim readWriteLock = new ReaderWriterLockSlim(); public FlagCacheManager(IUserFlagCache inMemoryCache, IUserFlagCache deviceCache, - IFlagListenerUpdater flagListenerUpdater, + IFlagChangedEventManager flagChangedEventManager, User user) { this.inMemoryCache = inMemoryCache; this.deviceCache = deviceCache; - this.flagListenerUpdater = flagListenerUpdater; + this.flagChangedEventManager = flagChangedEventManager; var flagsFromDevice = deviceCache.RetrieveFlags(user); if (flagsFromDevice != null) @@ -43,6 +45,7 @@ public IDictionary FlagsForUser(User user) public void CacheFlagsFromService(IDictionary flags, User user) { + List> changes = null; readWriteLock.EnterWriteLock(); try { @@ -52,18 +55,16 @@ public void CacheFlagsFromService(IDictionary flags, User u foreach (var flag in flags) { - bool flagAlreadyExisted = previousFlags.ContainsKey(flag.Key); - bool flagValueChanged = false; - if (flagAlreadyExisted) + if (previousFlags.TryGetValue(flag.Key, out var originalFlag)) { - var originalFlag = previousFlags[flag.Key]; - flagValueChanged = originalFlag.value != flag.Value.value; - } - - // only update the Listeners if the flag value changed - if (flagValueChanged) - { - flagListenerUpdater.FlagWasUpdated(flag.Key, flag.Value.value); + if (!JToken.DeepEquals(originalFlag.value, flag.Value.value)) + { + if (changes == null) + { + changes = new List>(); + } + changes.Add(Tuple.Create(flag.Key, flag.Value.value, originalFlag.value)); + } } } } @@ -71,55 +72,78 @@ public void CacheFlagsFromService(IDictionary flags, User u { readWriteLock.ExitWriteLock(); } + if (changes != null) + { + foreach (var c in changes) + { + flagChangedEventManager.FlagWasUpdated(c.Item1, c.Item2, c.Item3); + } + } } public FeatureFlag FlagForUser(string flagKey, User user) { var flags = FlagsForUser(user); - FeatureFlag featureFlag; - if (flags.TryGetValue(flagKey, out featureFlag)) + if (flags.TryGetValue(flagKey, out var featureFlag)) { return featureFlag; } - return null; } public void RemoveFlagForUser(string flagKey, User user) { + JToken oldValue = null; readWriteLock.EnterWriteLock(); - try { var flagsForUser = inMemoryCache.RetrieveFlags(user); - flagsForUser.Remove(flagKey); - deviceCache.CacheFlagsForUser(flagsForUser, user); - inMemoryCache.CacheFlagsForUser(flagsForUser, user); - flagListenerUpdater.FlagWasDeleted(flagKey); + if (flagsForUser.TryGetValue(flagKey, out var flag)) + { + oldValue = flag.value; + flagsForUser.Remove(flagKey); + deviceCache.CacheFlagsForUser(flagsForUser, user); + inMemoryCache.CacheFlagsForUser(flagsForUser, user); + } } finally { readWriteLock.ExitWriteLock(); } - + if (oldValue != null) + { + flagChangedEventManager.FlagWasDeleted(flagKey, oldValue); + } } public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) { + bool changed = false; + JToken oldValue = null; readWriteLock.EnterWriteLock(); - try { var flagsForUser = inMemoryCache.RetrieveFlags(user); + if (flagsForUser.TryGetValue(flagKey, out var oldFlag)) + { + if (!JToken.DeepEquals(oldFlag.value, featureFlag.value)) + { + oldValue = oldFlag.value; + changed = true; + } + } flagsForUser[flagKey] = featureFlag; deviceCache.CacheFlagsForUser(flagsForUser, user); inMemoryCache.CacheFlagsForUser(flagsForUser, user); - flagListenerUpdater.FlagWasUpdated(flagKey, featureFlag.value); } finally { readWriteLock.ExitWriteLock(); } + if (changed) + { + flagChangedEventManager.FlagWasUpdated(flagKey, featureFlag.value, oldValue); + } } } } diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs new file mode 100644 index 00000000..de534699 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using Common.Logging; +using Newtonsoft.Json.Linq; + +namespace LaunchDarkly.Xamarin +{ + public class FlagChangedEventArgs + { + public string Key { get; private set; } + + public JToken NewValue { get; private set; } + + public JToken OldValue { get; private set; } + + public bool FlagWasDeleted { get; private set; } + + public bool NewBoolValue => NewValue.Value(); + + public string NewStringValue => NewValue.Value(); + + public int NewIntValue => NewValue.Value(); + + public float NewFloatValue => NewValue.Value(); + + internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) + { + Key = key; + NewValue = newValue; + OldValue = oldValue; + FlagWasDeleted = flagWasDeleted; + } + } + + internal interface IFlagChangedEventManager + { + event EventHandler FlagChanged; + void FlagWasDeleted(string flagKey, JToken oldValue); + void FlagWasUpdated(string flagKey, JToken newValue, JToken oldValue); + } + + internal class FlagChangedEventManager : IFlagChangedEventManager + { + private static readonly ILog Log = LogManager.GetLogger(typeof(IFlagChangedEventManager)); + + public event EventHandler FlagChanged; + + public bool IsHandlerRegistered(EventHandler handler) + { + return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); + } + + public void FlagWasDeleted(string flagKey, JToken oldValue) + { + FireEvent(new FlagChangedEventArgs(flagKey, null, oldValue, true)); + } + + public void FlagWasUpdated(string flagKey, JToken newValue, JToken oldValue) + { + FireEvent(new FlagChangedEventArgs(flagKey, newValue, oldValue, false)); + } + + private void FireEvent(FlagChangedEventArgs eventArgs) + { + var copyOfHandlers = FlagChanged; + var sender = this; + if (copyOfHandlers != null) + { + foreach (var h in copyOfHandlers.GetInvocationList()) + { + // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. + PlatformSpecific.AsyncScheduler.ScheduleAction(() => + { + try + { + h.DynamicInvoke(sender, eventArgs); + } + catch (Exception e) + { + Log.Warn("Unexpected exception from FlagChanged event handler", e); + } + }); + } + } + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs deleted file mode 100644 index 1ff877aa..00000000 --- a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListener.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - /// - /// Represents a callback listener for feature flag value changes. - /// - /// You should have your ViewController, Activity or custom class implement this interface if you want to - /// be notified of value changes for a given flag key. - /// - /// Look at for - /// usage of this interface. - /// - public interface IFeatureFlagListener - { - /// - /// Tells the implementer of this interface that the feature flag for the given key - /// was changed to the given value. - /// - /// It is important to know that this will not be called on your main UI thread, so if you plan - /// on updating the UI when this is called, you will need to use the appropriate pattern to post safely to the UI - /// main thread. - /// - /// The feature flag key. - /// The feature flag value that changed. - void FeatureFlagChanged(string featureFlagKey, JToken value); - - /// - /// Tells the implementer of this interface that the feature flag for the given key - /// was deleted on the LaunchDarkly service side. - /// - /// It is important to know that this will not be called on your main UI thread, so if you plan - /// on updating the UI when this is called, you will need to use the appropriate pattern to post safely to the UI - /// main thread. - /// - /// The feature flag key. - void FeatureFlagDeleted(string featureFlagKey); - } -} - diff --git a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs b/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs deleted file mode 100644 index 0e9668af..00000000 --- a/src/LaunchDarkly.XamarinSdk/IFeatureFlagListenerManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - internal interface IFeatureFlagListenerManager : IFlagListenerUpdater - { - void RegisterListener(IFeatureFlagListener listener, string flagKey); - void UnregisterListener(IFeatureFlagListener listener, string flagKey); - bool IsListenerRegistered(IFeatureFlagListener listener, string flagKey); // used for testing - } - - internal interface IFlagListenerUpdater - { - void FlagWasUpdated(string flagKey, JToken value); - void FlagWasDeleted(string flagKey); - } -} \ No newline at end of file diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index 7773eb49..ee9eb6c9 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json.Linq; using LaunchDarkly.Common; using System.Threading.Tasks; @@ -150,28 +151,33 @@ public interface ILdMobileClient : ILdCommonClient IDictionary AllFlags(); /// - /// Registers an instance of for a given flag key to observe - /// flag value changes. - /// - /// On platforms that have a main UI thread (such as iOS and Android), the listener is guaranteed to - /// be called on that thread; on other platforms, the SDK uses a thread pool. Either way, the listener - /// is called asynchronously after whichever SDK action triggered the flag change has already completed-- - /// so as to avoid deadlocks, in case the action was also on the main thread, or on a thread that was - /// holding a lock on some application resource that the listener also uses. - /// - /// - /// The flag key you want to observe changes for. - /// The instance of the IFeatureFlagListener. - void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener); - - /// - /// Unregisters an instance of for a given flag key to stop observing - /// flag value changes. - /// + /// This event is triggered when the client has received an updated value for a feature flag. /// - /// The flag key you want to observe changes for. - /// The instance of the IFeatureFlagListener. - void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener); + /// + /// This could mean that the flag configuration was changed in LaunchDarkly, or that you have changed the current + /// user and the flag values are different for this user than for the previous user. The event is not + /// triggered for the flag values that are first obtained when the client is initialized. It is only triggered + /// if the newly received flag value is actually different from the previous one. + /// + /// The properties will indicate the key of the feature flag, the new value, + /// and the previous value. + /// + /// On platforms that have a main UI thread (such as iOS and Android), handlers for this event are guaranteed to + /// be called on that thread; on other platforms, the SDK uses a thread pool. Either way, the handler is called + /// called asynchronously after whichever SDK action triggered the flag change has already completed. This is to + /// avoid deadlocks, in case the action was also on the main thread, or on a thread that was holding a lock on + /// some application resource that the handler also uses. + /// + /// + /// + /// client.FlagChanged += (sender, eventArgs) => { + /// if (eventArgs.Key == "key-for-flag-i-am-watching") { + /// DoSomethingWithNewFlagValue(eventArgs.NewBoolValue); + /// } + /// }; + /// + /// + event EventHandler FlagChanged; /// /// Registers the user. diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index b84688b1..6e3580e4 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -44,20 +44,20 @@ public sealed class LdClient : ILdMobileClient /// The User. public User User { get; private set; } - object myLockObjForConnectionChange = new object(); - object myLockObjForUserUpdate = new object(); - - IFlagCacheManager flagCacheManager; - IConnectionManager connectionManager; - IMobileUpdateProcessor updateProcessor; - IEventProcessor eventProcessor; - IPersistentStorage persister; - IDeviceInfo deviceInfo; + readonly object myLockObjForConnectionChange = new object(); + readonly object myLockObjForUserUpdate = new object(); + + readonly IFlagCacheManager flagCacheManager; + readonly IConnectionManager connectionManager; + IMobileUpdateProcessor updateProcessor; // not readonly - may need to be recreated + readonly IEventProcessor eventProcessor; + readonly IPersistentStorage persister; + readonly IDeviceInfo deviceInfo; readonly EventFactory eventFactoryDefault = EventFactory.Default; readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; - IFeatureFlagListenerManager flagListenerManager; + internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing - SemaphoreSlim connectionLock; + readonly SemaphoreSlim connectionLock; // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) @@ -67,11 +67,11 @@ public sealed class LdClient : ILdMobileClient { if (configuration == null) { - throw new ArgumentNullException("configuration"); + throw new ArgumentNullException(nameof(configuration)); } if (user == null) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException(nameof(user)); } Config = configuration; @@ -80,11 +80,11 @@ public sealed class LdClient : ILdMobileClient persister = Factory.CreatePersistentStorage(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); - flagListenerManager = Factory.CreateFeatureFlagListenerManager(configuration); + flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); User = DecorateUser(user); - flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagListenerManager, User); + flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); connectionManager = Factory.CreateConnectionManager(configuration); updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null); eventProcessor = Factory.CreateEventProcessor(configuration); @@ -457,7 +457,7 @@ public async Task IdentifyAsync(User user) { if (user == null) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException(nameof(user)); } User newUser = DecorateUser(user); @@ -552,23 +552,18 @@ public Version Version } } - /// - public void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) - { - flagListenerManager.RegisterListener(listener, flagKey); - } - - /// - public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) - { - flagListenerManager.UnregisterListener(listener, flagKey); - } - - // for tests only - internal bool IsFeatureFlagListenerRegistered(string flagKey, IFeatureFlagListener listener) + /// + public event EventHandler FlagChanged { - return flagListenerManager.IsListenerRegistered(listener, flagKey); - } + add + { + flagChangedEventManager.FlagChanged += value; + } + remove + { + flagChangedEventManager.FlagChanged -= value; + } + } internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index ad9e5b13..58182c3f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -66,9 +66,6 @@ SharedTestCode\ConfigurationTest.cs - - SharedTestCode\FeatureFlagListenerTests.cs - SharedTestCode\FeatureFlagRequestorTests.cs @@ -78,6 +75,9 @@ SharedTestCode\FlagCacheManagerTests.cs + + SharedTestCode\FlagChangedEventTests.cs + SharedTestCode\LDClientEndToEndTests.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs deleted file mode 100644 index 2bc58016..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagListenerTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class FeatureFlagListenerTests : BaseTest - { - private const string INT_FLAG = "int-flag"; - private const string DOUBLE_FLAG = "double-flag"; - - FeatureFlagListenerManager Manager() - { - return new FeatureFlagListenerManager(); - } - - TestListener Listener(int expectedTimes) - { - return new TestListener(expectedTimes); - } - - [Fact] - public void CanRegisterListeners() - { - var manager = Manager(); - var listener1 = Listener(2); - var listener2 = Listener(2); - manager.RegisterListener(listener1, INT_FLAG); - manager.RegisterListener(listener1, DOUBLE_FLAG); - manager.RegisterListener(listener2, INT_FLAG); - manager.RegisterListener(listener2, DOUBLE_FLAG); - - manager.FlagWasUpdated(INT_FLAG, 7); - manager.FlagWasUpdated(DOUBLE_FLAG, 10.5); - listener1.Countdown.Wait(); - listener2.Countdown.Wait(); - - Assert.Equal(7, listener1.FeatureFlags[INT_FLAG]); - Assert.Equal(10.5, listener1.FeatureFlags[DOUBLE_FLAG]); - Assert.Equal(7, listener2.FeatureFlags[INT_FLAG]); - Assert.Equal(10.5, listener2.FeatureFlags[DOUBLE_FLAG]); - } - - [Fact] - public void CanUnregisterListeners() - { - var manager = Manager(); - var listener1 = Listener(2); - var listener2 = Listener(2); - manager.RegisterListener(listener1, INT_FLAG); - manager.RegisterListener(listener1, DOUBLE_FLAG); - manager.RegisterListener(listener2, INT_FLAG); - manager.RegisterListener(listener2, DOUBLE_FLAG); - - manager.FlagWasUpdated(INT_FLAG, 7); - manager.FlagWasUpdated(DOUBLE_FLAG, 10.5); - listener1.Countdown.Wait(); - listener2.Countdown.Wait(); - - Assert.Equal(7, listener1.FeatureFlags[INT_FLAG]); - Assert.Equal(10.5, listener1.FeatureFlags[DOUBLE_FLAG]); - Assert.Equal(7, listener2.FeatureFlags[INT_FLAG]); - Assert.Equal(10.5, listener2.FeatureFlags[DOUBLE_FLAG]); - - manager.UnregisterListener(listener1, INT_FLAG); - manager.UnregisterListener(listener2, INT_FLAG); - manager.UnregisterListener(listener1, DOUBLE_FLAG); - manager.UnregisterListener(listener2, DOUBLE_FLAG); - listener1.Reset(); - listener2.Reset(); - manager.FlagWasUpdated(INT_FLAG, 2); - manager.FlagWasUpdated(DOUBLE_FLAG, 12.5); - - // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. - Thread.Sleep(100); - - Assert.NotEqual(2, listener1.FeatureFlags[INT_FLAG]); - Assert.NotEqual(12.5, listener1.FeatureFlags[DOUBLE_FLAG]); - Assert.NotEqual(2, listener2.FeatureFlags[INT_FLAG]); - Assert.NotEqual(12.5, listener2.FeatureFlags[DOUBLE_FLAG]); - } - - [Fact] - public void ListenerGetsUpdatedFlagValues() - { - var manager = Manager(); - var listener1 = Listener(1); - var listener2 = Listener(1); - manager.RegisterListener(listener1, INT_FLAG); - manager.RegisterListener(listener2, INT_FLAG); - - manager.FlagWasUpdated(INT_FLAG, JToken.FromObject(99)); - listener1.Countdown.Wait(); - listener2.Countdown.Wait(); - - Assert.Equal(99, listener1.FeatureFlags[INT_FLAG].Value()); - Assert.Equal(99, listener2.FeatureFlags[INT_FLAG].Value()); - } - - [Fact] - public void ListenerGetsUpdatedWhenManagerFlagDeleted() - { - var manager = Manager(); - var listener = Listener(1); - manager.RegisterListener(listener, INT_FLAG); - - manager.FlagWasUpdated(INT_FLAG, 2); - listener.Countdown.Wait(); - Assert.True(listener.FeatureFlags.ContainsKey(INT_FLAG)); - - listener.Reset(); - manager.FlagWasDeleted(INT_FLAG); - listener.Countdown.Wait(); - - Assert.False(listener.FeatureFlags.ContainsKey(INT_FLAG)); - } - - [Fact] - public void ListenerCallIsDeferred() - { - // This verifies that we are not making synchronous calls to listeners, so they cannot deadlock by trying to - // acquire some resource that is being held by the caller. There are three possible things that can happen: - // 1. We call the listener synchronously; listener.Called gets set to true before FlagWasUpdated returns. Fail. - // 2. The listener is queued somewhere for later execution, so it doesn't even start to run before the end of - // the test. Pass. - // 3. The listener starts executing immediately on another thread; that's OK too, because the lock(locker) block - // ensures it won't set Called until after we have checked it. Pass. - var manager = Manager(); - var locker = new object(); - var listener = new TestDeadlockListener(locker); - - manager.RegisterListener(listener, INT_FLAG); - lock (locker) - { - manager.FlagWasUpdated(INT_FLAG, 2); - Assert.False(listener.Called); - } - } - } - - public class TestListener : IFeatureFlagListener - { - private readonly int ExpectedCalls; - public CountdownEvent Countdown; - - public TestListener(int expectedCalls) - { - ExpectedCalls = expectedCalls; - Reset(); - } - - IDictionary featureFlags = new Dictionary(); - public IDictionary FeatureFlags - { - get - { - return featureFlags; - } - } - - public void FeatureFlagChanged(string featureFlagKey, JToken value) - { - featureFlags[featureFlagKey] = value; - Countdown.Signal(); - } - - public void FeatureFlagDeleted(string featureFlagKey) - { - featureFlags.Remove(featureFlagKey); - Countdown.Signal(); - } - - public void Reset() - { - Countdown = new CountdownEvent(ExpectedCalls); - } - } - - public class TestDeadlockListener : IFeatureFlagListener - { - private readonly object _locker; - public volatile bool Called; - - public TestDeadlockListener(object locker) - { - _locker = locker; - } - - public void FeatureFlagChanged(string featureFlagKey, JToken value) - { - lock(_locker) - { - Called = true; - } - } - - public void FeatureFlagDeleted(string featureFlagKey) - { - lock(_locker) - { - Called = true; - } - } - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index a7435808..9c8cef20 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -14,7 +14,7 @@ public class FlagCacheManagerTests : BaseTest IUserFlagCache deviceCache = new UserFlagInMemoryCache(); IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); - FeatureFlagListenerManager listenerManager = new FeatureFlagListenerManager(); + FlagChangedEventManager listenerManager = new FlagChangedEventManager(); User user = User.WithKey("someKey"); @@ -47,7 +47,7 @@ public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() } [Fact] - public void ShouldBeAbleToRemoveFlagForUser() + public void CanRemoveFlagForUser() { var manager = ManagerWithCachedFlags(); manager.RemoveFlagForUser("int-key", user); @@ -55,7 +55,7 @@ public void ShouldBeAbleToRemoveFlagForUser() } [Fact] - public void ShouldBeAbleToUpdateFlagForUser() + public void CanUpdateFlagForUser() { var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); @@ -68,49 +68,53 @@ public void ShouldBeAbleToUpdateFlagForUser() } [Fact] - public void UpdateFlagUpdatesTheFlagOnListenerManager() + public void UpdateFlagSendsFlagChangeEvent() { - var listener = new TestListener(1); - listenerManager.RegisterListener(listener, "int-flag"); + var listener = new FlagChangedEventSink(); + listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); updatedFeatureFlag.value = JToken.FromObject(7); flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); - listener.Countdown.Wait(); - Assert.Equal(7, listener.FeatureFlags["int-flag"].ToObject()); + var e = listener.Await(); + Assert.Equal("int-flag", e.Key); + Assert.Equal(7, e.NewIntValue); + Assert.False(e.FlagWasDeleted); } [Fact] - public void RemoveFlagTellsListenerManagerToTellListenersFlagWasDeleted() - { - var listener = new TestListener(1); - listener.FeatureFlags["int-flag"] = JToken.FromObject(1); - listenerManager.RegisterListener(listener, "int-flag"); + public void RemoveFlagSendsFlagChangeEvent() + { + var listener = new FlagChangedEventSink(); + listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); updatedFeatureFlag.value = JToken.FromObject(7); - flagCacheManager.RemoveFlagForUser("int-flag", user); - listener.Countdown.Wait(); - - Assert.False(listener.FeatureFlags.ContainsKey("int-flag")); + flagCacheManager.RemoveFlagForUser("int-flag", user); + + var e = listener.Await(); + Assert.Equal("int-flag", e.Key); + Assert.True(e.FlagWasDeleted); } [Fact] public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() - { - var listener = new TestListener(1); - listenerManager.RegisterListener(listener, "int-flag"); + { + var listener = new FlagChangedEventSink(); + listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); var newFlagsJson = "{\"int-flag\":{\"value\":5}}"; flagCacheManager.CacheFlagsFromService(TestUtil.DecodeFlagsJson(newFlagsJson), user); - listener.Countdown.Wait(); - Assert.Equal(5, listener.FeatureFlags["int-flag"].ToObject()); + var e = listener.Await(); + Assert.Equal("int-flag", e.Key); + Assert.Equal(5, e.NewIntValue); + Assert.False(e.FlagWasDeleted); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs new file mode 100644 index 00000000..01dcc176 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -0,0 +1,138 @@ +using System.Collections.Concurrent; +using System.Threading; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class FlagChangedEventTests : BaseTest + { + private const string INT_FLAG = "int-flag"; + private const string DOUBLE_FLAG = "double-flag"; + + FlagChangedEventManager Manager() + { + return new FlagChangedEventManager(); + } + + [Fact] + public void CanRegisterListeners() + { + var manager = Manager(); + var listener1 = new FlagChangedEventSink(); + var listener2 = new FlagChangedEventSink(); + manager.FlagChanged += listener1.Handler; + manager.FlagChanged += listener2.Handler; + + manager.FlagWasUpdated(INT_FLAG, 7, 6); + manager.FlagWasUpdated(DOUBLE_FLAG, 10.5, 9.5); + + var event1a = listener1.Await(); + var event1b = listener1.Await(); + var event2a = listener2.Await(); + var event2b = listener2.Await(); + + Assert.Equal(INT_FLAG, event1a.Key); + Assert.Equal(INT_FLAG, event2a.Key); + Assert.Equal(7, event1a.NewIntValue); + Assert.Equal(7, event2a.NewIntValue); + Assert.Equal(6, event1a.OldValue); + Assert.Equal(6, event2a.OldValue); + Assert.False(event1a.FlagWasDeleted); + Assert.False(event2a.FlagWasDeleted); + + Assert.Equal(DOUBLE_FLAG, event1b.Key); + Assert.Equal(DOUBLE_FLAG, event2b.Key); + Assert.Equal(10.5, event1b.NewFloatValue); + Assert.Equal(10.5, event2b.NewFloatValue); + Assert.Equal(9.5, event1b.OldValue); + Assert.Equal(9.5, event2b.OldValue); + Assert.False(event1b.FlagWasDeleted); + Assert.False(event2b.FlagWasDeleted); + } + + [Fact] + public void CanUnregisterListeners() + { + var manager = Manager(); + var listener1 = new FlagChangedEventSink(); + var listener2 = new FlagChangedEventSink(); + manager.FlagChanged += listener1.Handler; + manager.FlagChanged += listener2.Handler; + + manager.FlagChanged -= listener1.Handler; + + manager.FlagWasUpdated(INT_FLAG, 7, 6); + + var e = listener2.Await(); + Assert.Equal(INT_FLAG, e.Key); + Assert.Equal(7, e.NewIntValue); + Assert.Equal(6, e.OldValue); + + // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. + Thread.Sleep(100); + + Assert.True(listener1.IsEmpty); + } + + [Fact] + public void ListenerGetsUpdatedWhenManagerFlagDeleted() + { + var manager = Manager(); + var listener = new FlagChangedEventSink(); + manager.FlagChanged += listener.Handler; + + manager.FlagWasDeleted(INT_FLAG, 1); + + var e = listener.Await(); + Assert.Equal(INT_FLAG, e.Key); + Assert.Equal(1, e.OldValue); + Assert.True(e.FlagWasDeleted); + } + + [Fact] + public void ListenerCallIsDeferred() + { + // This verifies that we are not making synchronous calls to listeners, so they cannot deadlock by trying to + // acquire some resource that is being held by the caller. There are three possible things that can happen: + // 1. We call the listener synchronously; listener.Called gets set to true before FlagWasUpdated returns. Fail. + // 2. The listener is queued somewhere for later execution, so it doesn't even start to run before the end of + // the test. Pass. + // 3. The listener starts executing immediately on another thread; that's OK too, because the lock(locker) block + // ensures it won't set Called until after we have checked it. Pass. + var manager = Manager(); + var locker = new object(); + var called = false; + + manager.FlagChanged += (sender, args) => + { + lock (locker) + { + Volatile.Write(ref called, true); + } + }; + + lock (locker) + { + manager.FlagWasUpdated(INT_FLAG, 2, 1); + Assert.False(Volatile.Read(ref called)); + } + } + } + + public class FlagChangedEventSink + { + private BlockingCollection _events = new BlockingCollection(); + + public void Handler(object sender, FlagChangedEventArgs args) + { + _events.Add(args); + } + + public FlagChangedEventArgs Await() + { + return _events.Take(); + } + + public bool IsEmpty => _events.Count == 0; + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 8eabf37e..4f2eda7c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -226,29 +226,18 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() } [Fact] - public void CanRegisterListener() + public void CanRegisterAndUnregisterFlagChangedHandlers() { using (var client = Client()) { - var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(1); - client.RegisterFeatureFlagListener("user1-flag", listener); - Assert.True(client.IsFeatureFlagListenerRegistered("user1-flag", listener)); - } - } - - [Fact] - public void UnregisterListenerUnregistersPassedInListenerForFlagKeyOnListenerManager() - { - using (var client = Client()) - { - var listenerMgr = client.Config.FeatureFlagListenerManager as FeatureFlagListenerManager; - var listener = new TestListener(1); - client.RegisterFeatureFlagListener("user2-flag", listener); - Assert.True(client.IsFeatureFlagListenerRegistered("user2-flag", listener)); - - client.UnregisterFeatureFlagListener("user2-flag", listener); - Assert.False(client.IsFeatureFlagListenerRegistered("user2-flag", listener)); + EventHandler handler1 = (sender, args) => { }; + EventHandler handler2 = (sender, args) => { }; + var eventManager = client.flagChangedEventManager as FlagChangedEventManager; + client.FlagChanged += handler1; + client.FlagChanged += handler2; + client.FlagChanged -= handler1; + Assert.False(eventManager.IsHandlerRegistered(handler1)); + Assert.True(eventManager.IsHandlerRegistered(handler2)); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 05b35799..ca31976b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -148,8 +148,7 @@ public static Configuration ConfigWithFlagsJson(User user, string appKey, string .WithEventProcessor(new MockEventProcessor()) .WithUpdateProcessorFactory(MockPollingProcessor.Factory(null)) .WithPersistentStorage(new MockPersistentStorage()) - .WithDeviceInfo(new MockDeviceInfo("")) - .WithFeatureFlagListenerManager(new FeatureFlagListenerManager()); + .WithDeviceInfo(new MockDeviceInfo("")); return configuration; } } diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index a2988ae1..b04ca764 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -151,9 +151,6 @@ SharedTestCode\ConfigurationTest.cs - - SharedTestCode\FeatureFlagListenerTests.cs - SharedTestCode\FeatureFlagRequestorTests.cs @@ -163,6 +160,9 @@ SharedTestCode\FlagCacheManagerTests.cs + + SharedTestCode\FlagChangedEventTests.cs + SharedTestCode\LdClientEndToEndTests.cs From 157b9d087e93f093a239faf43d1142b9e7e12c90 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Jul 2019 11:50:21 -0700 Subject: [PATCH 172/499] doc comments --- .../FlagChangedEvent.cs | 79 +++++++++++++++++-- .../ILdMobileClient.cs | 3 +- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index de534699..a630cf0c 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -5,23 +5,88 @@ namespace LaunchDarkly.Xamarin { + /// + /// An event object that is sent to handlers for the event. + /// public class FlagChangedEventArgs { + /// + /// The unique key of the feature flag whose value has changed. + /// public string Key { get; private set; } + /// + /// The updated value of the flag for the current user. + /// + /// + /// Since flag values can be of any JSON type, this property is a . The properties + /// , , etc. are shortcuts for accessing its value with a + /// specific data type. + /// + /// Flag evaluations always produce non-null values, but this property could still be null if the flag was + /// completely deleted or if it could not be evaluated due to an error of some kind. + /// + /// Note that in those cases, the Variation methods may return a different result from this property, + /// because of their "default value" behavior. For instance, if the flag "feature1" has been deleted, the + /// following expression will return the string "xyz", because that is the default value that you specified + /// in the method call: + /// + /// + /// client.StringVariation("feature1", "xyz"); + /// + /// + /// But when a FlagChangedEvent is sent for the deletion of the flag, it has no way to know that you + /// would have specified "xyz" as a default value when evaluating the flag, so NewValue will simply + /// be null. + /// public JToken NewValue { get; private set; } + /// + /// The last known value of the flag for the current user prior to the update. + /// public JToken OldValue { get; private set; } + /// + /// True if the flag was completely removed from the environment. + /// public bool FlagWasDeleted { get; private set; } - public bool NewBoolValue => NewValue.Value(); - - public string NewStringValue => NewValue.Value(); - - public int NewIntValue => NewValue.Value(); - - public float NewFloatValue => NewValue.Value(); + /// + /// Shortcut for converting to a bool. Returns false if the value is null or is not a + /// boolean (will never throw an exception). + /// + public bool NewBoolValue => AsType(ValueTypes.Bool, false); + + /// + /// Shortcut for converting to a string. Returns null if the value is null or is not a + /// string (will never throw an exception). + /// + public string NewStringValue => AsType(ValueTypes.String, null); + + /// + /// Shortcut for converting to an int. Returns 0 if the value is null or is not + /// numeric (will never throw an exception). + /// + public int NewIntValue => AsType(ValueTypes.Int, 0); + + /// + /// Shortcut for converting to a float. Returns 0 if the value is null or is not + /// numeric (will never throw an exception). + /// + public float NewFloatValue => AsType(ValueTypes.Float, 0); + + private T AsType(ValueType valueType, T defaultValue) + { + if (NewValue != null) + { + try + { + return valueType.ValueFromJson(NewValue); + } + catch (ArgumentException) {} + } + return defaultValue; + } internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) { diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index ee9eb6c9..21f33952 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -155,8 +155,7 @@ public interface ILdMobileClient : ILdCommonClient /// /// /// This could mean that the flag configuration was changed in LaunchDarkly, or that you have changed the current - /// user and the flag values are different for this user than for the previous user. The event is not - /// triggered for the flag values that are first obtained when the client is initialized. It is only triggered + /// user and the flag values are different for this user than for the previous user. The event is only triggered /// if the newly received flag value is actually different from the previous one. /// /// The properties will indicate the key of the feature flag, the new value, From 64eef47fe49a1bf80d1a969224c797d1884aa59a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Jul 2019 18:07:32 -0700 Subject: [PATCH 173/499] try pinning to older versions of Mono packages --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6432a0c7..d0284371 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: sudo apt -y install apt-transport-https dirmngr gnupg ca-certificates sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - sudo apt -y install mono-devel nuget libzip4 libpulse0 + sudo apt -y install mono-devel=5.20.1.19-0xamarin2+debian9b1 nuget=4.8.2.5835.bin-0xamarin1+debian9b1 libzip4 libpulse0 # libpulse0 is required to run the emulator; the result are required to build the app - restore_cache: From 7e986ab4a55435a995a70b2cd362b1b78d718a85 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Jul 2019 18:18:05 -0700 Subject: [PATCH 174/499] pin apt repo --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d0284371..42fffc9a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,8 +38,8 @@ jobs: command: | sudo apt -y install apt-transport-https dirmngr gnupg ca-certificates sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF - echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - sudo apt -y install mono-devel=5.20.1.19-0xamarin2+debian9b1 nuget=4.8.2.5835.bin-0xamarin1+debian9b1 libzip4 libpulse0 + echo "deb https://download.mono-project.com/repo/debian stable-stretch/snapshots/5.20.1.19 main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update + sudo apt -y install mono-devel nuget libzip4 libpulse0 # libpulse0 is required to run the emulator; the result are required to build the app - restore_cache: From 2569142d7b442e648487a6cf62b10cb4a4738dc1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 24 Jul 2019 14:06:40 -0700 Subject: [PATCH 175/499] bump common lib version, use User.Builder --- .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 23 ++++++++++++++----- .../LDClientEndToEndTests.cs | 4 +++- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientTests.cs | 23 ++++++++++--------- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 3c13cb95..0ea57782 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 6e3580e4..6e3e2c31 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -499,22 +499,33 @@ void ClearUpdateProcessor() User DecorateUser(User user) { - var newUser = new User(user); + IUserBuilder buildUser = null; if (UserMetadata.DeviceName != null) { - newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("device", UserMetadata.DeviceName); } if (UserMetadata.OSName != null) { - newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("os", UserMetadata.OSName); } // If you pass in a user with a null or blank key, one will be assigned to them. if (String.IsNullOrEmpty(user.Key)) { - newUser.Key = deviceInfo.UniqueDeviceId(); - newUser.Anonymous = true; + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Key(deviceInfo.UniqueDeviceId()).Anonymous(true); } - return newUser; + return buildUser is null ? user : buildUser.Build(); } public void Dispose() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 183c7dc5..02f3bd8e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -154,7 +154,8 @@ await WithServerAsync(async server => server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); - var anonUser = User.WithKey(null).AndAnonymous(true); + var name = "Sue"; + var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. @@ -165,6 +166,7 @@ await WithServerAsync(async server => { Assert.NotNull(client.User.Key); generatedKey = client.User.Key; + Assert.Equal(name, client.User.Name); } using (var client = await TestUtil.CreateClientAsync(config, anonUser)) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 0f8ebb7f..cf929fbb 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 4f2eda7c..6729ca2d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -194,16 +194,17 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() [Fact] public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() { - var user = User.WithKey("") - .AndSecondaryKey("secondary") - .AndIpAddress("10.0.0.1") - .AndCountry("US") - .AndFirstName("John") - .AndLastName("Doe") - .AndName("John Doe") - .AndAvatar("images.google.com/myAvatar") - .AndEmail("test@example.com") - .AndCustomAttribute("attr", "value"); + var user = User.Builder("") + .SecondaryKey("secondary") + .IPAddress("10.0.0.1") + .Country("US") + .FirstName("John") + .LastName("Doe") + .Name("John Doe") + .Avatar("images.google.com/myAvatar") + .Email("test@example.com") + .Custom("attr", "value") + .Build(); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .WithDeviceInfo(new MockDeviceInfo(uniqueId)); @@ -218,7 +219,7 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() Assert.Equal(user.FirstName, newUser.FirstName); Assert.Equal(user.LastName, newUser.LastName); Assert.Equal(user.Name, newUser.Name); - Assert.Equal(user.IpAddress, newUser.IpAddress); + Assert.Equal(user.IPAddress, newUser.IPAddress); Assert.Equal(user.SecondaryKey, newUser.SecondaryKey); Assert.Equal(user.Custom["attr"], newUser.Custom["attr"]); Assert.True(newUser.Anonymous); From 8a6fe6acb14554e27f520242326ec928776aa4ab Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 24 Jul 2019 16:29:05 -0700 Subject: [PATCH 176/499] Moved some functions for greater clarity, fixed isInitialized behavior, fixed StartUpdateProcessorAsync hanging while offline --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 104 +++++++++--------- .../PlatformSpecific/Connectivity.android.cs | 4 - .../PlatformSpecific/Connectivity.shared.cs | 2 +- 3 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index e4d5058d..f0aebeed 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -55,22 +55,29 @@ public sealed class LdClient : ILdMobileClient SemaphoreSlim connectionLock; + bool online; + /// + public bool Online + { + get => online; + set + { + var doNotAwaitResult = SetOnlineAsync(value); + } + } + // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) LdClient() { } LdClient(Configuration configuration, User user) { - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } if (user == null) { throw new ArgumentNullException("user"); } - Config = configuration; + Config = configuration ?? throw new ArgumentNullException("configuration"); connectionLock = new SemaphoreSlim(1, 1); @@ -217,17 +224,7 @@ static LdClient CreateInstance(Configuration configuration, User user) var c = new LdClient(configuration, user); Instance = c; Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); - return c; - } - - bool StartUpdateProcessor(TimeSpan maxWaitTime) - { - return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); - } - - Task StartUpdateProcessorAsync() - { - return updateProcessor.Start(); + return c; } void SetupConnectionManager() @@ -241,17 +238,6 @@ void SetupConnectionManager() online = connectionManager.IsConnected; } - bool online; - /// - public bool Online - { - get => online; - set - { - var doNotAwaitResult = SetOnlineAsync(value); - } - } - public async Task SetOnlineAsync(bool value) { await connectionLock.WaitAsync(); @@ -385,6 +371,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); } } + var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); eventProcessor.SendEvent(featureEvent); @@ -424,7 +411,7 @@ public void Track(string eventName) /// public bool Initialized() { - return Online && updateProcessor.Initialized(); + return Online; } /// @@ -469,6 +456,19 @@ public async Task IdentifyAsync(User user) eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(newUser)); } + bool StartUpdateProcessor(TimeSpan maxWaitTime) + { + return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + } + + Task StartUpdateProcessorAsync() + { + if (Online) + return updateProcessor.Start(); + else + return Task.FromResult(true); + } + async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) { ClearAndSetUpdateProcessor(pollingInterval); @@ -490,24 +490,24 @@ void ClearUpdateProcessor() } } - User DecorateUser(User user) - { + User DecorateUser(User user) + { var newUser = new User(user); - if (UserMetadata.DeviceName != null) - { - newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); + if (UserMetadata.DeviceName != null) + { + newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); } - if (UserMetadata.OSName != null) - { - newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); + if (UserMetadata.OSName != null) + { + newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); } // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) + if (String.IsNullOrEmpty(user.Key)) { newUser.Key = deviceInfo.UniqueDeviceId(); - newUser.Anonymous = true; + newUser.Anonymous = true; } - return newUser; + return newUser; } void IDisposable.Dispose() @@ -547,8 +547,8 @@ public void RegisterFeatureFlagListener(string flagKey, IFeatureFlagListener lis public void UnregisterFeatureFlagListener(string flagKey, IFeatureFlagListener listener) { flagListenerManager.UnregisterListener(listener, flagKey); - } - + } + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); @@ -556,19 +556,19 @@ internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventA internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) { - if (args.IsInBackground) - { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - if (Config.EnableBackgroundUpdating) - { - await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); - } + if (args.IsInBackground) + { + ClearUpdateProcessor(); + Config.IsStreamingEnabled = false; + if (Config.EnableBackgroundUpdating) + { + await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); + } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } - else - { - ResetProcessorForForeground(); + else + { + ResetProcessorForForeground(); await RestartUpdateProcessorAsync(Config.PollingInterval); } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs index a074cc95..1413f2d0 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs @@ -86,9 +86,6 @@ static NetworkAccess PlatformNetworkAccess var info = manager.GetNetworkInfo(network); - if (info == null || !info.IsAvailable) - continue; - // Check to see if it has the internet capability if (!capabilities.HasCapability(NetCapability.Internet)) { @@ -119,7 +116,6 @@ void ProcessNetworkInfo(NetworkInfo info) { if (info == null || !info.IsAvailable) return; - if (info.IsConnected) currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Internet); else if (info.IsConnectedOrConnecting) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs index 942350d2..ea018a7d 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs @@ -49,7 +49,7 @@ internal static partial class Connectivity static event EventHandler ConnectivityChangedInternal; // a cache so that events aren't fired unnecessarily - // this is mainly an issue on Android, but we can stiil do this everywhere + // this is mainly an issue on Android, but we can still do this everywhere static NetworkAccess currentAccess; static List currentProfiles; From 970708983a74b3601d6df26b8a5fb69f90b46d60 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 24 Jul 2019 16:31:47 -0700 Subject: [PATCH 177/499] Fixed NullReferenceException in ResetProcessorForForeground --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 82 ++++++++++++------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 6e3580e4..9bc15db1 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -213,7 +213,7 @@ public static Task InitAsync(Configuration config, User user) static LdClient CreateInstance(Configuration configuration, User user) { - lock (createInstanceLock) + lock (createInstanceLock) { if (Instance != null) { @@ -223,8 +223,8 @@ static LdClient CreateInstance(Configuration configuration, User user) var c = new LdClient(configuration, user); Interlocked.CompareExchange(ref instance, c, null); Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); - return c; - } + return c; + } } bool StartUpdateProcessor(TimeSpan maxWaitTime) @@ -497,24 +497,24 @@ void ClearUpdateProcessor() } } - User DecorateUser(User user) - { + User DecorateUser(User user) + { var newUser = new User(user); - if (UserMetadata.DeviceName != null) - { - newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); + if (UserMetadata.DeviceName != null) + { + newUser = newUser.AndCustomAttribute("device", UserMetadata.DeviceName); } - if (UserMetadata.OSName != null) - { - newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); + if (UserMetadata.OSName != null) + { + newUser = newUser.AndCustomAttribute("os", UserMetadata.OSName); } // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) + if (String.IsNullOrEmpty(user.Key)) { newUser.Key = deviceInfo.UniqueDeviceId(); - newUser.Anonymous = true; + newUser.Anonymous = true; } - return newUser; + return newUser; } public void Dispose() @@ -538,9 +538,9 @@ void Dispose(bool disposing) } } - internal void DetachInstance() // exposed for testing - { - Interlocked.CompareExchange(ref instance, null, this); + internal void DetachInstance() // exposed for testing + { + Interlocked.CompareExchange(ref instance, null, this); } /// @@ -553,18 +553,18 @@ public Version Version } /// - public event EventHandler FlagChanged - { - add - { - flagChangedEventManager.FlagChanged += value; - } - remove - { - flagChangedEventManager.FlagChanged -= value; - } - } - + public event EventHandler FlagChanged + { + add + { + flagChangedEventManager.FlagChanged += value; + } + remove + { + flagChangedEventManager.FlagChanged -= value; + } + } + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); @@ -572,19 +572,19 @@ internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventA internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) { - if (args.IsInBackground) - { - ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; - if (Config.EnableBackgroundUpdating) - { - await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); - } + if (args.IsInBackground) + { + ClearUpdateProcessor(); + Config.IsStreamingEnabled = false; + if (Config.EnableBackgroundUpdating) + { + await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); + } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } - else - { - ResetProcessorForForeground(); + else + { + ResetProcessorForForeground(); await RestartUpdateProcessorAsync(Config.PollingInterval); } } @@ -592,7 +592,7 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh void ResetProcessorForForeground() { string didBackground = persister.GetValue(Constants.BACKGROUNDED_WHILE_STREAMING); - if (didBackground.Equals("true")) + if (didBackground != null && didBackground.Equals("true")) { persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "false"); ClearUpdateProcessor(); From 8e022418271dfc689d24e9567197e0dbea84a17d Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Thu, 25 Jul 2019 10:09:17 -0700 Subject: [PATCH 178/499] Added nameof(configuration), added Online check to StartUpdateProcessor --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index f0aebeed..9df5ef56 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -77,7 +77,7 @@ public bool Online throw new ArgumentNullException("user"); } - Config = configuration ?? throw new ArgumentNullException("configuration"); + Config = nameof(configuration) ?? throw new ArgumentNullException("configuration"); connectionLock = new SemaphoreSlim(1, 1); @@ -458,7 +458,10 @@ public async Task IdentifyAsync(User user) bool StartUpdateProcessor(TimeSpan maxWaitTime) { - return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + if (Online) + return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + else + return true; } Task StartUpdateProcessorAsync() From 4d36317101bd1c6cb9879e41d06c035a46e6a35e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 25 Jul 2019 10:21:11 -0700 Subject: [PATCH 179/499] use prebuilt Xamarin Android image (#55) --- .circleci/config.yml | 38 ++------------------------ scripts/check_xamarin_android_cache.sh | 11 -------- 2 files changed, 2 insertions(+), 47 deletions(-) delete mode 100755 scripts/check_xamarin_android_cache.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 42fffc9a..e26d43b3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,52 +22,18 @@ jobs: test-android: docker: - - image: circleci/android:api-27 + - image: ldcircleci/ld-xamarin-android-linux steps: - checkout - - run: - name: Install Android SDK tools - command: | - sdkmanager "system-images;android-24;default;armeabi-v7a" || true - sdkmanager --licenses - - - run: - name: Install Mono tools - command: | - sudo apt -y install apt-transport-https dirmngr gnupg ca-certificates - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF - echo "deb https://download.mono-project.com/repo/debian stable-stretch/snapshots/5.20.1.19 main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list && sudo apt update - sudo apt -y install mono-devel nuget libzip4 libpulse0 - # libpulse0 is required to run the emulator; the result are required to build the app - - - restore_cache: - key: xamarin-android-cache-v9-2-99-172 - - run: - name: Download Xamarin Android tools if not already cached - command: ./scripts/check_xamarin_android_cache.sh - - save_cache: - key: xamarin-android-cache-v9-2-99-172 - paths: - - ~/project/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release - - - run: - name: Move tools to expected locations - command: | - sudo mkdir "/usr/lib/xamarin.android" && sudo mkdir "/usr/lib/mono/xbuild/Xamarin/" - cd ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release && sudo cp -a "bin/Debug/lib/xamarin.android/." "/usr/lib/xamarin.android/" - rm -rf "/usr/lib/mono/xbuild/Xamarin/Android" && rm -rf "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - sudo ln -s "/usr/lib/xamarin.android/xbuild/Xamarin/Android/" "/usr/lib/mono/xbuild/Xamarin/Android" - sudo ln -s "/usr/lib/xamarin.android/xbuild-frameworks/MonoAndroid/" "/usr/lib/mono/xbuild-frameworks/MonoAndroid" - - run: name: Build SDK command: msbuild /restore /p:TargetFramework=MonoAndroid81 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj - run: name: Build test project - command: xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release/bin/Debug/bin/xabuild /restore /t:SignAndroidPackage tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + command: ~/xamarin-android/bin/Debug/bin/xabuild /restore /t:SignAndroidPackage tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild diff --git a/scripts/check_xamarin_android_cache.sh b/scripts/check_xamarin_android_cache.sh deleted file mode 100755 index 41eae00a..00000000 --- a/scripts/check_xamarin_android_cache.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -# used only for CI build - -if [ -e "xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release" ]; then - echo "Xamarin Android cache exists" -else - wget https://jenkins.mono-project.com/view/Xamarin.Android/job/xamarin-android-linux/lastSuccessfulBuild/Azure/processDownloadRequest/xamarin-android/xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 - tar xjf ./xamarin.android-oss_v9.2.99.172_Linux-x86_64_master_d33bbd8e-Release.tar.bz2 - echo "Downloaded Xamarin Android from Mono Jenkins" -fi From 306eaaab4667dcf8cd0ab8a146a3e64795e49c32 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Thu, 25 Jul 2019 10:25:09 -0700 Subject: [PATCH 180/499] Applied nameof to incorrect configuration --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 1fce5b6a..ef84e39b 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -81,7 +81,7 @@ public bool Online throw new ArgumentNullException(nameof(user)); } - Config = nameof(configuration) ?? throw new ArgumentNullException("configuration"); + Config = configuration ?? throw new ArgumentNullException(nameof(configuration)); connectionLock = new SemaphoreSlim(1, 1); From 219ff8137d3902ef42612dca7ef91b60d11eb92c Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Thu, 25 Jul 2019 12:20:09 -0700 Subject: [PATCH 181/499] Added back null check in Android Connectivity --- .../PlatformSpecific/Connectivity.android.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs index 1413f2d0..c8c6945c 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs @@ -86,6 +86,9 @@ static NetworkAccess PlatformNetworkAccess var info = manager.GetNetworkInfo(network); + if (info == null || !info.IsAvailable) + continue; + // Check to see if it has the internet capability if (!capabilities.HasCapability(NetCapability.Internet)) { From 55207d646656203a021be91b239fca7e14621394 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Thu, 25 Jul 2019 14:49:24 -0700 Subject: [PATCH 182/499] Reverted Initialized behavior, was a red herring during debugging --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index ef84e39b..a0c711de 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -418,7 +418,7 @@ public void Track(string eventName) /// public bool Initialized() { - return Online; + return Online && updateProcessor.Initialized(); } /// From ce8cf9d29aa6abe0e252e6718141915edf6adbf1 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Thu, 25 Jul 2019 14:55:39 -0700 Subject: [PATCH 183/499] Added volatile to online per review comment --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index a0c711de..b3edcbb6 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -59,7 +59,7 @@ public sealed class LdClient : ILdMobileClient readonly SemaphoreSlim connectionLock; - bool online; + volatile bool online; /// public bool Online { From ce865ad92a180b89bad7c5ec71d0a462620c0f40 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Sat, 27 Jul 2019 15:40:49 -0700 Subject: [PATCH 184/499] change tag of Xamarin Android Docker image --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e26d43b3..d4cd36d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: test-android: docker: - - image: ldcircleci/ld-xamarin-android-linux + - image: ldcircleci/ld-xamarin-android-linux:api27 steps: - checkout From 8264b3ce52172a392aa53803806da698960facde Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 29 Jul 2019 11:41:29 -0700 Subject: [PATCH 185/499] Add api28 docker tag to circleci android test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e26d43b3..180d308b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: test-android: docker: - - image: ldcircleci/ld-xamarin-android-linux + - image: ldcircleci/ld-xamarin-android-linux:api28 steps: - checkout From 52c15dab987a5808ce818fb73c210426517796f8 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 29 Jul 2019 11:45:46 -0700 Subject: [PATCH 186/499] Add api27 docker tag to circleci android test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 180d308b..d4cd36d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: test-android: docker: - - image: ldcircleci/ld-xamarin-android-linux:api28 + - image: ldcircleci/ld-xamarin-android-linux:api27 steps: - checkout From 50e4c810384e66db473738986a09c8fd5b69fe11 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 29 Jul 2019 15:29:48 -0700 Subject: [PATCH 187/499] Added tests to trigger StartUpdateProcessor and StartUpdateProcessorAsync while offline --- .../LDClientEndToEndTests.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 02f3bd8e..64d94b1e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -284,6 +284,62 @@ await WithServerAsync(async server => }); } + [Fact(Skip = SkipIfCannotCreateHttpServer)] + public void BackgroundOfflineClientUsesCachedFlagsSync() + { + WithServer(server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + using (var client = TestUtil.CreateClient(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + }); + } + + [Fact(Skip = SkipIfCannotCreateHttpServer)] + public async Task BackgroundOfflineClientUsesCachedFlagsAsync() + { + await WithServerAsync(async server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + }); + } + private Configuration BaseConfig(FluentMockServer server) { return Configuration.Default(_mobileKey) From 881cbdffa3a73144ad241c2c780996793076826f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 29 Jul 2019 16:12:58 -0700 Subject: [PATCH 188/499] use immutable user API --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 0ea57782..ce250990 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -23,7 +23,7 @@ - + From 080e12eb5e4690066cc10766fdd7d455de7db770 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 29 Jul 2019 16:15:29 -0700 Subject: [PATCH 189/499] update dependency --- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index cf929fbb..22e0c8a5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From d3f10fdeaab4816fdc18e068e0d3346d80a1df19 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 29 Jul 2019 16:32:34 -0700 Subject: [PATCH 190/499] fix encoded user constants in tests --- .../LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs | 2 +- .../MobileStreamingProcessorTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 9788ef5d..2b53e47b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -10,7 +10,7 @@ public class FeatureFlagRequestorTests : BaseTest private const string _mobileKey = "FAKE_KEY"; private static readonly User _user = User.WithKey("foo"); - private const string _encodedUser = "eyJrZXkiOiJmb28iLCJjdXN0b20iOnt9fQ=="; + private const string _encodedUser = "eyJrZXkiOiJmb28iLCJhbm9ueW1vdXMiOmZhbHNlLCJjdXN0b20iOnt9fQ=="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index a7f8b1d0..562753e4 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -18,7 +18,7 @@ public class MobileStreamingProcessorTests : BaseTest "}"; private readonly User user = User.WithKey("me"); - private const string encodedUser = "eyJrZXkiOiJtZSIsImN1c3RvbSI6e319"; + private const string encodedUser = "eyJrZXkiOiJtZSIsImFub255bW91cyI6ZmFsc2UsImN1c3RvbSI6e319"; private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; From 85f1c01290a1dec43f6fb3996e535ccee0f27370 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 29 Jul 2019 16:52:18 -0700 Subject: [PATCH 191/499] Changed misleading test names --- tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 64d94b1e..1ad970b9 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -285,7 +285,7 @@ await WithServerAsync(async server => } [Fact(Skip = SkipIfCannotCreateHttpServer)] - public void BackgroundOfflineClientUsesCachedFlagsSync() + public void OfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() { WithServer(server => { @@ -313,7 +313,7 @@ public void BackgroundOfflineClientUsesCachedFlagsSync() } [Fact(Skip = SkipIfCannotCreateHttpServer)] - public async Task BackgroundOfflineClientUsesCachedFlagsAsync() + public async Task OfflineClientUsesCachedFlagsAsyncAfterStartUpdateProcessorAsync() { await WithServerAsync(async server => { From df1f0e9609b594de3bdaae21f2dd09bb7ab5e97a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 29 Jul 2019 22:00:41 -0700 Subject: [PATCH 192/499] add tests for user platform metadata --- .../LaunchDarkly.XamarinSdk.csproj | 2 +- .../AndroidSpecificTests.cs | 23 +++++++++++++++++++ ...unchDarkly.XamarinSdk.Android.Tests.csproj | 1 + .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../IOsSpecificTests.cs | 23 +++++++++++++++++++ .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 1 + 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ce250990..b13b3e72 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs new file mode 100644 index 00000000..35fb99da --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -0,0 +1,23 @@ +using LaunchDarkly.Client; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class AndroidSpecificTests + { + [Fact] + public void UserHasOSAndDeviceAttributesForPlatform() + { + var baseUser = User.WithKey("key"); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}"); + using (var client = TestUtil.CreateClient(config, baseUser)) + { + var user = client.User; + Assert.Equal(baseUser.Key, user.Key); + Assert.Contains("os", user.Custom.Keys); + Assert.StartsWith("Android ", user.Custom["os"].AsString); + Assert.Contains("device", user.Custom.Keys); + } + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 58182c3f..92602515 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -108,6 +108,7 @@ SharedTestCode\WireMockExtensions.cs + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 22e0c8a5..90d7d771 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs new file mode 100644 index 00000000..32d619ad --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -0,0 +1,23 @@ +using LaunchDarkly.Client; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class IOsSpecificTests + { + [Fact] + public void UserHasOSAndDeviceAttributesForPlatform() + { + var baseUser = User.WithKey("key"); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}"); + using (var client = TestUtil.CreateClient(config, baseUser)) + { + var user = client.User; + Assert.Equal(baseUser.Key, user.Key); + Assert.Contains("os", user.Custom.Keys); + Assert.StartsWith("iOs ", user.Custom["os"].AsString); + Assert.Contains("device", user.Custom.Keys); + } + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index b04ca764..db128f57 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -193,6 +193,7 @@ SharedTestCode\WireMockExtensions.cs + From 9ead7218dc95ce49adaeff90395be38767b34562 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 29 Jul 2019 23:03:14 -0700 Subject: [PATCH 193/499] typo --- .../IOsSpecificTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index 32d619ad..c16ffe57 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -1,13 +1,13 @@ -using LaunchDarkly.Client; -using Xunit; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class IOsSpecificTests - { - [Fact] - public void UserHasOSAndDeviceAttributesForPlatform() - { +using LaunchDarkly.Client; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class IOsSpecificTests + { + [Fact] + public void UserHasOSAndDeviceAttributesForPlatform() + { var baseUser = User.WithKey("key"); var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}"); using (var client = TestUtil.CreateClient(config, baseUser)) @@ -15,9 +15,9 @@ public void UserHasOSAndDeviceAttributesForPlatform() var user = client.User; Assert.Equal(baseUser.Key, user.Key); Assert.Contains("os", user.Custom.Keys); - Assert.StartsWith("iOs ", user.Custom["os"].AsString); + Assert.StartsWith("iOS ", user.Custom["os"].AsString); Assert.Contains("device", user.Custom.Keys); } - } - } -} + } + } +} From f7ddb29509518c0f37e3a288f3f681eb1ed58959 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 00:32:28 -0700 Subject: [PATCH 194/499] make Configuration immutable, add builder --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 731 ++++-------------- .../ConfigurationBuilder.cs | 448 +++++++++++ src/LaunchDarkly.XamarinSdk/Factory.cs | 26 +- .../IMobileConfiguration.cs | 21 +- .../LaunchDarkly.XamarinSdk.csproj | 9 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 116 +-- .../ConfigurationTest.cs | 31 +- .../FeatureFlagRequestorTests.cs | 19 +- .../LDClientEndToEndTests.cs | 34 +- .../LdClientEvaluationTests.cs | 2 +- .../LdClientEventTests.cs | 6 +- .../LdClientTests.cs | 51 +- .../MobileStreamingProcessorTests.cs | 48 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 17 +- 14 files changed, 806 insertions(+), 753 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 763ddfb0..e684369f 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -1,674 +1,265 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Net.Http; -using Common.Logging; using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { /// - /// This class exposes advanced configuration options for . + /// This class exposes advanced configuration options for . /// + /// + /// Instances of Configuration are immutable once created. They can be created with the factory method + /// , or using a builder pattern with + /// or . + /// public class Configuration : IMobileConfiguration { - /// - /// The base URI of the LaunchDarkly server. - /// - public Uri BaseUri { get; internal set; } - /// - /// The base URL of the LaunchDarkly streaming server. - /// - public Uri StreamUri { get; internal set; } - /// - /// The base URL of the LaunchDarkly analytics event server. - /// - public Uri EventsUri { get; internal set; } - /// - /// The Mobile key for your LaunchDarkly environment. - /// - public string MobileKey { get; internal set; } - /// - /// The SDK key for your LaunchDarkly environment. This is the Mobile key. - /// - /// Returns the Mobile Key. - public string SdkKey { get { return MobileKey; } } - /// - /// Whether or not the streaming API should be used to receive flag updates. This is true by default. - /// Streaming should only be disabled on the advice of LaunchDarkly support. - /// - public bool IsStreamingEnabled { get; internal set; } - /// - /// The capacity of the events buffer. The client buffers up to this many events in - /// memory before flushing. If the capacity is exceeded before the buffer is flushed, - /// events will be discarded. Increasing the capacity means that events are less likely - /// to be discarded, at the cost of consuming more memory. - /// - public int EventQueueCapacity { get; internal set; } - /// - /// The time between flushes of the event buffer. Decreasing the flush interval means - /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. - /// - public TimeSpan EventQueueFrequency { get; internal set; } - /// - /// Enables event sampling if non-zero. When set to the default of zero, all analytics events are - /// sent back to LaunchDarkly. When greater than zero, there is a 1 in EventSamplingInterval - /// chance that events will be sent (example: if the interval is 20, on average 5% of events will be sent). - /// - public int EventSamplingInterval { get; internal set; } - /// - /// Set the polling interval (when streaming is disabled). The default value is 30 seconds. - /// - public TimeSpan PollingInterval { get; internal set; } - /// - /// The timeout when reading data from the EventSource API. The default value is 5 minutes. - /// - public TimeSpan ReadTimeout { get; internal set; } - /// - /// The reconnect base time for the streaming connection.The streaming connection - /// uses an exponential backoff algorithm (with jitter) for reconnects, but will start the - /// backoff with a value near the value specified here. The default value is 1 second. - /// - public TimeSpan ReconnectTime { get; internal set; } - /// - /// The connection timeout. The default value is 10 seconds. - /// - public TimeSpan HttpClientTimeout { get; internal set; } - /// - /// The object to be used for sending HTTP requests. This is exposed for testing purposes. - /// - public HttpClientHandler HttpClientHandler { get; internal set; } - /// - /// Whether or not this client is offline. If true, no calls to Launchdarkly will be made. - /// - public bool Offline { get; internal set; } + private readonly bool _allAttributesPrivate; + private readonly TimeSpan _backgroundPollingInterval; + private readonly Uri _baseUri; + private readonly TimeSpan _connectionTimeout; + private readonly bool _enableBackgroundUpdating; + private readonly bool _evaluationReasons; + private readonly TimeSpan _eventFlushInterval; + private readonly int _eventCapacity; + private readonly int _eventSamplingInterval; + private readonly Uri _eventsUri; + private readonly HttpClientHandler _httpClientHandler; + private readonly TimeSpan _httpClientTimeout; + private readonly bool _inlineUsersInEvents; + private readonly bool _isStreamingEnabled; + private readonly string _mobileKey; + private readonly bool _offline; + private readonly bool _persistFlagValues; + private readonly TimeSpan _pollingInterval; + private readonly ImmutableHashSet _privateAttributeNames; + private readonly TimeSpan _readTimeout; + private readonly TimeSpan _reconnectTime; + private readonly Uri _streamUri; + private readonly bool _useReport; + private readonly int _userKeysCapacity; + private readonly TimeSpan _userKeysFlushInterval; + + // Settable only for testing + internal readonly IConnectionManager _connectionManager; + internal readonly IDeviceInfo _deviceInfo; + internal readonly IEventProcessor _eventProcessor; + internal readonly IFlagCacheManager _flagCacheManager; + internal readonly IFlagChangedEventManager _flagChangedEventManager; + internal readonly IPersistentStorage _persistentStorage; + internal readonly Func _updateProcessorFactory; + /// /// Whether or not user attributes (other than the key) should be private (not sent to /// the LaunchDarkly server). If this is true, all of the user attributes will be private, /// not just the attributes specified with the AndPrivate... methods on the /// object. By default, this is false. /// - public bool AllAttributesPrivate { get; internal set; } + public bool AllAttributesPrivate => _allAttributesPrivate; + + /// + public TimeSpan BackgroundPollingInterval => _backgroundPollingInterval; + /// - /// Marks a set of attribute names as private. Any users sent to LaunchDarkly with this - /// configuration active will have attributes with these names removed, even if you did - /// not use the AndPrivate... methods on the object. - /// - public ISet PrivateAttributeNames { get; internal set; } - /// - /// The number of user keys that the event processor can remember at any one time, so that - /// duplicate user details will not be sent in analytics events. - /// - public int UserKeysCapacity { get; internal set; } - /// - /// The interval at which the event processor will reset its set of known user keys. The - /// default value is five minutes. - /// - public TimeSpan UserKeysFlushInterval { get; internal set; } - /// - /// True if full user details should be included in every analytics event. The default is false (events will - /// only include the user key, except for one "index" event that provides the full details for the user). + /// The base URI of the LaunchDarkly server. /// - public bool InlineUsersInEvents { get; internal set; } - /// - /// True if LaunchDarkly should provide additional information about how flag values were - /// calculated. The additional information will then be available through the client's "detail" - /// methods such as . Since this - /// increases the size of network requests, such information is not sent unless you set this option - /// to true. - /// - public bool EvaluationReasons { get; internal set; } - /// - public TimeSpan BackgroundPollingInterval { get; internal set; } + public Uri BaseUri => _baseUri; + /// public TimeSpan ConnectionTimeout { get; internal set; } + /// - public bool EnableBackgroundUpdating { get; internal set; } - /// - public bool UseReport { get; internal set; } - /// - public bool PersistFlagValues { get; internal set; } + public bool EnableBackgroundUpdating => _enableBackgroundUpdating; - internal IFlagCacheManager FlagCacheManager { get; set; } - internal IConnectionManager ConnectionManager { get; set; } - internal IEventProcessor EventProcessor { get; set; } - internal Func UpdateProcessorFactory { get; set; } - internal IPersistentStorage PersistentStorage { get; set; } - internal IDeviceInfo DeviceInfo { get; set; } - internal IFlagChangedEventManager FlagChangedEventManager { get; set; } + /// + public bool EvaluationReasons => _evaluationReasons; /// - /// Default value for . - /// - public static TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5); - /// - /// Minimum value for . - /// - public static TimeSpan MinimumPollingInterval = TimeSpan.FromMinutes(5); - /// - /// Default value for . - /// - internal static readonly Uri DefaultUri = new Uri("https://app.launchdarkly.com"); - /// - /// Default value for . - /// - private static readonly Uri DefaultStreamUri = new Uri("https://clientstream.launchdarkly.com"); - /// - /// Default value for . - /// - private static readonly Uri DefaultEventsUri = new Uri("https://mobile.launchdarkly.com"); - /// - /// Default value for . - /// - private static readonly int DefaultEventQueueCapacity = 100; - /// - /// Default value for . - /// - private static readonly TimeSpan DefaultEventQueueFrequency = TimeSpan.FromSeconds(5); - /// - /// Default value for . - /// - private static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); - /// - /// Default value for . - /// - private static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); - /// - /// Default value for . - /// - private static readonly TimeSpan DefaultHttpClientTimeout = TimeSpan.FromSeconds(10); - /// - /// Default value for . - /// - private static readonly int DefaultUserKeysCapacity = 1000; - /// - /// Default value for . - /// - private static readonly TimeSpan DefaultUserKeysFlushInterval = TimeSpan.FromMinutes(5); - /// - /// The default value for . - /// - private static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(60); - /// - /// The minimum value for . - /// - public static readonly TimeSpan MinimumBackgroundPollingInterval = TimeSpan.FromMinutes(15); - /// - /// The default value for . + /// The time between flushes of the event buffer. Decreasing the flush interval means + /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. /// - private static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); + public TimeSpan EventFlushInterval => _eventFlushInterval; /// - /// Creates a configuration with all parameters set to the default. Use extension methods - /// to set additional parameters. + /// The capacity of the events buffer. The client buffers up to this many events in + /// memory before flushing. If the capacity is exceeded before the buffer is flushed, + /// events will be discarded. Increasing the capacity means that events are less likely + /// to be discarded, at the cost of consuming more memory. /// - /// the SDK key for your LaunchDarkly environment - /// a Configuration instance - public static Configuration Default(string mobileKey) - { - if (String.IsNullOrEmpty(mobileKey)) - { - throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); - } - var defaultConfiguration = new Configuration - { - BaseUri = DefaultUri, - StreamUri = DefaultStreamUri, - EventsUri = DefaultEventsUri, - EventQueueCapacity = DefaultEventQueueCapacity, - EventQueueFrequency = DefaultEventQueueFrequency, - PollingInterval = DefaultPollingInterval, - BackgroundPollingInterval = DefaultBackgroundPollingInterval, - ReadTimeout = DefaultReadTimeout, - ReconnectTime = DefaultReconnectTime, - HttpClientTimeout = DefaultHttpClientTimeout, - HttpClientHandler = new HttpClientHandler(), - Offline = false, - MobileKey = mobileKey, - IsStreamingEnabled = true, - AllAttributesPrivate = false, - PrivateAttributeNames = null, - UserKeysCapacity = DefaultUserKeysCapacity, - UserKeysFlushInterval = DefaultUserKeysFlushInterval, - InlineUsersInEvents = false, - EnableBackgroundUpdating = true, - UseReport = true, - PersistFlagValues = true - }; - - return defaultConfiguration; - } - } - - /// - /// Extension methods that can be called on a to add to its properties. - /// - public static class ConfigurationExtensions - { - private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationExtensions)); + public int EventCapacity => _eventCapacity; /// - /// Sets the base URI of the LaunchDarkly server for this configuration. + /// Deprecated name for . /// - /// the configuration - /// the base URI as a string - /// the same Configuration instance - public static Configuration WithBaseUri(this Configuration configuration, string uri) - { - if (uri != null) - configuration.BaseUri = new Uri(uri); - - return configuration; - } + [Obsolete] + public int EventQueueCapacity => EventCapacity; /// - /// Sets the base URI of the LaunchDarkly server for this configuration. + /// Deprecated name for . /// - /// the configuration - /// the base URI - /// the same Configuration instance - public static Configuration WithBaseUri(this Configuration configuration, Uri uri) - { - if (uri != null) - configuration.BaseUri = uri; + [Obsolete] + public TimeSpan EventQueueFrequency => EventFlushInterval; - return configuration; - } - - /// - /// Sets the base URL of the LaunchDarkly streaming server for this configuration. + /// + /// Enables event sampling if non-zero. When set to the default of zero, all analytics events are + /// sent back to LaunchDarkly. When greater than zero, there is a 1 in EventSamplingInterval + /// chance that events will be sent (example: if the interval is 20, on average 5% of events will be sent). /// - /// the configuration - /// the stream URI as a string - /// the same Configuration instance - public static Configuration WithStreamUri(this Configuration configuration, string uri) - { - if (uri != null) - configuration.StreamUri = new Uri(uri); - - return configuration; - } + public int EventSamplingInterval => _eventSamplingInterval; /// - /// Sets the base URL of the LaunchDarkly streaming server for this configuration. + /// The base URL of the LaunchDarkly analytics event server. /// - /// the configuration - /// the stream URI - /// the same Configuration instance - public static Configuration WithStreamUri(this Configuration configuration, Uri uri) - { - if (uri != null) - configuration.StreamUri = uri; - - return configuration; - } - + public Uri EventsUri => _eventsUri; + /// - /// Sets the base URL of the LaunchDarkly analytics event server for this configuration. + /// The object to be used for sending HTTP requests. This is exposed for testing purposes. /// - /// the configuration - /// the events URI as a string - /// the same Configuration instance - public static Configuration WithEventsUri(this Configuration configuration, string uri) - { - if (uri != null) - configuration.EventsUri = new Uri(uri); - - return configuration; - } + public HttpClientHandler HttpClientHandler => _httpClientHandler; /// - /// Sets the base URL of the LaunchDarkly analytics event server for this configuration. + /// The connection timeout. The default value is 10 seconds. /// - /// the configuration - /// the events URI - /// the same Configuration instance - public static Configuration WithEventsUri(this Configuration configuration, Uri uri) - { - if (uri != null) - configuration.EventsUri = uri; + public TimeSpan HttpClientTimeout => _httpClientTimeout; - return configuration; - } + /// + /// True if full user details should be included in every analytics event. The default is false (events will + /// only include the user key, except for one "index" event that provides the full details for the user). + /// + public bool InlineUsersInEvents => _inlineUsersInEvents; /// - /// Sets the capacity of the events buffer. The client buffers up to this many events in - /// memory before flushing. If the capacity is exceeded before the buffer is flushed, - /// events will be discarded. Increasing the capacity means that events are less likely - /// to be discarded, at the cost of consuming more memory. + /// Whether or not the streaming API should be used to receive flag updates. This is true by default. + /// Streaming should only be disabled on the advice of LaunchDarkly support. /// - /// the configuration - /// - /// the same Configuration instance - public static Configuration WithEventQueueCapacity(this Configuration configuration, int eventQueueCapacity) - { - configuration.EventQueueCapacity = eventQueueCapacity; - return configuration; - } + public bool IsStreamingEnabled => _isStreamingEnabled; /// - /// Sets the time between flushes of the event buffer. Decreasing the flush interval means - /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. - /// - /// the configuration - /// the flush interval - /// the same Configuration instance - public static Configuration WithEventQueueFrequency(this Configuration configuration, TimeSpan frequency) - { - configuration.EventQueueFrequency = frequency; - return configuration; - } - - /// - /// Enables event sampling if non-zero. When set to the default of zero, all analytics events are - /// sent back to LaunchDarkly. When greater than zero, there is a 1 in EventSamplingInterval - /// chance that events will be sent (example: if the interval is 20, on average 5% of events will be sent). + /// The Mobile key for your LaunchDarkly environment. /// - /// the configuration - /// the sampling interval - /// the same Configuration instance - public static Configuration WithEventSamplingInterval(this Configuration configuration, int interval) - { - if (interval < 0) - { - Log.Warn("EventSamplingInterval cannot be less than zero."); - interval = 0; - } - configuration.EventSamplingInterval = interval; - return configuration; - } + public string MobileKey => _mobileKey; /// - /// Sets the polling interval (when streaming is disabled). Values less than the default of - /// 30 seconds will be changed to the default. + /// Whether or not this client is offline. If true, no calls to Launchdarkly will be made. /// - /// the configuration - /// the rule update polling interval - /// the same Configuration instance - public static Configuration WithPollingInterval(this Configuration configuration, TimeSpan pollingInterval) - { - if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) - { - Log.WarnFormat("PollingInterval cannot be less than the default of {0}.", Configuration.MinimumPollingInterval); - pollingInterval = Configuration.MinimumPollingInterval; - } - configuration.PollingInterval = pollingInterval; - return configuration; - } + public bool Offline => _offline; + + /// + public bool PersistFlagValues => _persistFlagValues; /// - /// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made. + /// Set the polling interval (when streaming is disabled). The default value is 30 seconds. /// - /// the configuration - /// true if the client should remain offline - /// the same Configuration instance - public static Configuration WithOffline(this Configuration configuration, bool offline) - { - configuration.Offline = offline; - return configuration; - } + public TimeSpan PollingInterval => _pollingInterval; /// - /// Sets the connection timeout. The default value is 10 seconds. + /// Marks a set of attribute names as private. Any users sent to LaunchDarkly with this + /// configuration active will have attributes with these names removed, even if you did + /// not use the AndPrivate... methods on the object. /// - /// the configuration - /// the connection timeout - /// the same Configuration instance - public static Configuration WithHttpClientTimeout(this Configuration configuration, TimeSpan timeSpan) - { - configuration.HttpClientTimeout = timeSpan; - return configuration; - } + public ISet PrivateAttributeNames => _privateAttributeNames; /// - /// Sets the timeout when reading data from the EventSource API. The default value is 5 minutes. + /// The timeout when reading data from the EventSource API. The default value is 5 minutes. /// - /// the configuration - /// the read timeout - /// the same Configuration instance - public static Configuration WithReadTimeout(this Configuration configuration, TimeSpan timeSpan) - { - configuration.ReadTimeout = timeSpan; - return configuration; - } + public TimeSpan ReadTimeout => _readTimeout; /// - /// Sets the reconnect base time for the streaming connection. The streaming connection + /// The reconnect base time for the streaming connection.The streaming connection /// uses an exponential backoff algorithm (with jitter) for reconnects, but will start the /// backoff with a value near the value specified here. The default value is 1 second. /// - /// the configuration - /// the reconnect time base value - /// the same Configuration instance - public static Configuration WithReconnectTime(this Configuration configuration, TimeSpan timeSpan) - { - configuration.ReconnectTime = timeSpan; - return configuration; - } - - /// - /// Sets the object to be used for sending HTTP requests. This is exposed for testing purposes. - /// - /// the configuration - /// the HttpClientHandler to use - /// the same Configuration instance - public static Configuration WithHttpClientHandler(this Configuration configuration, HttpClientHandler httpClientHandler) - { - configuration.HttpClientHandler = httpClientHandler; - return configuration; - } + public TimeSpan ReconnectTime => _reconnectTime; /// - /// Sets whether or not the streaming API should be used to receive flag updates. This - /// is true by default. Streaming should only be disabled on the advice of LaunchDarkly - /// support. + /// Alternate name for . /// - /// the configuration - /// true if the streaming API should be used - /// the same Configuration instance - public static Configuration WithIsStreamingEnabled(this Configuration configuration, bool enableStream) - { - configuration.IsStreamingEnabled = enableStream; - return configuration; - } + public string SdkKey => MobileKey; /// - /// Sets whether or not user attributes (other than the key) should be private (not sent to - /// the LaunchDarkly server). If this is true, all of the user attributes will be private, - /// not just the attributes specified with the AndPrivate... methods on the - /// object. By default, this is false. + /// The base URL of the LaunchDarkly streaming server. /// - /// the configuration - /// true if all attributes should be private - /// the same Configuration instance - public static Configuration WithAllAttributesPrivate(this Configuration configuration, bool allAttributesPrivate) - { - configuration.AllAttributesPrivate = allAttributesPrivate; - return configuration; - } + public Uri StreamUri => _streamUri; - /// - /// Marks an attribute name as private. Any users sent to LaunchDarkly with this - /// configuration active will have attributes with this name removed, even if you did - /// not use the AndPrivate... methods on the object. You may - /// call this method repeatedly to mark multiple attributes as private. - /// - /// the configuration - /// the attribute name - /// the same Configuration instance - public static Configuration WithPrivateAttributeName(this Configuration configuration, string attributeName) - { - if (configuration.PrivateAttributeNames == null) - { - configuration.PrivateAttributeNames = new HashSet(); - } - configuration.PrivateAttributeNames.Add(attributeName); - return configuration; - } + /// + public bool UseReport => _useReport; - /// Configuration - /// Sets the number of user keys that the event processor can remember at any one time, so that + /// + /// The number of user keys that the event processor can remember at any one time, so that /// duplicate user details will not be sent in analytics events. /// - /// the configuration - /// the user key cache capacity - /// the same Configuration instance - public static Configuration WithUserKeysCapacity(this Configuration configuration, int capacity) - { - configuration.UserKeysCapacity = capacity; - return configuration; - } + public int UserKeysCapacity => _userKeysCapacity; /// - /// Sets the interval at which the event processor will reset its set of known user keys. The + /// The interval at which the event processor will reset its set of known user keys. The /// default value is five minutes. - /// - /// the configuration - /// the flush interval - /// the same Configuration instance - public static Configuration WithUserKeysFlushInterval(this Configuration configuration, TimeSpan flushInterval) - { - configuration.UserKeysFlushInterval = flushInterval; - return configuration; - } - - /// - /// Sets whether to include full user details in every analytics event. The default is false (events will - /// only include the user key, except for one "index" event that provides the full details for the user). - /// - /// the configuration - /// true or false - /// the same Configuration instance - public static Configuration WithInlineUsersInEvents(this Configuration configuration, bool inlineUsers) - { - configuration.InlineUsersInEvents = inlineUsers; - return configuration; - } - - /// - /// Sets the IConnectionManager instance, used internally for stubbing mock instances. /// - /// Configuration. - /// Connection manager. - /// the same Configuration instance - public static Configuration WithConnectionManager(this Configuration configuration, IConnectionManager connectionManager) - { - configuration.ConnectionManager = connectionManager; - return configuration; - } - - /// - /// Sets the IEventProcessor instance, used internally for stubbing mock instances. - /// - /// Configuration. - /// Event processor. - /// the same Configuration instance - public static Configuration WithEventProcessor(this Configuration configuration, IEventProcessor eventProcessor) - { - configuration.EventProcessor = eventProcessor; - return configuration; - } + public TimeSpan UserKeysFlushInterval => _userKeysFlushInterval; /// - /// Determines whether to use the Report method for networking requests + /// Default value for . /// - /// Configuration. - /// If set to true use report. - /// the same Configuration instance - public static Configuration WithUseReport(this Configuration configuration, bool useReport) - { - configuration.UseReport = useReport; - return configuration; - } - + public static TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5); + /// - /// Set to true if LaunchDarkly should provide additional information about how flag values were - /// calculated. The additional information will then be available through the client's "detail" - /// methods such as . Since this - /// increases the size of network requests, such information is not sent unless you set this option - /// to true. - /// - /// Configuration. - /// True if evaluation reasons are desired. - /// the same Configuration instance - public static Configuration WithEvaluationReasons(this Configuration configuration, bool evaluationReasons) - { - configuration.EvaluationReasons = evaluationReasons; - return configuration; - } - - /// - /// Sets whether to enable background polling. + /// Minimum value for . /// - /// Configuration. - /// If set to true enable background updating. - /// the same Configuration instance - public static Configuration WithEnableBackgroundUpdating(this Configuration configuration, bool enableBackgroundUpdating) - { - configuration.EnableBackgroundUpdating = enableBackgroundUpdating; - return configuration; - } + public static TimeSpan MinimumPollingInterval = TimeSpan.FromMinutes(5); - /// - /// Sets the interval for background polling. - /// - /// Configuration. - /// Background polling internal. - /// the same Configuration instance - public static Configuration WithBackgroundPollingInterval(this Configuration configuration, TimeSpan backgroundPollingInternal) - { - if (backgroundPollingInternal.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) - { - Log.WarnFormat("BackgroundPollingInterval cannot be less than the default of {0}.", Configuration.MinimumBackgroundPollingInterval); - backgroundPollingInternal = Configuration.MinimumBackgroundPollingInterval; - } - configuration.BackgroundPollingInterval = backgroundPollingInternal; - return configuration; - } + internal static readonly Uri DefaultUri = new Uri("https://app.launchdarkly.com"); + internal static readonly Uri DefaultStreamUri = new Uri("https://clientstream.launchdarkly.com"); + internal static readonly Uri DefaultEventsUri = new Uri("https://mobile.launchdarkly.com"); + internal static readonly int DefaultEventCapacity = 100; + internal static readonly TimeSpan DefaultEventFlushInterval = TimeSpan.FromSeconds(5); + internal static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); + internal static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); + internal static readonly TimeSpan DefaultHttpClientTimeout = TimeSpan.FromSeconds(10); + internal static readonly int DefaultUserKeysCapacity = 1000; + internal static readonly TimeSpan DefaultUserKeysFlushInterval = TimeSpan.FromMinutes(5); + internal static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(60); + internal static readonly TimeSpan MinimumBackgroundPollingInterval = TimeSpan.FromMinutes(15); + internal static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); /// - /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be - /// immediately available the next time the SDK is started for the same user. The default is . + /// Creates a configuration with all parameters set to the default. Use extension methods + /// to set additional parameters. /// - /// the configuration - /// true or false - /// the same Configuration instance - /// - public static Configuration WithPersistFlagValues(this Configuration configuration, bool persistFlagValues) - { - configuration.PersistFlagValues = persistFlagValues; - return configuration; - } - - // The following properties can only be set internally. They are used for providing stub implementations in unit tests. - - internal static Configuration WithDeviceInfo(this Configuration configuration, IDeviceInfo deviceInfo) + /// the SDK key for your LaunchDarkly environment + /// a Configuration instance + public static Configuration Default(string mobileKey) { - configuration.DeviceInfo = deviceInfo; - return configuration; + return Builder(mobileKey).Build(); } - internal static Configuration WithFlagChangedEventManager(this Configuration configuration, IFlagChangedEventManager flagChangedEventManager) - { - configuration.FlagChangedEventManager = flagChangedEventManager; - return configuration; - } + /// /// Creates a for constructing a configuration object using a fluent syntax. /// /// /// This is the only method for building a Configuration if you are setting properties /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of /// properties, after which you call to get the resulting /// Configuration instance. /// /// /// /// var config = Configuration.Builder("my-sdk-key") /// .EventQueueFrequency(TimeSpan.FromSeconds(90)) /// .StartWaitTime(TimeSpan.FromSeconds(5)) /// .Build(); /// /// /// the SDK key for your LaunchDarkly environment + /// a builder object public static IConfigurationBuilder Builder(string mobileKey) { + if (String.IsNullOrEmpty(mobileKey)) + { + throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); + } + return new ConfigurationBuilder(mobileKey); } - internal static Configuration WithFlagCacheManager(this Configuration configuration, IFlagCacheManager flagCacheManager) - { - configuration.FlagCacheManager = flagCacheManager; - return configuration; - } + internal static ConfigurationBuilder BuilderInternal(string mobileKey) { + return new ConfigurationBuilder(mobileKey); } - internal static Configuration WithPersistentStorage(this Configuration configuration, IPersistentStorage persistentStorage) + public static IConfigurationBuilder Builder(Configuration fromConfiguration) { - configuration.PersistentStorage = persistentStorage; - return configuration; + return new ConfigurationBuilder(fromConfiguration); } - internal static Configuration WithUpdateProcessorFactory(this Configuration configuration, Func factory) + internal Configuration(ConfigurationBuilder builder) { - configuration.UpdateProcessorFactory = factory; - return configuration; + _allAttributesPrivate = builder._allAttributesPrivate; _backgroundPollingInterval = builder._backgroundPollingInterval; _baseUri = builder._baseUri; _connectionTimeout = builder._connectionTimeout; _enableBackgroundUpdating = builder._enableBackgroundUpdating; _evaluationReasons = builder._evaluationReasons; _eventFlushInterval = builder._eventFlushInterval; _eventCapacity = builder._eventCapacity; _eventSamplingInterval = builder._eventSamplingInterval; _eventsUri = builder._eventsUri; _httpClientHandler = builder._httpClientHandler; _httpClientTimeout = builder._httpClientTimeout; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; _offline = builder._offline; _persistFlagValues = builder._persistFlagValues; _pollingInterval = builder._pollingInterval; _privateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); _readTimeout = builder._readTimeout; _reconnectTime = builder._reconnectTime; _streamUri = builder._streamUri; _useReport = builder._useReport; _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; + + _connectionManager = builder._connectionManager; + _deviceInfo = builder._deviceInfo; + _eventProcessor = builder._eventProcessor; + _flagCacheManager = builder._flagCacheManager; + _flagChangedEventManager = builder._flagChangedEventManager; + _persistentStorage = builder._persistentStorage; + _updateProcessorFactory = builder._updateProcessorFactory; } } } diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs new file mode 100644 index 00000000..f07b05aa --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Net.Http; +using Common.Logging; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin +{ + /// + /// A mutable object that uses the Builder pattern to specify properties for a object. + /// + /// + /// Obtain an instance of this class by calling . + /// + /// All of the builder methods for setting a configuration property return a reference to the same builder, so they can be + /// chained together. + /// + /// + /// + /// var config = Configuration.Builder("my-mobile-key").AllAttributesPrivate(true).EventCapacity(1000).Build(); + /// + /// + public interface IConfigurationBuilder + { + /// /// Creates a based on the properties that have been set on the builder. /// Modifying the builder after this point does not affect the returned Configuration. /// /// the configured Configuration object Configuration Build(); /// + /// Sets whether or not user attributes (other than the key) should be private (not sent to + /// the LaunchDarkly server). + /// + /// + /// If this is true, all of the user attributes will be private, not just the attributes specified with + /// on the object. + /// By default, this is false. + /// + /// true if all attributes should be private + /// the same builder + IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); /// + /// Sets the interval for background polling. + /// + /// the background polling interval + /// the same builder + IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); /// + /// Sets the base URI of the LaunchDarkly server. + /// + /// the base URI + /// the same builder + IConfigurationBuilder BaseUri(Uri baseUri); /// /// Set to true if LaunchDarkly should provide additional information about how flag values were + /// calculated. + /// + /// + /// The additional information will then be available through the client's "detail" + /// methods such as . Since this + /// increases the size of network requests, such information is not sent unless you set this option + /// to true. + /// + /// True if evaluation reasons are desired. + /// the same builder + IConfigurationBuilder EvaluationReasons(bool evaluationReasons); + /// + /// Sets the capacity of the events buffer. + /// + /// + /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded + /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events + /// are less likely to be discarded, at the cost of consuming more memory. + /// + /// the capacity of the events buffer + /// the same builder + IConfigurationBuilder EventCapacity(int eventCapacity); /// + /// Sets the time between flushes of the event buffer. + /// + /// + /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. The + /// default value is 5 seconds. + /// + /// the flush interval + /// the same builder + IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval); + /// + /// Enables event sampling if non-zero. + /// + /// + /// When set to the default of zero, all analytics events are sent back to LaunchDarkly. When greater + /// than zero, there is a 1 in EventSamplingInterval chance that events will be sent (example: + /// if the interval is 20, on average 5% of events will be sent). + /// + /// the sampling interval + /// the same builder + IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval); /// + /// Sets the base URL of the LaunchDarkly analytics event server. + /// + /// the events URI + /// the same builder + IConfigurationBuilder EventsUri(Uri eventsUri); + + /// + /// Sets the object to be used for sending HTTP requests. This is exposed for testing purposes. + /// + /// the HttpClientHandler to use + /// the same builder IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler); /// + /// Sets the connection timeout. The default value is 10 seconds. + /// + /// the connection timeout + /// the same builder + IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout); + + /// + /// Sets whether to include full user details in every analytics event. + /// + /// + /// The default is false: events will only include the user key, except for one "index" event that + /// provides the full details for the user. + /// + /// true or false + /// the same builder IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents); + + /// + /// Sets whether or not the streaming API should be used to receive flag updates. + /// + /// + /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. + /// + /// true if the streaming API should be used + /// the same builder IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); + /// + /// + /// + /// + /// the same builder IConfigurationBuilder MobileKey(string mobileKey); /// + /// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made. + /// + /// true if the client should remain offline + /// the same builder + IConfigurationBuilder Offline(bool offline); + + /// + /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be + /// immediately available the next time the SDK is started for the same user. The default is . + /// + /// true to save flag values + /// the same Configuration instance + /// the same builder + IConfigurationBuilder PersistFlagValues(bool persistFlagValues); /// + /// Sets the polling interval (when streaming is disabled). + /// + /// + /// Values less than the default of 30 seconds will be changed to the default. + /// + /// the rule update polling interval + /// the same builder + IConfigurationBuilder PollingInterval(TimeSpan pollingInterval); + + /// + /// Marks an attribute name as private. + /// + /// + /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name + /// removed, even if you did not use the AndPrivate... methods on the object. + /// You may call this method repeatedly to mark multiple attributes as private. + /// + /// the attribute name + /// the same builder IConfigurationBuilder PrivateAttribute(string privateAtributeName); /// + /// Sets the timeout when reading data from the streaming connection. + /// + /// + /// The default value is 5 minutes. + /// + /// the read timeout + /// the same builder + IConfigurationBuilder ReadTimeout(TimeSpan readTimeout); /// + /// Sets the reconnect base time for the streaming connection. + /// + /// + /// The streaming connection uses an exponential backoff algorithm (with jitter) for reconnects, but + /// will start the backoff with a value near the value specified here. The default value is 1 second. + /// + /// the reconnect time base value + /// the same builder + IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime); /// + /// Sets the base URI of the LaunchDarkly streaming server. + /// + /// the stream URI + /// the same builder + IConfigurationBuilder StreamUri(Uri streamUri); + + /// + /// Determines whether to use the REPORT method for networking requests. + /// + /// whether to use REPORT mode + /// the same builder + IConfigurationBuilder UseReport(bool useReport); + + /// + /// Sets the number of user keys that the event processor can remember at any one time. + /// + /// + /// The event processor keeps track of recently seen user keys so that duplicate user details will not + /// be sent in analytics events. + /// + /// the user key cache capacity + /// the same builder IConfigurationBuilder UserKeysCapacity(int userKeysCapacity); /// + /// Sets the interval at which the event processor will clear its cache of known user keys. + /// + /// + /// The default value is five minutes. + /// + /// the flush interval + /// the same builder + IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval); + } + + internal class ConfigurationBuilder : IConfigurationBuilder + { + private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationBuilder)); + + internal bool _allAttributesPrivate = false; + internal TimeSpan _backgroundPollingInterval; + internal Uri _baseUri = Configuration.DefaultUri; + internal TimeSpan _connectionTimeout; + internal bool _enableBackgroundUpdating; + internal bool _evaluationReasons = false; + internal int _eventCapacity = Configuration.DefaultEventCapacity; + internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; + internal int _eventSamplingInterval = 0; + internal Uri _eventsUri = Configuration.DefaultEventsUri; + internal HttpClientHandler _httpClientHandler = new HttpClientHandler(); + internal TimeSpan _httpClientTimeout = Configuration.DefaultHttpClientTimeout; + internal bool _inlineUsersInEvents = false; + internal bool _isStreamingEnabled = true; + internal string _mobileKey; + internal bool _offline = false; + internal bool _persistFlagValues = true; + internal TimeSpan _pollingInterval = Configuration.DefaultPollingInterval; + internal HashSet _privateAttributeNames = null; + internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; + internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; + internal Uri _streamUri = Configuration.DefaultStreamUri; + internal bool _useReport; + internal int _userKeysCapacity = Configuration.DefaultUserKeysCapacity; + internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; + + // Internal properties only settable for testing + internal IConnectionManager _connectionManager; + internal IDeviceInfo _deviceInfo; + internal IEventProcessor _eventProcessor; + internal IFlagCacheManager _flagCacheManager; + internal IFlagChangedEventManager _flagChangedEventManager; + internal IPersistentStorage _persistentStorage; + internal Func _updateProcessorFactory; + + internal ConfigurationBuilder(string mobileKey) { _mobileKey = mobileKey; + } internal ConfigurationBuilder(Configuration copyFrom) { _allAttributesPrivate = copyFrom.AllAttributesPrivate; _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; _baseUri = copyFrom.BaseUri; _connectionTimeout = copyFrom.ConnectionTimeout; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; _eventCapacity = copyFrom.EventCapacity; _eventFlushInterval = copyFrom.EventFlushInterval; _eventSamplingInterval = copyFrom.EventSamplingInterval; _eventsUri = copyFrom.EventsUri; _httpClientHandler = copyFrom.HttpClientHandler; _httpClientTimeout = copyFrom.HttpClientTimeout; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _isStreamingEnabled = copyFrom.IsStreamingEnabled; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; _pollingInterval = copyFrom.PollingInterval; _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : new HashSet(copyFrom.PrivateAttributeNames); _readTimeout = copyFrom.ReadTimeout; _reconnectTime = copyFrom.ReconnectTime; _streamUri = copyFrom.StreamUri; _useReport = copyFrom.UseReport; _userKeysCapacity = copyFrom.UserKeysCapacity; _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; } public Configuration Build() { return new Configuration(this); } + + public IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) + { + _allAttributesPrivate = allAttributesPrivate; + return this; + } + + public IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval) + { + if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) + { + Log.WarnFormat("BackgroundPollingInterval cannot be less than {0}", Configuration.MinimumBackgroundPollingInterval); + _backgroundPollingInterval = Configuration.MinimumBackgroundPollingInterval; + } + else + { + _backgroundPollingInterval = backgroundPollingInterval; + } + return this; + } + + public IConfigurationBuilder BaseUri(Uri baseUri) + { + _baseUri = baseUri; + return this; + } + + public IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) + { + _connectionTimeout = connectionTimeout; + return this; + } + + public IConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating) + { + _enableBackgroundUpdating = enableBackgroundUpdating; + return this; + } + + public IConfigurationBuilder EvaluationReasons(bool evaluationReasons) + { + _evaluationReasons = evaluationReasons; + return this; + } + + public IConfigurationBuilder EventCapacity(int eventCapacity) + { + _eventCapacity = eventCapacity; + return this; + } public IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) + { + _eventFlushInterval = eventflushInterval; + return this; + } + + public IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval) + { + _eventSamplingInterval = eventSamplingInterval; + return this; + } public IConfigurationBuilder EventsUri(Uri eventsUri) + { + _eventsUri = eventsUri; + return this; + } + + public IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler) + { + _httpClientHandler = httpClientHandler; + return this; + } public IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout) + { + _httpClientTimeout = httpClientTimeout; + return this; + } + + public IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) + { + _inlineUsersInEvents = inlineUsersInEvents; + return this; + } + + public IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) + { + _isStreamingEnabled = isStreamingEnabled; + return this; + } + + public IConfigurationBuilder MobileKey(string mobileKey) + { + _mobileKey = mobileKey; + return this; + } public IConfigurationBuilder Offline(bool offline) { _offline = offline; return this; } public IConfigurationBuilder PersistFlagValues(bool persistFlagValues) + { + _persistFlagValues = persistFlagValues; + return this; + } public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) + { + if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) + { + Log.WarnFormat("PollingInterval cannot be less than {0}", Configuration.MinimumPollingInterval); + _pollingInterval = Configuration.MinimumPollingInterval; + } + else + { + _pollingInterval = pollingInterval; + } + return this; + } + + public IConfigurationBuilder PrivateAttribute(string privateAtributeName) + { + if (_privateAttributeNames is null) + { + _privateAttributeNames = new HashSet(); + } + _privateAttributeNames.Add(privateAtributeName); + return this; + } public IConfigurationBuilder ReadTimeout(TimeSpan readTimeout) + { + _readTimeout = readTimeout; + return this; + } public IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) + { + _reconnectTime = reconnectTime; + return this; + } public IConfigurationBuilder StreamUri(Uri streamUri) + { + _streamUri = streamUri; + return this; + } + + public IConfigurationBuilder UseReport(bool useReport) + { + _useReport = useReport; + return this; + } + + public IConfigurationBuilder UserKeysCapacity(int userKeysCapacity) + { + _userKeysCapacity = userKeysCapacity; + return this; + } public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) + { + _userKeysFlushInterval = userKeysFlushInterval; + return this; + } + + // The following properties are internal and settable only for testing. They are not part + // of the IConfigurationBuilder interface, so you must call the internal method + // Configuration.BuilderInternal() which exposes the internal ConfigurationBuilder, + // and then call these methods before you have called any of the public methods (since + // only these methods return ConfigurationBuilder rather than IConfigurationBuilder). + + internal ConfigurationBuilder ConnectionManager(IConnectionManager connectionManager) + { + _connectionManager = connectionManager; + return this; + } + + internal ConfigurationBuilder DeviceInfo(IDeviceInfo deviceInfo) + { + _deviceInfo = deviceInfo; + return this; + } + + internal ConfigurationBuilder EventProcessor(IEventProcessor eventProcessor) + { + _eventProcessor = eventProcessor; + return this; + } + + internal ConfigurationBuilder FlagCacheManager(IFlagCacheManager flagCacheManager) + { + _flagCacheManager = flagCacheManager; + return this; + } + + internal ConfigurationBuilder FlagChangedEventManager(IFlagChangedEventManager flagChangedEventManager) + { + _flagChangedEventManager = flagChangedEventManager; + return this; + } + + internal ConfigurationBuilder PersistentStorage(IPersistentStorage persistentStorage) + { + _persistentStorage = persistentStorage; + return this; + } + + internal ConfigurationBuilder UpdateProcessorFactory(Func updateProcessorFactory) + { + _updateProcessorFactory = updateProcessorFactory; + return this; + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 924f59b2..7a8d6e59 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -15,9 +15,9 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura IFlagChangedEventManager flagChangedEventManager, User user) { - if (configuration.FlagCacheManager != null) + if (configuration._flagCacheManager != null) { - return configuration.FlagCacheManager; + return configuration._flagCacheManager; } else { @@ -29,10 +29,12 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura internal static IConnectionManager CreateConnectionManager(Configuration configuration) { - return configuration.ConnectionManager ?? new MobileConnectionManager(); + return configuration._connectionManager ?? new MobileConnectionManager(); } - internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, User user, IFlagCacheManager flagCacheManager, TimeSpan? overridePollingInterval) + internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, User user, + IFlagCacheManager flagCacheManager, TimeSpan? overridePollingInterval, + bool disableStreaming) { if (configuration.Offline) { @@ -40,12 +42,12 @@ internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration confi return new NullUpdateProcessor(); } - if (configuration.UpdateProcessorFactory != null) + if (configuration._updateProcessorFactory != null) { - return configuration.UpdateProcessorFactory(configuration, flagCacheManager, user); + return configuration._updateProcessorFactory(configuration, flagCacheManager, user); } - if (configuration.IsStreamingEnabled) + if (configuration.IsStreamingEnabled && !disableStreaming) { return new MobileStreamingProcessor(configuration, flagCacheManager, user, null); } @@ -61,9 +63,9 @@ internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration confi internal static IEventProcessor CreateEventProcessor(Configuration configuration) { - if (configuration.EventProcessor != null) + if (configuration._eventProcessor != null) { - return configuration.EventProcessor; + return configuration._eventProcessor; } if (configuration.Offline) { @@ -76,17 +78,17 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration internal static IPersistentStorage CreatePersistentStorage(Configuration configuration) { - return configuration.PersistentStorage ?? new DefaultPersistentStorage(); + return configuration._persistentStorage ?? new DefaultPersistentStorage(); } internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) { - return configuration.DeviceInfo ?? new DefaultDeviceInfo(); + return configuration._deviceInfo ?? new DefaultDeviceInfo(); } internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration) { - return configuration.FlagChangedEventManager ?? new FlagChangedEventManager(); + return configuration._flagChangedEventManager ?? new FlagChangedEventManager(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs index 8fb923a8..572d808f 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs +++ b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs @@ -8,38 +8,37 @@ public interface IMobileConfiguration : IBaseConfiguration /// /// The interval between feature flag updates when the app is running in the background. /// - /// The background polling interval. TimeSpan BackgroundPollingInterval { get; } /// /// When streaming mode is disabled, this is the interval between feature flag updates. /// - /// The feature flag polling interval. TimeSpan PollingInterval { get; } /// - /// Gets the connection timeout to the LaunchDarkly server + /// The connection timeout to the LaunchDarkly server. /// - /// The connection timeout. TimeSpan ConnectionTimeout { get; } /// /// Whether to enable feature flag updates when the app is running in the background. /// - /// true if disable background updating; otherwise, false. - bool EnableBackgroundUpdating { get; } + bool EnableBackgroundUpdating { get; } + + /// + /// The time between flushes of the event buffer. Decreasing the flush interval means + /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. + /// + TimeSpan EventFlushInterval { get; } /// - /// Whether to enable real-time streaming flag updates. When false, - /// feature flags are updated via polling. + /// Whether to enable real-time streaming flag updates. When false, feature flags are updated via polling. /// - /// true if streaming; otherwise, false. bool IsStreamingEnabled { get; } /// /// Whether to use the REPORT HTTP verb when fetching flags from LaunchDarkly. /// - /// true if use report; otherwise, false. bool UseReport { get; } /// @@ -49,7 +48,6 @@ public interface IMobileConfiguration : IBaseConfiguration /// increases the size of network requests, such information is not sent unless you set this option /// to true. /// - /// true if evaluation reasons are desired. bool EvaluationReasons { get; } /// @@ -63,7 +61,6 @@ public interface IMobileConfiguration : IBaseConfiguration /// API, which stores file data under the current account's home directory at /// ~/.local/share/IsolateStorage/. /// - /// true if flag values should be stored locally (the default). bool PersistFlagValues { get; } } } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ce250990..b2cf6f2c 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -3,7 +3,9 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + + netstandard1.6;netstandard2.0;Xamarin.iOS10; 1.0.0-beta18 Library LaunchDarkly.XamarinSdk @@ -74,5 +76,10 @@ + + + + + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index dfb71833..e09962ff 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -19,8 +19,29 @@ public sealed class LdClient : ILdMobileClient { private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); - static volatile LdClient instance; - static readonly object createInstanceLock = new object(); + static volatile LdClient _instance; + static volatile User _user; + + static readonly object _createInstanceLock = new object(); + static readonly EventFactory _eventFactoryDefault = EventFactory.Default; + static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; + + readonly object _myLockObjForConnectionChange = new object(); + readonly object _myLockObjForUserUpdate = new object(); + + readonly Configuration _config; + readonly SemaphoreSlim _connectionLock; + + readonly IDeviceInfo deviceInfo; + readonly IConnectionManager connectionManager; + readonly IEventProcessor eventProcessor; + readonly IFlagCacheManager flagCacheManager; + internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing + readonly IPersistentStorage persister; + + // These LdClient fields are not readonly because they change according to online status + volatile IMobileUpdateProcessor updateProcessor; + volatile bool _disableStreaming; /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot @@ -30,38 +51,19 @@ public sealed class LdClient : ILdMobileClient /// to set this LdClient instance. /// /// The LdClient instance. - public static LdClient Instance => instance; + public static LdClient Instance => _instance; /// /// The Configuration instance used to setup the LdClient. /// /// The Configuration instance. - public Configuration Config { get; private set; } + public Configuration Config => _config; /// /// The User for the LdClient operations. /// /// The User. - public User User { get; private set; } - - readonly object myLockObjForConnectionChange = new object(); - readonly object myLockObjForUserUpdate = new object(); - - readonly IFlagCacheManager flagCacheManager; - readonly IConnectionManager connectionManager; - IMobileUpdateProcessor updateProcessor; // not readonly - may need to be recreated - readonly IEventProcessor eventProcessor; - readonly IPersistentStorage persister; - readonly IDeviceInfo deviceInfo; - readonly EventFactory eventFactoryDefault = EventFactory.Default; - readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; - internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing - - readonly SemaphoreSlim connectionLock; - - // private constructor prevents initialization of this class - // without using WithConfigAnduser(config, user) - LdClient() { } + public User User => _user; LdClient(Configuration configuration, User user) { @@ -74,29 +76,29 @@ public sealed class LdClient : ILdMobileClient throw new ArgumentNullException(nameof(user)); } - Config = configuration; + _config = configuration; - connectionLock = new SemaphoreSlim(1, 1); + _connectionLock = new SemaphoreSlim(1, 1); persister = Factory.CreatePersistentStorage(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); - User = DecorateUser(user); + _user = DecorateUser(user); flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null); + updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null, false); eventProcessor = Factory.CreateEventProcessor(configuration); - eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(User)); + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); SetupConnectionManager(); BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; } /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching feature flags. /// /// This constructor will wait and block on the current thread until initialization and the @@ -122,7 +124,7 @@ public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) } /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching feature flags. This constructor should be used if you do not want to wait /// for the client to finish initializing and receive the first response /// from the LaunchDarkly service. @@ -143,7 +145,7 @@ public static async Task InitAsync(string mobileKey, User user) } /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. /// /// This constructor will wait and block on the current thread until initialization and the @@ -183,7 +185,7 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim } /// - /// Creates and returns new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. This constructor should be used if you do not want to wait /// for the IUpdateProcessor instance to finish initializing and receive the first response /// from the LaunchDarkly service. @@ -213,15 +215,15 @@ public static Task InitAsync(Configuration config, User user) static LdClient CreateInstance(Configuration configuration, User user) { - lock (createInstanceLock) + lock (_createInstanceLock) { - if (Instance != null) + if (_instance != null) { throw new Exception("LdClient instance already exists."); } var c = new LdClient(configuration, user); - Interlocked.CompareExchange(ref instance, c, null); + _instance = c; Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); return c; } @@ -261,7 +263,7 @@ public bool Online public async Task SetOnlineAsync(bool value) { - await connectionLock.WaitAsync(); + await _connectionLock.WaitAsync(); online = value; try { @@ -276,7 +278,7 @@ public async Task SetOnlineAsync(bool value) } finally { - connectionLock.Release(); + _connectionLock.Release(); } return; @@ -290,61 +292,61 @@ void MobileConnectionManager_ConnectionChanged(bool isOnline) /// public bool BoolVariation(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueTypes.Bool, eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; } /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueTypes.Bool, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); } /// public string StringVariation(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.String, eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; } /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.String, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); } /// public float FloatVariation(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Float, eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; } /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Float, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); } /// public int IntVariation(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Int, eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; } /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Int, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); } /// public JToken JsonVariation(string key, JToken defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; } /// public EvaluationDetail JsonVariationDetail(string key, JToken defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryWithReasons); + return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); } EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) @@ -419,7 +421,7 @@ public IDictionary AllFlags() /// public void Track(string eventName, JToken data) { - eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data)); + eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data)); } /// @@ -462,18 +464,18 @@ public async Task IdentifyAsync(User user) User newUser = DecorateUser(user); - await connectionLock.WaitAsync(); + await _connectionLock.WaitAsync(); try { - User = newUser; + _user = newUser; await RestartUpdateProcessorAsync(Config.PollingInterval); } finally { - connectionLock.Release(); + _connectionLock.Release(); } - eventProcessor.SendEvent(eventFactoryDefault.NewIdentifyEvent(newUser)); + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(newUser)); } async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) @@ -485,7 +487,7 @@ async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) void ClearAndSetUpdateProcessor(TimeSpan pollingInterval) { ClearUpdateProcessor(); - updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval); + updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval, _disableStreaming); } void ClearUpdateProcessor() @@ -551,7 +553,7 @@ void Dispose(bool disposing) internal void DetachInstance() // exposed for testing { - Interlocked.CompareExchange(ref instance, null, this); + Interlocked.CompareExchange(ref _instance, null, this); } /// @@ -586,7 +588,7 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh if (args.IsInBackground) { ClearUpdateProcessor(); - Config.IsStreamingEnabled = false; + _disableStreaming = true; if (Config.EnableBackgroundUpdating) { await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); @@ -607,7 +609,7 @@ void ResetProcessorForForeground() { persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "false"); ClearUpdateProcessor(); - Config.IsStreamingEnabled = true; + _disableStreaming = false; } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index 641a3c22..b9f76e20 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -9,25 +9,27 @@ public class ConfigurationTest : BaseTest [Fact] public void CanOverrideConfiguration() { - var config = Configuration.Default("AnyOtherSdkKey") - .WithBaseUri("https://app.AnyOtherEndpoint.com") - .WithEventQueueCapacity(99) - .WithPollingInterval(TimeSpan.FromMinutes(45)); + var config = Configuration.Builder("AnyOtherSdkKey") + .BaseUri(new Uri("https://app.AnyOtherEndpoint.com")) + .EventCapacity(99) + .PollingInterval(TimeSpan.FromMinutes(45)) + .Build(); Assert.Equal(new Uri("https://app.AnyOtherEndpoint.com"), config.BaseUri); Assert.Equal("AnyOtherSdkKey", config.MobileKey); - Assert.Equal(99, config.EventQueueCapacity); + Assert.Equal(99, config.EventCapacity); Assert.Equal(TimeSpan.FromMinutes(45), config.PollingInterval); } [Fact] public void CanOverrideStreamConfiguration() { - var config = Configuration.Default("AnyOtherSdkKey") - .WithStreamUri("https://stream.AnyOtherEndpoint.com") - .WithIsStreamingEnabled(false) - .WithReadTimeout(TimeSpan.FromDays(1)) - .WithReconnectTime(TimeSpan.FromDays(1)); + var config = Configuration.Builder("AnyOtherSdkKey") + .StreamUri(new Uri("https://stream.AnyOtherEndpoint.com")) + .IsStreamingEnabled(false) + .ReadTimeout(TimeSpan.FromDays(1)) + .ReconnectTime(TimeSpan.FromDays(1)) + .Build(); Assert.Equal(new Uri("https://stream.AnyOtherEndpoint.com"), config.StreamUri); Assert.False(config.IsStreamingEnabled); @@ -50,7 +52,7 @@ public void MobileKeyCannotBeEmpty() [Fact] public void CannotSetTooSmallPollingInterval() { - var config = Configuration.Default("AnyOtherSdkKey").WithPollingInterval(TimeSpan.FromSeconds(299)); + var config = Configuration.Builder("AnyOtherSdkKey").PollingInterval(TimeSpan.FromSeconds(299)).Build(); Assert.Equal(TimeSpan.FromSeconds(300), config.PollingInterval); } @@ -58,7 +60,7 @@ public void CannotSetTooSmallPollingInterval() [Fact] public void CannotSetTooSmallBackgroundPollingInterval() { - var config = Configuration.Default("SdkKey").WithBackgroundPollingInterval(TimeSpan.FromSeconds(899)); + var config = Configuration.Builder("SdkKey").BackgroundPollingInterval(TimeSpan.FromSeconds(899)).Build(); Assert.Equal(TimeSpan.FromSeconds(900), config.BackgroundPollingInterval); } @@ -67,8 +69,9 @@ public void CannotSetTooSmallBackgroundPollingInterval() public void CanSetHttpClientHandler() { var handler = new HttpClientHandler(); - var config = Configuration.Default("AnyOtherSdkKey") - .WithHttpClientHandler(handler); + var config = Configuration.Builder("AnyOtherSdkKey") + .HttpClientHandler(handler) + .Build(); Assert.Equal(handler, config.HttpClientHandler); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 2b53e47b..0f68f833 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using LaunchDarkly.Client; using Xunit; @@ -23,8 +24,8 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) - .WithUseReport(false); + var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + .UseReport(false).Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { @@ -49,8 +50,8 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) - .WithUseReport(false).WithEvaluationReasons(true); + var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + .UseReport(false).EvaluationReasons(true).Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { @@ -75,8 +76,8 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) - .WithUseReport(true); + var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + .UseReport(true).Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { @@ -104,8 +105,8 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Default(_mobileKey).WithBaseUri(server.GetUrl()) - .WithUseReport(true).WithEvaluationReasons(true); + var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + .UseReport(true).EvaluationReasons(true).Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 02f3bd8e..07e39964 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -48,7 +48,7 @@ public void InitGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -65,7 +65,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -82,7 +82,7 @@ public void InitCanTimeOutSync() using (var log = new LogSinkScope()) { - var config = BaseConfig(server).WithIsStreamingEnabled(false); + var config = BaseConfig(server).IsStreamingEnabled(false).Build(); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized()); @@ -106,7 +106,7 @@ public void InitFailsOn401Sync(UpdateMode mode) { try { - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); using (var client = TestUtil.CreateClient(config, _user)) { } } catch (Exception e) @@ -132,7 +132,7 @@ await WithServerAsync(async server => using (var log = new LogSinkScope()) { - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that @@ -153,7 +153,7 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); var name = "Sue"; var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); @@ -184,7 +184,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -212,7 +212,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(mode.IsStreaming); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -239,7 +239,7 @@ public void OfflineClientUsesCachedFlagsSync() { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -250,7 +250,7 @@ public void OfflineClientUsesCachedFlagsSync() // out, we should still see the earlier flag values. server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); using (var client = TestUtil.CreateClient(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); @@ -265,7 +265,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).WithUseReport(false).WithIsStreamingEnabled(false); + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -276,7 +276,7 @@ await WithServerAsync(async server => // out, we should still see the earlier flag values. server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Default(_mobileKey).WithOffline(true); + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); @@ -284,12 +284,12 @@ await WithServerAsync(async server => }); } - private Configuration BaseConfig(FluentMockServer server) + private IConfigurationBuilder BaseConfig(FluentMockServer server) { - return Configuration.Default(_mobileKey) - .WithBaseUri(server.GetUrl()) - .WithStreamUri(server.GetUrl()) - .WithEventProcessor(new MockEventProcessor()); + return Configuration.BuilderInternal(_mobileKey) + .EventProcessor(new MockEventProcessor()) + .BaseUri(new Uri(server.GetUrl())) + .StreamUri(new Uri(server.GetUrl())); } private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 25a3cb10..6ecc0d4b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -12,7 +12,7 @@ public class LdClientEvaluationTests : BaseTest private static LdClient ClientWithFlagsJson(string flagsJson) { - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson).Build(); return TestUtil.CreateClient(config, user); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index daa4cc6f..fa985a4d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -11,9 +11,9 @@ public class LdClientEventTests : BaseTest public LdClient MakeClient(User user, string flagsJson) { - Configuration config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); - config.WithEventProcessor(eventProcessor); - return TestUtil.CreateClient(config, user); + var config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); + config.EventProcessor(eventProcessor); + return TestUtil.CreateClient(config.Build(), user); } [Fact] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 6729ca2d..d232fd4b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -14,7 +14,7 @@ public class DefaultLdClientTests : BaseTest LdClient Client() { - var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); return TestUtil.CreateClient(configuration, simpleUser); } @@ -27,21 +27,21 @@ public void CannotCreateClientWithNullConfig() [Fact] public void CannotCreateClientWithNullUser() { - Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); } [Fact] public void CannotCreateClientWithNegativeWaitTime() { - Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.FromMilliseconds(-2))); } [Fact] public void CanCreateClientWithInfiniteWaitTime() { - Configuration config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } TestUtil.ClearClient(); } @@ -81,7 +81,7 @@ public void SharedClientIsTheOnlyClientAvailable() { TestUtil.WithClientLock(() => { - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); @@ -96,7 +96,7 @@ public void CanCreateNewClientAfterDisposingOfSharedInstance() TestUtil.WithClientLock(() => { TestUtil.ClearClient(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}"); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } Assert.Null(LdClient.Instance); // Dispose() is called automatically at end of "using" block @@ -109,7 +109,7 @@ public void ConnectionManagerShouldKnowIfOnlineOrNot() { using (var client = Client()) { - var connMgr = client.Config.ConnectionManager as MockConnectionManager; + var connMgr = client.Config._connectionManager as MockConnectionManager; connMgr.ConnectionChanged += (bool obj) => client.Online = obj; connMgr.Connect(true); Assert.False(client.IsOffline()); @@ -123,10 +123,10 @@ public void ConnectionChangeShouldStopUpdateProcessor() { var mockUpdateProc = new MockPollingProcessor(null); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithUpdateProcessorFactory(mockUpdateProc.AsFactory()); + .UpdateProcessorFactory(mockUpdateProc.AsFactory()).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { - var connMgr = client.Config.ConnectionManager as MockConnectionManager; + var connMgr = client.Config._connectionManager as MockConnectionManager; connMgr.ConnectionChanged += (bool obj) => client.Online = obj; connMgr.Connect(false); Assert.False(mockUpdateProc.IsRunning); @@ -139,7 +139,7 @@ public void UserWithNullKeyWillHaveUniqueKeySet() var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, appKey, "{}") - .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, userWithNullKey)) { Assert.Equal(uniqueId, client.User.Key); @@ -153,7 +153,7 @@ public void UserWithEmptyKeyWillHaveUniqueKeySet() var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, appKey, "{}") - .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, userWithEmptyKey)) { Assert.Equal(uniqueId, client.User.Key); @@ -167,7 +167,7 @@ public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(userWithNullKey); @@ -182,7 +182,7 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(userWithEmptyKey); @@ -207,7 +207,7 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() .Build(); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithDeviceInfo(new MockDeviceInfo(uniqueId)); + .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(user); @@ -249,9 +249,10 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() var flagsJson = "{\"flag\": {\"value\": 100}}"; storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithOffline(true) - .WithPersistentStorage(storage) - .WithFlagCacheManager(null); // use actual cache logic, not mock component (even though persistence layer is a mock) + .PersistentStorage(storage) + .FlagCacheManager(null) // use actual cache logic, not mock component (even though persistence layer is a mock) + .Offline(true) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { Assert.Equal(100, client.IntVariation("flag", 99)); @@ -265,10 +266,11 @@ public void FlagsAreNotLoadedFromPersistentStorageIfPersistFlagValuesIsFalse() var flagsJson = "{\"flag\": {\"value\": 100}}"; storage.Save(Constants.FLAGS_KEY_PREFIX + simpleUser.Key, flagsJson); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .WithOffline(true) - .WithPersistFlagValues(false) - .WithPersistentStorage(storage) - .WithFlagCacheManager(null); // use actual cache logic, not mock component (even though persistence layer is a mock) + .PersistentStorage(storage) + .FlagCacheManager(null) // use actual cache logic, not mock component (even though persistence layer is a mock) + .PersistFlagValues(false) + .Offline(true) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { Assert.Equal(99, client.IntVariation("flag", 99)); // returns default value @@ -281,9 +283,10 @@ public void FlagsAreSavedToPersistentStorageByDefault() var storage = new MockPersistentStorage(); var flagsJson = "{\"flag\": {\"value\": 100}}"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, flagsJson) - .WithPersistentStorage(storage) - .WithFlagCacheManager(null) - .WithUpdateProcessorFactory(MockPollingProcessor.Factory(flagsJson)); + .FlagCacheManager(null) + .UpdateProcessorFactory(MockPollingProcessor.Factory(flagsJson)) + .PersistentStorage(storage) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { var storedJson = storage.GetValue(Constants.FLAGS_KEY_PREFIX + simpleUser.Key); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 562753e4..d1fcd60c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -23,23 +23,25 @@ public class MobileStreamingProcessorTests : BaseTest private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; private IFlagCacheManager mockFlagCacheMgr; - private Configuration config; + private IConfigurationBuilder configBuilder; public MobileStreamingProcessorTests() { mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); - config = Configuration.Default("someKey") - .WithConnectionManager(new MockConnectionManager(true)) - .WithIsStreamingEnabled(true) - .WithFlagCacheManager(mockFlagCacheMgr); + configBuilder = Configuration.BuilderInternal("someKey") + .ConnectionManager(new MockConnectionManager(true)) + .FlagCacheManager(mockFlagCacheMgr) + .IsStreamingEnabled(true); + } private IMobileUpdateProcessor MobileStreamingProcessorStarted() { - IMobileUpdateProcessor processor = new MobileStreamingProcessor(config, mockFlagCacheMgr, user, eventSourceFactory.Create()); + IMobileUpdateProcessor processor = new MobileStreamingProcessor(configBuilder.Build(), + mockFlagCacheMgr, user, eventSourceFactory.Create()); processor.Start(); return processor; } @@ -47,8 +49,8 @@ private IMobileUpdateProcessor MobileStreamingProcessorStarted() [Fact] public void StreamUriInGetModeHasUser() { - config.WithUseReport(false); - var streamingProcessor = MobileStreamingProcessorStarted(); + var config = configBuilder.UseReport(false).Build(); + MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(HttpMethod.Get, props.Method); Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser), props.StreamUri); @@ -57,9 +59,8 @@ public void StreamUriInGetModeHasUser() [Fact] public void StreamUriInGetModeHasReasonsParameterIfConfigured() { - config.WithUseReport(false); - config.WithEvaluationReasons(true); - var streamingProcessor = MobileStreamingProcessorStarted(); + var config = configBuilder.UseReport(false).EvaluationReasons(true).Build(); + MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser + "?withReasons=true"), props.StreamUri); } @@ -67,8 +68,8 @@ public void StreamUriInGetModeHasReasonsParameterIfConfigured() [Fact] public void StreamUriInReportModeHasNoUser() { - config.WithUseReport(true); - var streamingProcessor = MobileStreamingProcessorStarted(); + var config = configBuilder.UseReport(true).Build(); + MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(new HttpMethod("REPORT"), props.Method); Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH), props.StreamUri); @@ -77,18 +78,17 @@ public void StreamUriInReportModeHasNoUser() [Fact] public void StreamUriInReportModeHasReasonsParameterIfConfigured() { - config.WithUseReport(true); - config.WithEvaluationReasons(true); - var streamingProcessor = MobileStreamingProcessorStarted(); + var config = configBuilder.UseReport(true).EvaluationReasons(true).Build(); + MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + "?withReasons=true"), props.StreamUri); } [Fact] - public async void StreamRequestBodyInReportModeHasUser() + public async Task StreamRequestBodyInReportModeHasUser() { - config.WithUseReport(true); - var streamingProcessor = MobileStreamingProcessorStarted(); + configBuilder.UseReport(true); + MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; var body = Assert.IsType(props.RequestBody); var s = await body.ReadAsStringAsync(); @@ -98,7 +98,7 @@ public async void StreamRequestBodyInReportModeHasUser() [Fact] public void PUTstoresFeatureFlags() { - var streamingProcessor = MobileStreamingProcessorStarted(); + MobileStreamingProcessorStarted(); // should be empty before PUT message arrives var flagsInCache = mockFlagCacheMgr.FlagsForUser(user); Assert.Empty(flagsInCache); @@ -114,7 +114,7 @@ public void PUTstoresFeatureFlags() public void PATCHupdatesFeatureFlag() { // before PATCH, fill in flags - var streamingProcessor = MobileStreamingProcessorStarted(); + MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); Assert.Equal(15, intFlagFromPUT); @@ -132,7 +132,7 @@ public void PATCHupdatesFeatureFlag() public void PATCHdoesnotUpdateFlagIfVersionIsLower() { // before PATCH, fill in flags - var streamingProcessor = MobileStreamingProcessorStarted(); + MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); Assert.Equal(15, intFlagFromPUT); @@ -150,7 +150,7 @@ public void PATCHdoesnotUpdateFlagIfVersionIsLower() public void DELETEremovesFeatureFlag() { // before DELETE, fill in flags, test it's there - var streamingProcessor = MobileStreamingProcessorStarted(); + MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); Assert.Equal(15, intFlagFromPUT); @@ -167,7 +167,7 @@ public void DELETEremovesFeatureFlag() public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() { // before DELETE, fill in flags, test it's there - var streamingProcessor = MobileStreamingProcessorStarted(); + MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); Assert.Equal(15, intFlagFromPUT); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index ca31976b..208f9a25 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -133,7 +133,7 @@ public static IDictionary DecodeFlagsJson(string flagsJson) return JsonConvert.DeserializeObject>(flagsJson); } - public static Configuration ConfigWithFlagsJson(User user, string appKey, string flagsJson) + internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) { var flags = DecodeFlagsJson(flagsJson); IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); @@ -142,14 +142,13 @@ public static Configuration ConfigWithFlagsJson(User user, string appKey, string stubbedFlagCache.CacheFlagsForUser(flags, user); } - Configuration configuration = Configuration.Default(appKey) - .WithFlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) - .WithConnectionManager(new MockConnectionManager(true)) - .WithEventProcessor(new MockEventProcessor()) - .WithUpdateProcessorFactory(MockPollingProcessor.Factory(null)) - .WithPersistentStorage(new MockPersistentStorage()) - .WithDeviceInfo(new MockDeviceInfo("")); - return configuration; + return Configuration.BuilderInternal(appKey) + .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) + .ConnectionManager(new MockConnectionManager(true)) + .EventProcessor(new MockEventProcessor()) + .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) + .PersistentStorage(new MockPersistentStorage()) + .DeviceInfo(new MockDeviceInfo("")); } } } From 3614e66adfc3042a6bbebad46ef3f63af684fabc Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 00:34:31 -0700 Subject: [PATCH 195/499] revert accidental changes --- .../LaunchDarkly.XamarinSdk.csproj | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index b2cf6f2c..ce250990 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -3,9 +3,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - - netstandard1.6;netstandard2.0;Xamarin.iOS10; + netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; 1.0.0-beta18 Library LaunchDarkly.XamarinSdk @@ -76,10 +74,5 @@ - - - - - From c9c5a7490b27ee6bdc9a8c96b1ed1b817246d507 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 00:57:37 -0700 Subject: [PATCH 196/499] Change JToken to ImmutableJsonValue in public API --- src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs | 12 ++++++------ .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 16 ++++++++-------- src/LaunchDarkly.XamarinSdk/ValueType.cs | 7 ++++--- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientEvaluationTests.cs | 10 +++++----- .../LdClientEventTests.cs | 2 +- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index 21f33952..f750fc4f 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -92,30 +92,30 @@ public interface ILdMobileClient : ILdCommonClient EvaluationDetail IntVariationDetail(string key, int defaultValue = 0); /// - /// Returns the JToken value of a feature flag for a given flag key. + /// Returns the JSON value of a feature flag for a given flag key. /// /// the unique feature key for the feature flag /// the default value of the flag /// the variation for the selected user, or defaultValue if the flag is /// disabled in the LaunchDarkly control panel - JToken JsonVariation(string key, JToken defaultValue); + ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue); /// - /// Returns the JToken value of a feature flag for a given flag key, in an object that also + /// Returns the JSON value of a feature flag for a given flag key, in an object that also /// describes the way the value was determined. The Reason property in the result will /// also be included in analytics events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object - EvaluationDetail JsonVariationDetail(string key, JToken defaultValue); + EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue); /// /// Tracks that current user performed an event for the given JToken value and given event name. /// /// the name of the event - /// a JSON string containing additional data associated with the event - void Track(string eventName, JToken data); + /// a JSON value containing additional data associated with the event + void Track(string eventName, ImmutableJsonValue data); /// /// Tracks that current user performed an event for the given event name. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ce250990..b13b3e72 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index dfb71833..ebf631ec 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -335,14 +335,14 @@ public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0 return VariationInternal(key, defaultValue, ValueTypes.Int, eventFactoryWithReasons); } - /// - public JToken JsonVariation(string key, JToken defaultValue) + /// + public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryDefault).Value; } - /// - public EvaluationDetail JsonVariationDetail(string key, JToken defaultValue) + /// + public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, eventFactoryWithReasons); } @@ -416,16 +416,16 @@ public IDictionary AllFlags() .ToDictionary(p => p.Key, p => p.Value.value); } - /// - public void Track(string eventName, JToken data) + /// + public void Track(string eventName, ImmutableJsonValue data) { - eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data)); + eventProcessor.SendEvent(eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); } /// public void Track(string eventName) { - Track(eventName, null); + Track(eventName, ImmutableJsonValue.FromJToken(null)); } /// diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs index eef0c6ed..b8e89cf4 100644 --- a/src/LaunchDarkly.XamarinSdk/ValueType.cs +++ b/src/LaunchDarkly.XamarinSdk/ValueType.cs @@ -1,4 +1,5 @@ using System; +using LaunchDarkly.Client; using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin @@ -72,10 +73,10 @@ private static ArgumentException BadTypeException() ValueToJson = value => value == null ? null : new JValue(value) }; - public static ValueType Json = new ValueType + public static ValueType Json = new ValueType { - ValueFromJson = json => json, - ValueToJson = value => value + ValueFromJson = json => ImmutableJsonValue.FromJToken(json), + ValueToJson = value => value.AsJToken() }; } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 22e0c8a5..90d7d771 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 25a3cb10..bf5e4a8c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -167,8 +167,7 @@ public void JsonVariationReturnsValue() string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); using (var client = ClientWithFlagsJson(flagsJson)) { - var defaultValue = new JValue(3); - Assert.Equal(jsonValue, client.JsonVariation("flag-key", defaultValue)); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.FromJToken(3)).AsJToken()); } } @@ -177,7 +176,8 @@ public void JsonVariationReturnsDefaultForUnknownFlag() { using (var client = ClientWithFlagsJson("{}")) { - Assert.Null(client.JsonVariation(nonexistentFlagKey, null)); + var defaultVal = ImmutableJsonValue.FromJToken(3); + Assert.Equal(defaultVal, client.JsonVariation(nonexistentFlagKey, defaultVal)); } } @@ -190,9 +190,9 @@ public void JsonVariationDetailReturnsValue() using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(jsonValue, 1, reason); - var result = client.JsonVariationDetail("flag-key", new JValue(3)); + var result = client.JsonVariationDetail("flag-key", ImmutableJsonValue.FromJToken(3)); // Note, JToken.Equals() doesn't work, so we need to test each property separately - Assert.True(JToken.DeepEquals(expected.Value, result.Value)); + Assert.True(JToken.DeepEquals(expected.Value, result.Value.AsJToken())); Assert.Equal(expected.VariationIndex, result.VariationIndex); Assert.Equal(expected.Reason, result.Reason); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index daa4cc6f..cf8755c1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -35,7 +35,7 @@ public void TrackSendsCustomEvent() using (LdClient client = MakeClient(user, "{}")) { JToken data = new JValue("hi"); - client.Track("eventkey", data); + client.Track("eventkey", ImmutableJsonValue.FromJToken(data)); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { From 9a02c6e01b4ae21db992ca802240a52fc5d4992d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 10:18:05 -0700 Subject: [PATCH 197/499] whitespace --- .../ConfigurationBuilder.cs | 162 ++++++++++++++---- 1 file changed, 131 insertions(+), 31 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index f07b05aa..63d80634 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -23,7 +23,14 @@ namespace LaunchDarkly.Xamarin /// public interface IConfigurationBuilder { - /// /// Creates a based on the properties that have been set on the builder. /// Modifying the builder after this point does not affect the returned Configuration. /// /// the configured Configuration object Configuration Build(); /// + /// + /// Creates a based on the properties that have been set on the builder. + /// Modifying the builder after this point does not affect the returned Configuration. + /// + /// the configured Configuration object + Configuration Build(); + + /// /// Sets whether or not user attributes (other than the key) should be private (not sent to /// the LaunchDarkly server). /// @@ -34,17 +41,24 @@ public interface IConfigurationBuilder /// /// true if all attributes should be private /// the same builder - IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); /// + IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); + + /// /// Sets the interval for background polling. /// /// the background polling interval /// the same builder - IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); /// + IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); + + /// /// Sets the base URI of the LaunchDarkly server. /// /// the base URI /// the same builder - IConfigurationBuilder BaseUri(Uri baseUri); /// /// Set to true if LaunchDarkly should provide additional information about how flag values were + IConfigurationBuilder BaseUri(Uri baseUri); + + /// + /// Set to true if LaunchDarkly should provide additional information about how flag values were /// calculated. /// /// @@ -56,7 +70,8 @@ public interface IConfigurationBuilder /// True if evaluation reasons are desired. /// the same builder IConfigurationBuilder EvaluationReasons(bool evaluationReasons); - /// + + /// /// Sets the capacity of the events buffer. /// /// @@ -66,7 +81,9 @@ public interface IConfigurationBuilder /// /// the capacity of the events buffer /// the same builder - IConfigurationBuilder EventCapacity(int eventCapacity); /// + IConfigurationBuilder EventCapacity(int eventCapacity); + + /// /// Sets the time between flushes of the event buffer. /// /// @@ -76,7 +93,7 @@ public interface IConfigurationBuilder /// the flush interval /// the same builder IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval); - /// + /// /// Enables event sampling if non-zero. /// /// @@ -86,18 +103,23 @@ public interface IConfigurationBuilder /// /// the sampling interval /// the same builder - IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval); /// + IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval); + + /// /// Sets the base URL of the LaunchDarkly analytics event server. /// /// the events URI /// the same builder - IConfigurationBuilder EventsUri(Uri eventsUri); - + IConfigurationBuilder EventsUri(Uri eventsUri); + /// /// Sets the object to be used for sending HTTP requests. This is exposed for testing purposes. /// /// the HttpClientHandler to use - /// the same builder IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler); /// + /// the same builder + IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler); + + /// /// Sets the connection timeout. The default value is 10 seconds. /// /// the connection timeout @@ -112,7 +134,8 @@ public interface IConfigurationBuilder /// provides the full details for the user. /// /// true or false - /// the same builder IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents); + /// the same builder + IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents); /// /// Sets whether or not the streaming API should be used to receive flag updates. @@ -121,12 +144,16 @@ public interface IConfigurationBuilder /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. /// /// true if the streaming API should be used - /// the same builder IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); - /// + /// the same builder + IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); + /// /// /// /// - /// the same builder IConfigurationBuilder MobileKey(string mobileKey); /// + /// the same builder + IConfigurationBuilder MobileKey(string mobileKey); + + /// /// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made. /// /// true if the client should remain offline @@ -140,7 +167,9 @@ public interface IConfigurationBuilder /// true to save flag values /// the same Configuration instance /// the same builder - IConfigurationBuilder PersistFlagValues(bool persistFlagValues); /// + IConfigurationBuilder PersistFlagValues(bool persistFlagValues); + + /// /// Sets the polling interval (when streaming is disabled). /// /// @@ -159,7 +188,10 @@ public interface IConfigurationBuilder /// You may call this method repeatedly to mark multiple attributes as private. /// /// the attribute name - /// the same builder IConfigurationBuilder PrivateAttribute(string privateAtributeName); /// + /// the same builder + IConfigurationBuilder PrivateAttribute(string privateAtributeName); + + /// /// Sets the timeout when reading data from the streaming connection. /// /// @@ -167,7 +199,9 @@ public interface IConfigurationBuilder /// /// the read timeout /// the same builder - IConfigurationBuilder ReadTimeout(TimeSpan readTimeout); /// + IConfigurationBuilder ReadTimeout(TimeSpan readTimeout); + + /// /// Sets the reconnect base time for the streaming connection. /// /// @@ -176,7 +210,9 @@ public interface IConfigurationBuilder /// /// the reconnect time base value /// the same builder - IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime); /// + IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime); + + /// /// Sets the base URI of the LaunchDarkly streaming server. /// /// the stream URI @@ -198,7 +234,10 @@ public interface IConfigurationBuilder /// be sent in analytics events. /// /// the user key cache capacity - /// the same builder IConfigurationBuilder UserKeysCapacity(int userKeysCapacity); /// + /// the same builder + IConfigurationBuilder UserKeysCapacity(int userKeysCapacity); + + /// /// Sets the interval at which the event processor will clear its cache of known user keys. /// /// @@ -248,8 +287,45 @@ internal class ConfigurationBuilder : IConfigurationBuilder internal IPersistentStorage _persistentStorage; internal Func _updateProcessorFactory; - internal ConfigurationBuilder(string mobileKey) { _mobileKey = mobileKey; - } internal ConfigurationBuilder(Configuration copyFrom) { _allAttributesPrivate = copyFrom.AllAttributesPrivate; _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; _baseUri = copyFrom.BaseUri; _connectionTimeout = copyFrom.ConnectionTimeout; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; _eventCapacity = copyFrom.EventCapacity; _eventFlushInterval = copyFrom.EventFlushInterval; _eventSamplingInterval = copyFrom.EventSamplingInterval; _eventsUri = copyFrom.EventsUri; _httpClientHandler = copyFrom.HttpClientHandler; _httpClientTimeout = copyFrom.HttpClientTimeout; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _isStreamingEnabled = copyFrom.IsStreamingEnabled; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; _pollingInterval = copyFrom.PollingInterval; _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : new HashSet(copyFrom.PrivateAttributeNames); _readTimeout = copyFrom.ReadTimeout; _reconnectTime = copyFrom.ReconnectTime; _streamUri = copyFrom.StreamUri; _useReport = copyFrom.UseReport; _userKeysCapacity = copyFrom.UserKeysCapacity; _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; } public Configuration Build() { return new Configuration(this); } + internal ConfigurationBuilder(string mobileKey) + { + _mobileKey = mobileKey; + } + + internal ConfigurationBuilder(Configuration copyFrom) + { + _allAttributesPrivate = copyFrom.AllAttributesPrivate; + _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; + _baseUri = copyFrom.BaseUri; + _connectionTimeout = copyFrom.ConnectionTimeout; + _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; + _evaluationReasons = copyFrom.EvaluationReasons; + _eventCapacity = copyFrom.EventCapacity; + _eventFlushInterval = copyFrom.EventFlushInterval; + _eventSamplingInterval = copyFrom.EventSamplingInterval; + _eventsUri = copyFrom.EventsUri; + _httpClientHandler = copyFrom.HttpClientHandler; + _httpClientTimeout = copyFrom.HttpClientTimeout; + _inlineUsersInEvents = copyFrom.InlineUsersInEvents; + _isStreamingEnabled = copyFrom.IsStreamingEnabled; + _mobileKey = copyFrom.MobileKey; + _offline = copyFrom.Offline; + _persistFlagValues = copyFrom.PersistFlagValues; + _pollingInterval = copyFrom.PollingInterval; + _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : + new HashSet(copyFrom.PrivateAttributeNames); + _readTimeout = copyFrom.ReadTimeout; + _reconnectTime = copyFrom.ReconnectTime; + _streamUri = copyFrom.StreamUri; + _useReport = copyFrom.UseReport; + _userKeysCapacity = copyFrom.UserKeysCapacity; + _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; + } + + public Configuration Build() + { + return new Configuration(this); + } public IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) { @@ -299,7 +375,9 @@ public IConfigurationBuilder EventCapacity(int eventCapacity) { _eventCapacity = eventCapacity; return this; - } public IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) + } + + public IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) { _eventFlushInterval = eventflushInterval; return this; @@ -309,7 +387,9 @@ public IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval) { _eventSamplingInterval = eventSamplingInterval; return this; - } public IConfigurationBuilder EventsUri(Uri eventsUri) + } + + public IConfigurationBuilder EventsUri(Uri eventsUri) { _eventsUri = eventsUri; return this; @@ -319,7 +399,9 @@ public IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandl { _httpClientHandler = httpClientHandler; return this; - } public IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout) + } + + public IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout) { _httpClientTimeout = httpClientTimeout; return this; @@ -341,11 +423,21 @@ public IConfigurationBuilder MobileKey(string mobileKey) { _mobileKey = mobileKey; return this; - } public IConfigurationBuilder Offline(bool offline) { _offline = offline; return this; } public IConfigurationBuilder PersistFlagValues(bool persistFlagValues) + } + + public IConfigurationBuilder Offline(bool offline) + { + _offline = offline; + return this; + } + + public IConfigurationBuilder PersistFlagValues(bool persistFlagValues) { _persistFlagValues = persistFlagValues; return this; - } public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) + } + + public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) { if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) { @@ -367,15 +459,21 @@ public IConfigurationBuilder PrivateAttribute(string privateAtributeName) } _privateAttributeNames.Add(privateAtributeName); return this; - } public IConfigurationBuilder ReadTimeout(TimeSpan readTimeout) + } + + public IConfigurationBuilder ReadTimeout(TimeSpan readTimeout) { _readTimeout = readTimeout; return this; - } public IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) + } + + public IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) { _reconnectTime = reconnectTime; return this; - } public IConfigurationBuilder StreamUri(Uri streamUri) + } + + public IConfigurationBuilder StreamUri(Uri streamUri) { _streamUri = streamUri; return this; @@ -391,7 +489,9 @@ public IConfigurationBuilder UserKeysCapacity(int userKeysCapacity) { _userKeysCapacity = userKeysCapacity; return this; - } public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) + } + + public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) { _userKeysFlushInterval = userKeysFlushInterval; return this; From 9fc6d84d43f399e8786a469ce286c64e406a1dda Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 10:18:56 -0700 Subject: [PATCH 198/499] whitespace --- src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 63d80634..db585ec5 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -93,6 +93,7 @@ public interface IConfigurationBuilder /// the flush interval /// the same builder IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval); + /// /// Enables event sampling if non-zero. /// From 4e6e3fba797bf410a4b505d905095ecdd993af01 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 12:51:35 -0700 Subject: [PATCH 199/499] fix merge --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 276d35ba..d666600a 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -75,23 +75,6 @@ public bool Online } } - readonly object myLockObjForConnectionChange = new object(); - readonly object myLockObjForUserUpdate = new object(); - - readonly IFlagCacheManager flagCacheManager; - readonly IConnectionManager connectionManager; - IMobileUpdateProcessor updateProcessor; // not readonly - may need to be recreated - readonly IEventProcessor eventProcessor; - readonly IPersistentStorage persister; - readonly IDeviceInfo deviceInfo; - readonly EventFactory eventFactoryDefault = EventFactory.Default; - readonly EventFactory eventFactoryWithReasons = EventFactory.DefaultWithReasons; - internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing - - readonly SemaphoreSlim connectionLock; - - volatile bool online; - // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) LdClient() { } From e91bb1f4f32b2af38db164e73b3c519e61c96387 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 12:52:53 -0700 Subject: [PATCH 200/499] fix merge --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index d666600a..48f7dbf2 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -68,7 +68,7 @@ public sealed class LdClient : ILdMobileClient /// public bool Online { - get => online; + get => _online; set { var doNotAwaitResult = SetOnlineAsync(value); @@ -247,16 +247,16 @@ void SetupConnectionManager() Log.InfoFormat("The mobile client connection changed online to {0}", connectionManager.IsConnected); } - online = connectionManager.IsConnected; + _online = connectionManager.IsConnected; } public async Task SetOnlineAsync(bool value) { await _connectionLock.WaitAsync(); - online = value; + _online = value; try { - if (online) + if (_online) { await RestartUpdateProcessorAsync(Config.PollingInterval); } @@ -428,7 +428,7 @@ public bool Initialized() /// public bool IsOffline() { - return !online; + return !_online; } /// From a0ba32d41fc70dc546727345204211f6c31d7bc0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 12:55:23 -0700 Subject: [PATCH 201/499] fix merge --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 1 + .../AndroidSpecificTests.cs | 20 +++++++++---------- .../IOsSpecificTests.cs | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 48f7dbf2..9fed1adf 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -42,6 +42,7 @@ public sealed class LdClient : ILdMobileClient // These LdClient fields are not readonly because they change according to online status volatile IMobileUpdateProcessor updateProcessor; volatile bool _disableStreaming; + volatile bool _online; /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index 35fb99da..aa8ba571 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -8,16 +8,16 @@ public class AndroidSpecificTests [Fact] public void UserHasOSAndDeviceAttributesForPlatform() { - var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}"); - using (var client = TestUtil.CreateClient(config, baseUser)) - { - var user = client.User; - Assert.Equal(baseUser.Key, user.Key); - Assert.Contains("os", user.Custom.Keys); - Assert.StartsWith("Android ", user.Custom["os"].AsString); - Assert.Contains("device", user.Custom.Keys); - } + var baseUser = User.WithKey("key"); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").build(); + using (var client = TestUtil.CreateClient(config, baseUser)) + { + var user = client.User; + Assert.Equal(baseUser.Key, user.Key); + Assert.Contains("os", user.Custom.Keys); + Assert.StartsWith("Android ", user.Custom["os"].AsString); + Assert.Contains("device", user.Custom.Keys); + } } } } diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index c16ffe57..6032b61e 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -9,7 +9,7 @@ public class IOsSpecificTests public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}"); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; From f23bd49fb3800d7b361cdeff97402d402e4811d4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Jul 2019 12:58:29 -0700 Subject: [PATCH 202/499] typo --- .../AndroidSpecificTests.cs | 2 +- tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index aa8ba571..51490d24 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -9,7 +9,7 @@ public class AndroidSpecificTests public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").build(); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index 6032b61e..cf97ed9f 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -9,7 +9,7 @@ public class IOsSpecificTests public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").build(); + var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; From eebea8999b8226ff3f523a541c3f3335389793d0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 10:58:35 -0700 Subject: [PATCH 203/499] add project property that's required by recent Xamarin Android versions --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 1 + .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index b13b3e72..e2fd64f5 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -13,6 +13,7 @@ true latest True + False diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 92602515..da88579f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -22,6 +22,7 @@ Resources Assets true + False True From ff0e29c9915800f34ac017ec214f8a04579411b0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:31:58 -0700 Subject: [PATCH 204/499] debug logging of exception stacktrace --- src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index a630cf0c..8ef352c1 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -143,6 +143,7 @@ private void FireEvent(FlagChangedEventArgs eventArgs) catch (Exception e) { Log.Warn("Unexpected exception from FlagChanged event handler", e); + Log.Debug(e, e); } }); } From caef323d3756c6615ea5c3f79b10297ed14780ec Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:33:27 -0700 Subject: [PATCH 205/499] add internal property for detecting current build platform --- .../PlatformSpecific/UserMetadata.android.cs | 2 ++ .../PlatformSpecific/UserMetadata.ios.cs | 2 ++ .../UserMetadata.netstandard.cs | 2 ++ .../PlatformSpecific/UserMetadata.shared.cs | 4 +++- src/LaunchDarkly.XamarinSdk/PlatformType.cs | 24 +++++++++++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformType.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs index b6b1bc02..6753aa8b 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs @@ -9,5 +9,7 @@ internal static partial class UserMetadata private static string PlatformOS => "Android " + Build.VERSION.SdkInt; + + private static PlatformType PlatformPlatformType => PlatformType.Android; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs index ec7a1b29..5f5cd365 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs @@ -26,5 +26,7 @@ private static string PlatformDevice private static string PlatformOS => "iOS " + UIDevice.CurrentDevice.SystemVersion; + + private static PlatformType PlatformPlatformType => PlatformType.IOs; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs index 145b7ac4..6b6a2f07 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs @@ -6,5 +6,7 @@ internal static partial class UserMetadata private static string PlatformDevice => null; private static string PlatformOS => null; + + private static PlatformType PlatformPlatformType => PlatformType.Standard; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs index 7da32ec9..90152c3f 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs @@ -9,7 +9,7 @@ internal static partial class UserMetadata // to avoid having to recompute them many times. private static readonly string _os = PlatformOS; private static readonly string _device = PlatformDevice; - + /// /// Returns the string that should be passed in the "device" property for all users. /// @@ -21,5 +21,7 @@ internal static partial class UserMetadata /// /// The value for "os", or null if none. internal static string OSName => _os; + + internal static PlatformType PlatformType => PlatformPlatformType; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformType.cs b/src/LaunchDarkly.XamarinSdk/PlatformType.cs new file mode 100644 index 00000000..3c5ab789 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformType.cs @@ -0,0 +1,24 @@ + +namespace LaunchDarkly.Xamarin +{ + /// + /// Values returned by . + /// + public enum PlatformType + { + /// + /// You are using the .NET Standard version of the SDK. + /// + Standard, + + /// + /// You are using the Android version of the SDK. + /// + Android, + + /// + /// You are using the iOS version of the SDK. + /// + IOs + } +} From 463df24289cd9079eb14378c996ead8d5cd79ac6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:35:10 -0700 Subject: [PATCH 206/499] misc doc comment improvements --- .../ILdMobileClient.cs | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index f750fc4f..1f251f45 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -26,9 +26,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Returns the boolean value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. The Reason property in the result will - /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// describes the way the value was determined. /// + /// + /// The Reason property in the result will also be included in analytics events, if you are + /// capturing detailed event data for this flag. + /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object @@ -45,9 +48,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Returns the string value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. The Reason property in the result will - /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// describes the way the value was determined. /// + /// + /// The Reason property in the result will also be included in analytics events, if you are + /// capturing detailed event data for this flag. + /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object @@ -64,9 +70,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Returns the float value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. The Reason property in the result will - /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// describes the way the value was determined. /// + /// + /// The Reason property in the result will also be included in analytics events, if you are + /// capturing detailed event data for this flag. + /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object @@ -83,9 +92,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Returns the integer value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. The Reason property in the result will - /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// describes the way the value was determined. /// + /// + /// The Reason property in the result will also be included in analytics events, if you are + /// capturing detailed event data for this flag. + /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object @@ -102,23 +114,26 @@ public interface ILdMobileClient : ILdCommonClient /// /// Returns the JSON value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. The Reason property in the result will - /// also be included in analytics events, if you are capturing detailed event data for this flag. + /// describes the way the value was determined. /// + /// + /// The Reason property in the result will also be included in analytics events, if you are + /// capturing detailed event data for this flag. + /// /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue); /// - /// Tracks that current user performed an event for the given JToken value and given event name. + /// Tracks that the current user performed an event for the given event name, with additional JSON data. /// /// the name of the event /// a JSON value containing additional data associated with the event void Track(string eventName, ImmutableJsonValue data); /// - /// Tracks that current user performed an event for the given event name. + /// Tracks that the current user performed an event for the given event name. /// /// the name of the event void Track(string eventName); @@ -179,18 +194,56 @@ public interface ILdMobileClient : ILdCommonClient event EventHandler FlagChanged; /// - /// Registers the user. - /// - /// This method will wait and block the current thread until the update processor has finished - /// initializing and received a response from the LaunchDarkly service. + /// Changes the current user. /// - /// the user to register + /// + /// This both sets the current user for the purpose of flag evaluations and also generates an analytics event to + /// tell LaunchDarkly about the user. + /// + /// Identify waits and blocks the current thread until the SDK has received feature flag values for the + /// new user from LaunchDarkly. If you do not want to wait, consider . + /// + /// the new user void Identify(User user); /// - /// Registers the user. + /// Changes the current user. /// + /// + /// This both sets the current user for the purpose of flag evaluations and also generates an analytics event to + /// tell LaunchDarkly about the user. + /// + /// IdentifyAsync is meant to be used from asynchronous code. It returns a Task that is resolved once the + /// SDK has received feature flag values for the new user from LaunchDarkly. + /// + /// + /// // Within asynchronous code, use await to wait for the task to be resolved + /// await client.IdentifyAsync(user); + /// + /// // Or, if you want to let the flag values be retrieved in the background instead of waiting: + /// Task.Run(() => client.IdentifyAsync(user)); + /// /// the user to register Task IdentifyAsync(User user); + + /// + /// Indicates which platform the SDK is built for. + /// + /// + /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, + /// but rather which variant of the SDK is currently in use. + /// + /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android + /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done + /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard + /// variant. + /// + /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific + /// behavior such as detecting when an application has gone into the background, detecting network connectivity, + /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find + /// that these platform-specific behaviors are not working correctly, you may want to check this property to + /// make sure you are not for some reason running the .NET Standard SDK on a phone. + /// + PlatformType PlatformType { get; } } } From ee9b86d100f198480007855e629d107e7a0928bf Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:35:47 -0700 Subject: [PATCH 207/499] make AllFlags return immutable values; add PlatformType --- .../ILdMobileClient.cs | 25 +++++++++++-------- src/LaunchDarkly.XamarinSdk/LdClient.cs | 22 ++++++++++++++-- src/LaunchDarkly.XamarinSdk/ValueType.cs | 5 +++- .../LdClientEvaluationTests.cs | 4 +-- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index 1f251f45..d34e1e48 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -140,12 +140,12 @@ public interface ILdMobileClient : ILdCommonClient /// /// Gets or sets the online status of the client. - /// - /// The setter is equivalent to calling . If you are going from offline - /// to online, and you want to wait until the connection has been established, call - /// and then use await or call Wait() on - /// its return value. /// + /// + /// The setter is equivalent to calling ; if you are going from offline to + /// online, it does not wait until the connection has been established. If you want to wait for the + /// connection, call and then use await. + /// /// true if online; otherwise, false. bool Online { get; set; } @@ -153,17 +153,20 @@ public interface ILdMobileClient : ILdCommonClient /// Sets the client to be online or not. /// /// a Task - /// If set to true value. + /// true if the client should be online Task SetOnlineAsync(bool value); /// /// Returns a map from feature flag keys to feature flag values for the current user. - /// If the result of a flag's value would have returned the default variation, it will have a - /// null entry in the map. If the client is offline or has not been initialized, a null map will be returned. - /// This method will not send analytics events back to LaunchDarkly. /// - /// a map from feature flag keys to {@code JToken} for the current user - IDictionary AllFlags(); + /// + /// If the result of a flag's value would have returned the default variation, the value in the map will contain + /// null instead of a JToken. If the client is offline or has not been initialized, a null map will be returned. + /// + /// This method will not send analytics events back to LaunchDarkly. + /// + /// a map from feature flag keys to values for the current user + IDictionary AllFlags(); /// /// This event is triggered when the client has received an updated value for a feature flag. diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 9fed1adf..141fb0a3 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -391,7 +391,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } /// - public IDictionary AllFlags() + public IDictionary AllFlags() { if (IsOffline()) { @@ -405,7 +405,10 @@ public IDictionary AllFlags() } return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => p.Value.value); + .ToDictionary(p => p.Key, p => new ImmutableJsonValue(p.Value.value)); + // Note that we are calling the ImmutableJsonValue constructor directly instead of using FromJToken() + // because we do not need it to deep-copy mutable values immediately - we know that *we* won't be + // modifying those values. It will deep-copy them if and when the application tries to access them. } /// @@ -571,6 +574,15 @@ public Version Version } } + /// + public PlatformType PlatformType + { + get + { + return PlatformSpecific.UserMetadata.PlatformType; + } + } + /// public event EventHandler FlagChanged { @@ -591,14 +603,20 @@ internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventA internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) { + Log.DebugFormat("Background mode is changing to {0}", args.IsInBackground); if (args.IsInBackground) { ClearUpdateProcessor(); _disableStreaming = true; if (Config.EnableBackgroundUpdating) { + Log.Debug("Background updating is enabled, starting polling processor"); await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); } + else + { + Log.Debug("Background updating is disabled"); + } persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } else diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs index b8e89cf4..fc20b299 100644 --- a/src/LaunchDarkly.XamarinSdk/ValueType.cs +++ b/src/LaunchDarkly.XamarinSdk/ValueType.cs @@ -75,8 +75,11 @@ private static ArgumentException BadTypeException() public static ValueType Json = new ValueType { - ValueFromJson = json => ImmutableJsonValue.FromJToken(json), + ValueFromJson = json => new ImmutableJsonValue(json), ValueToJson = value => value.AsJToken() + // Note that we are calling the ImmutableJsonValue constructor directly instead of using FromJToken() + // because we do not need it to deep-copy mutable values immediately - we know that *we* won't be + // modifying those values. It will deep-copy them if and when the application tries to access them. }; } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index fe7db1d5..6929cb19 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -206,8 +206,8 @@ public void AllFlagsReturnsAllFlagValues() { var result = client.AllFlags(); Assert.Equal(2, result.Count); - Assert.Equal(new JValue("a"), result["flag1"]); - Assert.Equal(new JValue("b"), result["flag2"]); + Assert.Equal("a", result["flag1"].AsString); + Assert.Equal("b", result["flag2"].AsString); } } From 7e840c04c71802d45501382ff1162b5456dd1885 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:43:21 -0700 Subject: [PATCH 208/499] more debug logging --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 141fb0a3..df4319b2 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -276,6 +276,7 @@ public async Task SetOnlineAsync(bool value) void MobileConnectionManager_ConnectionChanged(bool isOnline) { + Log.DebugFormat("Setting online to {0} due to a connectivity change event", isOnline); Online = isOnline; } From 1b428afa594529e7e3f254137e466e22771e243f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:52:13 -0700 Subject: [PATCH 209/499] change PlatformType to static, add tests --- .../ILdMobileClient.cs | 20 ----------- src/LaunchDarkly.XamarinSdk/LdClient.cs | 35 ++++++++++++++----- .../AndroidSpecificTests.cs | 6 ++++ .../IOsSpecificTests.cs | 6 ++++ 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs index d34e1e48..983034e4 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs @@ -228,25 +228,5 @@ public interface ILdMobileClient : ILdCommonClient /// /// the user to register Task IdentifyAsync(User user); - - /// - /// Indicates which platform the SDK is built for. - /// - /// - /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, - /// but rather which variant of the SDK is currently in use. - /// - /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android - /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done - /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard - /// variant. - /// - /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific - /// behavior such as detecting when an application has gone into the background, detecting network connectivity, - /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find - /// that these platform-specific behaviors are not working correctly, you may want to check this property to - /// make sure you are not for some reason running the .NET Standard SDK on a phone. - /// - PlatformType PlatformType { get; } } } diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index df4319b2..0f5cd331 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -74,6 +74,32 @@ public bool Online { var doNotAwaitResult = SetOnlineAsync(value); } + } + + /// + /// Indicates which platform the SDK is built for. + /// + /// + /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, + /// but rather which variant of the SDK is currently in use. + /// + /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android + /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done + /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard + /// variant. + /// + /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific + /// behavior such as detecting when an application has gone into the background, detecting network connectivity, + /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find + /// that these platform-specific behaviors are not working correctly, you may want to check this property to + /// make sure you are not for some reason running the .NET Standard SDK on a phone. + /// + public static PlatformType PlatformType + { + get + { + return PlatformSpecific.UserMetadata.PlatformType; + } } // private constructor prevents initialization of this class @@ -575,15 +601,6 @@ public Version Version } } - /// - public PlatformType PlatformType - { - get - { - return PlatformSpecific.UserMetadata.PlatformType; - } - } - /// public event EventHandler FlagChanged { diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index 51490d24..337a3dc3 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -5,6 +5,12 @@ namespace LaunchDarkly.Xamarin.Tests { public class AndroidSpecificTests { + [Fact] + public void SdkReturnsAndroidPlatformType() + { + Assert.Equal(PlatformType.Android, LdClient.PlatformType); + } + [Fact] public void UserHasOSAndDeviceAttributesForPlatform() { diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index cf97ed9f..63bbb60d 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -5,6 +5,12 @@ namespace LaunchDarkly.Xamarin.Tests { public class IOsSpecificTests { + [Fact] + public void SdkReturnsIOsPlatformType() + { + Assert.Equal(PlatformType.IOs, LdClient.PlatformType); + } + [Fact] public void UserHasOSAndDeviceAttributesForPlatform() { From aabdc9ebc4029044472fcd9901d62b26de25cd3b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 11:52:40 -0700 Subject: [PATCH 210/499] the IDE keeps wanting to refresh this file so let's just commit the changes --- .../Resources/Resource.designer.cs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs index f352a1ff..bdd99699 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -26,6 +26,107 @@ static Resource() public static void UpdateIdValues() { + global::LaunchDarkly.XamarinSdk.Resource.Attribute.font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.font; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderCerts; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderPackage; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderQuery; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontWeight; + global::LaunchDarkly.XamarinSdk.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::LaunchDarkly.XamarinSdk.Resource.Color.notification_action_color_filter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_action_color_filter; + global::LaunchDarkly.XamarinSdk.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_icon_bg_color; + global::LaunchDarkly.XamarinSdk.Resource.Color.ripple_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_light; + global::LaunchDarkly.XamarinSdk.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_text_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_subtext_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_action_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_action_background; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_icon_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_icon_background; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_tile_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_container; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_divider; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_image; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_text; + global::LaunchDarkly.XamarinSdk.Resource.Id.actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.actions; + global::LaunchDarkly.XamarinSdk.Resource.Id.async = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.async; + global::LaunchDarkly.XamarinSdk.Resource.Id.blocking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.blocking; + global::LaunchDarkly.XamarinSdk.Resource.Id.chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.chronometer; + global::LaunchDarkly.XamarinSdk.Resource.Id.forever = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.forever; + global::LaunchDarkly.XamarinSdk.Resource.Id.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon; + global::LaunchDarkly.XamarinSdk.Resource.Id.icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon_group; + global::LaunchDarkly.XamarinSdk.Resource.Id.info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.info; + global::LaunchDarkly.XamarinSdk.Resource.Id.italic = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.italic; + global::LaunchDarkly.XamarinSdk.Resource.Id.line1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line1; + global::LaunchDarkly.XamarinSdk.Resource.Id.line3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line3; + global::LaunchDarkly.XamarinSdk.Resource.Id.normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.normal; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_background; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column_container; + global::LaunchDarkly.XamarinSdk.Resource.Id.right_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_icon; + global::LaunchDarkly.XamarinSdk.Resource.Id.right_side = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_side; + global::LaunchDarkly.XamarinSdk.Resource.Id.tag_transition_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tag_transition_group; + global::LaunchDarkly.XamarinSdk.Resource.Id.text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text; + global::LaunchDarkly.XamarinSdk.Resource.Id.text2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text2; + global::LaunchDarkly.XamarinSdk.Resource.Id.time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.time; + global::LaunchDarkly.XamarinSdk.Resource.Id.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title; + global::LaunchDarkly.XamarinSdk.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action_tombstone; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_custom_big; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_icon_group; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_time; + global::LaunchDarkly.XamarinSdk.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; From dd52e7473d3738891488dc8a08b95d14d63a904c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 18:44:15 -0700 Subject: [PATCH 211/499] use immutable JSON values in FlagChangedEvent --- .../FlagChangedEvent.cs | 19 ++++++++++++------- .../PlatformSpecific/Preferences.android.cs | 1 - .../FlagChangedEventTests.cs | 12 ++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 8ef352c1..59b5282e 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Common.Logging; +using LaunchDarkly.Client; using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin @@ -19,7 +20,7 @@ public class FlagChangedEventArgs /// The updated value of the flag for the current user. /// /// - /// Since flag values can be of any JSON type, this property is a . The properties + /// Since flag values can be of any JSON type, this property is an . The properties /// , , etc. are shortcuts for accessing its value with a /// specific data type. /// @@ -39,12 +40,12 @@ public class FlagChangedEventArgs /// would have specified "xyz" as a default value when evaluating the flag, so NewValue will simply /// be null. /// - public JToken NewValue { get; private set; } + public ImmutableJsonValue NewValue { get; private set; } /// /// The last known value of the flag for the current user prior to the update. /// - public JToken OldValue { get; private set; } + public ImmutableJsonValue OldValue { get; private set; } /// /// True if the flag was completely removed from the environment. @@ -77,11 +78,12 @@ public class FlagChangedEventArgs private T AsType(ValueType valueType, T defaultValue) { - if (NewValue != null) + var jt = NewValue.AsJToken(); + if (jt != null) { try { - return valueType.ValueFromJson(NewValue); + return valueType.ValueFromJson(jt); } catch (ArgumentException) {} } @@ -91,8 +93,11 @@ private T AsType(ValueType valueType, T defaultValue) internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) { Key = key; - NewValue = newValue; - OldValue = oldValue; + // Note that we're calling the ImmutableJsonValue constructor directly instead of using FromJToken(), + // because this is an internal value that we know we will not be modifying even if it is mutable. + // ImmutableJsonValue will take care of deep-copying the value if the application requests it. + NewValue = new ImmutableJsonValue(newValue); + OldValue = new ImmutableJsonValue(oldValue); FlagWasDeleted = flagWasDeleted; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs index c958bb64..0325457b 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs @@ -95,7 +95,6 @@ static string PlatformGet(string key, string defaultValue, string sharedName) { lock (locker) { - object value = null; using (var sharedPreferences = GetSharedPreferences(sharedName)) { return sharedPreferences.GetString(key, defaultValue); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs index 01dcc176..a404b3c5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -35,8 +35,8 @@ public void CanRegisterListeners() Assert.Equal(INT_FLAG, event2a.Key); Assert.Equal(7, event1a.NewIntValue); Assert.Equal(7, event2a.NewIntValue); - Assert.Equal(6, event1a.OldValue); - Assert.Equal(6, event2a.OldValue); + Assert.Equal(6, event1a.OldValue.AsInt); + Assert.Equal(6, event2a.OldValue.AsInt); Assert.False(event1a.FlagWasDeleted); Assert.False(event2a.FlagWasDeleted); @@ -44,8 +44,8 @@ public void CanRegisterListeners() Assert.Equal(DOUBLE_FLAG, event2b.Key); Assert.Equal(10.5, event1b.NewFloatValue); Assert.Equal(10.5, event2b.NewFloatValue); - Assert.Equal(9.5, event1b.OldValue); - Assert.Equal(9.5, event2b.OldValue); + Assert.Equal(9.5, event1b.OldValue.AsFloat); + Assert.Equal(9.5, event2b.OldValue.AsFloat); Assert.False(event1b.FlagWasDeleted); Assert.False(event2b.FlagWasDeleted); } @@ -66,7 +66,7 @@ public void CanUnregisterListeners() var e = listener2.Await(); Assert.Equal(INT_FLAG, e.Key); Assert.Equal(7, e.NewIntValue); - Assert.Equal(6, e.OldValue); + Assert.Equal(6, e.OldValue.AsInt); // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. Thread.Sleep(100); @@ -85,7 +85,7 @@ public void ListenerGetsUpdatedWhenManagerFlagDeleted() var e = listener.Await(); Assert.Equal(INT_FLAG, e.Key); - Assert.Equal(1, e.OldValue); + Assert.Equal(1, e.OldValue.AsInt); Assert.True(e.FlagWasDeleted); } From 850ce75f40a124b334ef2a2db1c53634688ccba5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Jul 2019 22:39:07 -0700 Subject: [PATCH 212/499] Remove redundant convenience methods --- .../FlagChangedEvent.cs | 48 ++----------------- 1 file changed, 5 insertions(+), 43 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 59b5282e..3635d21a 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -20,9 +20,9 @@ public class FlagChangedEventArgs /// The updated value of the flag for the current user. /// /// - /// Since flag values can be of any JSON type, this property is an . The properties - /// , , etc. are shortcuts for accessing its value with a - /// specific data type. + /// Since flag values can be of any JSON type, this property is an . You + /// can use convenience properties of ImmutableJsonValue such as AsBool to convert it to a + /// primitive type, or AsJToken() for complex types. /// /// Flag evaluations always produce non-null values, but this property could still be null if the flag was /// completely deleted or if it could not be evaluated due to an error of some kind. @@ -33,12 +33,12 @@ public class FlagChangedEventArgs /// in the method call: /// /// - /// client.StringVariation("feature1", "xyz"); + /// client.StringVariation("feature1", "xyz"); /// /// /// But when a FlagChangedEvent is sent for the deletion of the flag, it has no way to know that you /// would have specified "xyz" as a default value when evaluating the flag, so NewValue will simply - /// be null. + /// contain a null. /// public ImmutableJsonValue NewValue { get; private set; } @@ -52,44 +52,6 @@ public class FlagChangedEventArgs /// public bool FlagWasDeleted { get; private set; } - /// - /// Shortcut for converting to a bool. Returns false if the value is null or is not a - /// boolean (will never throw an exception). - /// - public bool NewBoolValue => AsType(ValueTypes.Bool, false); - - /// - /// Shortcut for converting to a string. Returns null if the value is null or is not a - /// string (will never throw an exception). - /// - public string NewStringValue => AsType(ValueTypes.String, null); - - /// - /// Shortcut for converting to an int. Returns 0 if the value is null or is not - /// numeric (will never throw an exception). - /// - public int NewIntValue => AsType(ValueTypes.Int, 0); - - /// - /// Shortcut for converting to a float. Returns 0 if the value is null or is not - /// numeric (will never throw an exception). - /// - public float NewFloatValue => AsType(ValueTypes.Float, 0); - - private T AsType(ValueType valueType, T defaultValue) - { - var jt = NewValue.AsJToken(); - if (jt != null) - { - try - { - return valueType.ValueFromJson(jt); - } - catch (ArgumentException) {} - } - return defaultValue; - } - internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) { Key = key; From f986012dfb53a1a4c4d5423ea14c345787bca0b7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 1 Aug 2019 09:36:49 -0700 Subject: [PATCH 213/499] fix tests --- .../FlagCacheManagerTests.cs | 4 ++-- .../FlagChangedEventTests.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index 9c8cef20..f568ec49 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -81,7 +81,7 @@ public void UpdateFlagSendsFlagChangeEvent() var e = listener.Await(); Assert.Equal("int-flag", e.Key); - Assert.Equal(7, e.NewIntValue); + Assert.Equal(7, e.NewValue.AsInt); Assert.False(e.FlagWasDeleted); } @@ -113,7 +113,7 @@ public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() var e = listener.Await(); Assert.Equal("int-flag", e.Key); - Assert.Equal(5, e.NewIntValue); + Assert.Equal(5, e.NewValue.AsInt); Assert.False(e.FlagWasDeleted); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs index a404b3c5..c56ff54d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -33,8 +33,8 @@ public void CanRegisterListeners() Assert.Equal(INT_FLAG, event1a.Key); Assert.Equal(INT_FLAG, event2a.Key); - Assert.Equal(7, event1a.NewIntValue); - Assert.Equal(7, event2a.NewIntValue); + Assert.Equal(7, event1a.NewValue.AsInt); + Assert.Equal(7, event2a.NewValue.AsInt); Assert.Equal(6, event1a.OldValue.AsInt); Assert.Equal(6, event2a.OldValue.AsInt); Assert.False(event1a.FlagWasDeleted); @@ -42,8 +42,8 @@ public void CanRegisterListeners() Assert.Equal(DOUBLE_FLAG, event1b.Key); Assert.Equal(DOUBLE_FLAG, event2b.Key); - Assert.Equal(10.5, event1b.NewFloatValue); - Assert.Equal(10.5, event2b.NewFloatValue); + Assert.Equal(10.5, event1b.NewValue.AsFloat); + Assert.Equal(10.5, event2b.NewValue.AsFloat); Assert.Equal(9.5, event1b.OldValue.AsFloat); Assert.Equal(9.5, event2b.OldValue.AsFloat); Assert.False(event1b.FlagWasDeleted); @@ -65,7 +65,7 @@ public void CanUnregisterListeners() var e = listener2.Await(); Assert.Equal(INT_FLAG, e.Key); - Assert.Equal(7, e.NewIntValue); + Assert.Equal(7, e.NewValue.AsInt); Assert.Equal(6, e.OldValue.AsInt); // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. From 590da3413bb98405cff4cceb051dcb435ff77bec Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 5 Aug 2019 12:32:09 -0700 Subject: [PATCH 214/499] remove deprecated stuff, update for CommonSdk 3.x API changes (#63) * remove deprecated stuff, update for CommonSdk 3.x API changes * rename ILdMobileClient to ILdClient * rename interface (missed commit) * rm IMobileConfiguration, make config classes sealed, rm SdkKey alias, update comments --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 208 ++++++++++++------ .../ConfigurationBuilder.cs | 66 +++--- src/LaunchDarkly.XamarinSdk/Factory.cs | 4 +- .../FeatureFlagRequestor.cs | 6 +- .../FlagChangedEvent.cs | 7 +- .../{ILdMobileClient.cs => ILdClient.cs} | 19 +- .../IMobileConfiguration.cs | 66 ------ .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 9 +- .../MobileStreamingProcessor.cs | 6 +- src/LaunchDarkly.XamarinSdk/ValueType.cs | 5 +- .../LDClientEndToEndTests.cs | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientEvaluationTests.cs | 7 +- .../LdClientEventTests.cs | 2 +- 15 files changed, 215 insertions(+), 196 deletions(-) rename src/LaunchDarkly.XamarinSdk/{ILdMobileClient.cs => ILdClient.cs} (95%) delete mode 100644 src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index e684369f..e9deb58e 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Net.Http; using LaunchDarkly.Client; +using LaunchDarkly.Common; namespace LaunchDarkly.Xamarin { @@ -14,7 +15,7 @@ namespace LaunchDarkly.Xamarin /// , or using a builder pattern with /// or . /// - public class Configuration : IMobileConfiguration + public sealed class Configuration { private readonly bool _allAttributesPrivate; private readonly TimeSpan _backgroundPollingInterval; @@ -24,7 +25,6 @@ public class Configuration : IMobileConfiguration private readonly bool _evaluationReasons; private readonly TimeSpan _eventFlushInterval; private readonly int _eventCapacity; - private readonly int _eventSamplingInterval; private readonly Uri _eventsUri; private readonly HttpClientHandler _httpClientHandler; private readonly TimeSpan _httpClientTimeout; @@ -53,13 +53,23 @@ public class Configuration : IMobileConfiguration /// /// Whether or not user attributes (other than the key) should be private (not sent to - /// the LaunchDarkly server). If this is true, all of the user attributes will be private, - /// not just the attributes specified with the AndPrivate... methods on the - /// object. By default, this is false. + /// the LaunchDarkly server). /// + /// + /// If this is true, all of the user attributes will be private, not just the attributes specified with + /// or with the + /// method in . + /// + /// By default, this is false. + /// public bool AllAttributesPrivate => _allAttributesPrivate; - /// + /// + /// The interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. + /// public TimeSpan BackgroundPollingInterval => _backgroundPollingInterval; /// @@ -67,47 +77,48 @@ public class Configuration : IMobileConfiguration /// public Uri BaseUri => _baseUri; - /// + /// + /// The connection timeout to the LaunchDarkly server. + /// public TimeSpan ConnectionTimeout { get; internal set; } - /// + /// + /// Whether to enable feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. + /// public bool EnableBackgroundUpdating => _enableBackgroundUpdating; - /// - public bool EvaluationReasons => _evaluationReasons; - /// - /// The time between flushes of the event buffer. Decreasing the flush interval means - /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. + /// True if LaunchDarkly should provide additional information about how flag values were + /// calculated. /// - public TimeSpan EventFlushInterval => _eventFlushInterval; + /// + /// The additional information will then be available through the client's "detail" + /// methods such as . Since this + /// increases the size of network requests, such information is not sent unless you set this option + /// to true. + /// + public bool EvaluationReasons => _evaluationReasons; /// - /// The capacity of the events buffer. The client buffers up to this many events in - /// memory before flushing. If the capacity is exceeded before the buffer is flushed, - /// events will be discarded. Increasing the capacity means that events are less likely - /// to be discarded, at the cost of consuming more memory. + /// The capacity of the events buffer. /// + /// + /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded + /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events + /// are less likely to be discarded, at the cost of consuming more memory. + /// public int EventCapacity => _eventCapacity; /// - /// Deprecated name for . - /// - [Obsolete] - public int EventQueueCapacity => EventCapacity; - - /// - /// Deprecated name for . - /// - [Obsolete] - public TimeSpan EventQueueFrequency => EventFlushInterval; - - /// - /// Enables event sampling if non-zero. When set to the default of zero, all analytics events are - /// sent back to LaunchDarkly. When greater than zero, there is a 1 in EventSamplingInterval - /// chance that events will be sent (example: if the interval is 20, on average 5% of events will be sent). + /// The time between flushes of the event buffer. /// - public int EventSamplingInterval => _eventSamplingInterval; + /// + /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. + /// + public TimeSpan EventFlushInterval => _eventFlushInterval; /// /// The base URL of the LaunchDarkly analytics event server. @@ -124,21 +135,29 @@ public class Configuration : IMobileConfiguration /// public TimeSpan HttpClientTimeout => _httpClientTimeout; - /// - /// True if full user details should be included in every analytics event. The default is false (events will - /// only include the user key, except for one "index" event that provides the full details for the user). + /// + /// Sets whether to include full user details in every analytics event. /// + /// + /// The default is false: events will only include the user key, except for one "index" event that + /// provides the full details for the user. + /// public bool InlineUsersInEvents => _inlineUsersInEvents; /// - /// Whether or not the streaming API should be used to receive flag updates. This is true by default. - /// Streaming should only be disabled on the advice of LaunchDarkly support. + /// Whether or not the streaming API should be used to receive flag updates. /// + /// + /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. + /// public bool IsStreamingEnabled => _isStreamingEnabled; /// - /// The Mobile key for your LaunchDarkly environment. + /// The key for your LaunchDarkly environment. /// + /// + /// This should be the "mobile key" field for the environment on your LaunchDarkly dashboard. + /// public string MobileKey => _mobileKey; /// @@ -146,55 +165,70 @@ public class Configuration : IMobileConfiguration /// public bool Offline => _offline; - /// + /// + /// Whether the SDK should save flag values for each user in persistent storage, so they will be + /// immediately available the next time the SDK is started for the same user. + /// + /// + /// The default is true. + /// public bool PersistFlagValues => _persistFlagValues; /// - /// Set the polling interval (when streaming is disabled). The default value is 30 seconds. + /// The polling interval (when streaming is disabled). /// public TimeSpan PollingInterval => _pollingInterval; /// - /// Marks a set of attribute names as private. Any users sent to LaunchDarkly with this - /// configuration active will have attributes with these names removed, even if you did - /// not use the AndPrivate... methods on the object. + /// Attribute names that have been marked as private for all users. /// + /// + /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name + /// removed, even if you did not use the + /// method in . + /// public ISet PrivateAttributeNames => _privateAttributeNames; /// - /// The timeout when reading data from the EventSource API. The default value is 5 minutes. + /// The timeout when reading data from the streaming connection. /// public TimeSpan ReadTimeout => _readTimeout; /// - /// The reconnect base time for the streaming connection.The streaming connection - /// uses an exponential backoff algorithm (with jitter) for reconnects, but will start the - /// backoff with a value near the value specified here. The default value is 1 second. + /// The reconnect base time for the streaming connection. /// + /// + /// The streaming connection uses an exponential backoff algorithm (with jitter) for reconnects, but + /// will start the backoff with a value near the value specified here. The default value is 1 second. + /// public TimeSpan ReconnectTime => _reconnectTime; - /// - /// Alternate name for . - /// - public string SdkKey => MobileKey; - /// /// The base URL of the LaunchDarkly streaming server. /// public Uri StreamUri => _streamUri; - /// + /// + /// Whether to use the HTTP REPORT method for feature flag requests. + /// + /// + /// By default, polling and streaming connections are made with the GET method, witht the user data + /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. + /// However, some network gateways do not support REPORT. + /// public bool UseReport => _useReport; - /// - /// The number of user keys that the event processor can remember at any one time, so that - /// duplicate user details will not be sent in analytics events. - /// + /// + /// The number of user keys that the event processor can remember at any one time. + /// + /// + /// The event processor keeps track of recently seen user keys so that duplicate user details will not + /// be sent in analytics events. + /// public int UserKeysCapacity => _userKeysCapacity; /// - /// The interval at which the event processor will reset its set of known user keys. The - /// default value is five minutes. + /// The interval at which the event processor will reset its set of known user keys. /// public TimeSpan UserKeysFlushInterval => _userKeysFlushInterval; @@ -233,7 +267,7 @@ public static Configuration Default(string mobileKey) return Builder(mobileKey).Build(); } - /// /// Creates a for constructing a configuration object using a fluent syntax. /// /// /// This is the only method for building a Configuration if you are setting properties /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of /// properties, after which you call to get the resulting /// Configuration instance. /// /// /// /// var config = Configuration.Builder("my-sdk-key") /// .EventQueueFrequency(TimeSpan.FromSeconds(90)) /// .StartWaitTime(TimeSpan.FromSeconds(5)) /// .Build(); /// /// /// the SDK key for your LaunchDarkly environment + /// /// Creates a ConfigurationBuilder for constructing a configuration object using a fluent syntax. /// /// /// This is the only method for building a Configuration if you are setting properties /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of /// properties, after which you call to get the resulting /// Configuration instance. /// /// ///
        ///     var config = Configuration.Builder("my-sdk-key")
        ///         .EventQueueFrequency(TimeSpan.FromSeconds(90))
        ///         .StartWaitTime(TimeSpan.FromSeconds(5))
        ///         .Build();
        /// 
///
/// the mobile SDK key for your LaunchDarkly environment /// a builder object public static IConfigurationBuilder Builder(string mobileKey) { if (String.IsNullOrEmpty(mobileKey)) { @@ -241,9 +275,20 @@ public static Configuration Default(string mobileKey) } return new ConfigurationBuilder(mobileKey); } + /// + /// Exposed for test code that needs to access the internal methods of ConfigurationBuilder that + /// are not in . + /// + /// the mobile SDK key + /// a builder object internal static ConfigurationBuilder BuilderInternal(string mobileKey) { return new ConfigurationBuilder(mobileKey); } + /// + /// Creates a ConfigurationBuilder starting with the properties of an existing Configuration. + /// + /// the configuration to copy + /// a builder object public static IConfigurationBuilder Builder(Configuration fromConfiguration) { return new ConfigurationBuilder(fromConfiguration); @@ -251,7 +296,7 @@ public static IConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { - _allAttributesPrivate = builder._allAttributesPrivate; _backgroundPollingInterval = builder._backgroundPollingInterval; _baseUri = builder._baseUri; _connectionTimeout = builder._connectionTimeout; _enableBackgroundUpdating = builder._enableBackgroundUpdating; _evaluationReasons = builder._evaluationReasons; _eventFlushInterval = builder._eventFlushInterval; _eventCapacity = builder._eventCapacity; _eventSamplingInterval = builder._eventSamplingInterval; _eventsUri = builder._eventsUri; _httpClientHandler = builder._httpClientHandler; _httpClientTimeout = builder._httpClientTimeout; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; _offline = builder._offline; _persistFlagValues = builder._persistFlagValues; _pollingInterval = builder._pollingInterval; _privateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); _readTimeout = builder._readTimeout; _reconnectTime = builder._reconnectTime; _streamUri = builder._streamUri; _useReport = builder._useReport; _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; + _allAttributesPrivate = builder._allAttributesPrivate; _backgroundPollingInterval = builder._backgroundPollingInterval; _baseUri = builder._baseUri; _connectionTimeout = builder._connectionTimeout; _enableBackgroundUpdating = builder._enableBackgroundUpdating; _evaluationReasons = builder._evaluationReasons; _eventFlushInterval = builder._eventFlushInterval; _eventCapacity = builder._eventCapacity; _eventsUri = builder._eventsUri; _httpClientHandler = builder._httpClientHandler; _httpClientTimeout = builder._httpClientTimeout; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; _offline = builder._offline; _persistFlagValues = builder._persistFlagValues; _pollingInterval = builder._pollingInterval; _privateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); _readTimeout = builder._readTimeout; _reconnectTime = builder._reconnectTime; _streamUri = builder._streamUri; _useReport = builder._useReport; _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; _connectionManager = builder._connectionManager; _deviceInfo = builder._deviceInfo; @@ -261,5 +306,42 @@ internal Configuration(ConfigurationBuilder builder) _persistentStorage = builder._persistentStorage; _updateProcessorFactory = builder._updateProcessorFactory; } + + internal IEventProcessorConfiguration EventProcessorConfiguration => new EventProcessorAdapter { Config = this }; + internal IHttpRequestConfiguration HttpRequestConfiguration => new HttpRequestAdapter { Config = this }; + internal IStreamManagerConfiguration StreamManagerConfiguration => new StreamManagerAdapter { Config = this }; + + private class EventProcessorAdapter : IEventProcessorConfiguration + { + internal Configuration Config { get; set; } + public bool AllAttributesPrivate => Config.AllAttributesPrivate; + public int EventCapacity => Config.EventCapacity; + public TimeSpan EventFlushInterval => Config.EventFlushInterval; + public Uri EventsUri => Config.EventsUri; + public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; + public bool InlineUsersInEvents => Config.InlineUsersInEvents; + public ISet PrivateAttributeNames => Config.PrivateAttributeNames; + public TimeSpan ReadTimeout => Config.ReadTimeout; + public TimeSpan ReconnectTime => Config.ReconnectTime; + public int UserKeysCapacity => Config.UserKeysCapacity; + public TimeSpan UserKeysFlushInterval => Config.UserKeysFlushInterval; + } + + private class HttpRequestAdapter : IHttpRequestConfiguration + { + internal Configuration Config { get; set; } + public string HttpAuthorizationKey => Config.MobileKey; + public HttpClientHandler HttpClientHandler => Config.HttpClientHandler; + } + + private class StreamManagerAdapter : IStreamManagerConfiguration + { + internal Configuration Config { get; set; } + public string HttpAuthorizationKey => Config.MobileKey; + public HttpClientHandler HttpClientHandler => Config.HttpClientHandler; + public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; + public TimeSpan ReadTimeout => Config.ReadTimeout; + public TimeSpan ReconnectTime => Config.ReconnectTime; + } } } diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index db585ec5..e1fcc82f 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Net.Http; using Common.Logging; using LaunchDarkly.Client; @@ -36,16 +35,21 @@ public interface IConfigurationBuilder ///
/// /// If this is true, all of the user attributes will be private, not just the attributes specified with - /// on the object. + /// or with the + /// method with . + /// /// By default, this is false. /// /// true if all attributes should be private /// the same builder IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); - /// - /// Sets the interval for background polling. - /// + /// + /// Sets the interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. + /// /// the background polling interval /// the same builder IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); @@ -63,7 +67,7 @@ public interface IConfigurationBuilder ///
/// /// The additional information will then be available through the client's "detail" - /// methods such as . Since this + /// methods such as . Since this /// increases the size of network requests, such information is not sent unless you set this option /// to true. /// @@ -94,18 +98,6 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval); - /// - /// Enables event sampling if non-zero. - /// - /// - /// When set to the default of zero, all analytics events are sent back to LaunchDarkly. When greater - /// than zero, there is a 1 in EventSamplingInterval chance that events will be sent (example: - /// if the interval is 20, on average 5% of events will be sent). - /// - /// the sampling interval - /// the same builder - IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval); - /// /// Sets the base URL of the LaunchDarkly analytics event server. /// @@ -147,9 +139,13 @@ public interface IConfigurationBuilder /// true if the streaming API should be used /// the same builder IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); + /// - /// + /// Sets the key for your LaunchDarkly environment. /// + /// + /// This should be the "mobile key" field for the environment on your LaunchDarkly dashboard. + /// /// /// the same builder IConfigurationBuilder MobileKey(string mobileKey); @@ -163,8 +159,11 @@ public interface IConfigurationBuilder /// /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be - /// immediately available the next time the SDK is started for the same user. The default is . + /// immediately available the next time the SDK is started for the same user. /// + /// + /// The default is true. + /// /// true to save flag values /// the same Configuration instance /// the same builder @@ -181,16 +180,18 @@ public interface IConfigurationBuilder IConfigurationBuilder PollingInterval(TimeSpan pollingInterval); /// - /// Marks an attribute name as private. + /// Marks an attribute name as private for all users. /// /// /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name - /// removed, even if you did not use the AndPrivate... methods on the object. + /// removed, even if you did not use the + /// method in . + /// /// You may call this method repeatedly to mark multiple attributes as private. /// - /// the attribute name + /// the attribute name /// the same builder - IConfigurationBuilder PrivateAttribute(string privateAtributeName); + IConfigurationBuilder PrivateAttribute(string privateAttributeName); /// /// Sets the timeout when reading data from the streaming connection. @@ -221,8 +222,13 @@ public interface IConfigurationBuilder IConfigurationBuilder StreamUri(Uri streamUri); /// - /// Determines whether to use the REPORT method for networking requests. + /// Sets whether to use the HTTP REPORT method for feature flag requests. /// + /// + /// By default, polling and streaming connections are made with the GET method, witht the user data + /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. + /// However, some network gateways do not support REPORT. + /// /// whether to use REPORT mode /// the same builder IConfigurationBuilder UseReport(bool useReport); @@ -249,7 +255,7 @@ public interface IConfigurationBuilder IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval); } - internal class ConfigurationBuilder : IConfigurationBuilder + internal sealed class ConfigurationBuilder : IConfigurationBuilder { private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationBuilder)); @@ -261,7 +267,6 @@ internal class ConfigurationBuilder : IConfigurationBuilder internal bool _evaluationReasons = false; internal int _eventCapacity = Configuration.DefaultEventCapacity; internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; - internal int _eventSamplingInterval = 0; internal Uri _eventsUri = Configuration.DefaultEventsUri; internal HttpClientHandler _httpClientHandler = new HttpClientHandler(); internal TimeSpan _httpClientTimeout = Configuration.DefaultHttpClientTimeout; @@ -303,7 +308,6 @@ internal ConfigurationBuilder(Configuration copyFrom) _evaluationReasons = copyFrom.EvaluationReasons; _eventCapacity = copyFrom.EventCapacity; _eventFlushInterval = copyFrom.EventFlushInterval; - _eventSamplingInterval = copyFrom.EventSamplingInterval; _eventsUri = copyFrom.EventsUri; _httpClientHandler = copyFrom.HttpClientHandler; _httpClientTimeout = copyFrom.HttpClientTimeout; @@ -384,12 +388,6 @@ public IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) return this; } - public IConfigurationBuilder EventSamplingInterval(int eventSamplingInterval) - { - _eventSamplingInterval = eventSamplingInterval; - return this; - } - public IConfigurationBuilder EventsUri(Uri eventsUri) { _eventsUri = eventsUri; diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 7a8d6e59..043f854e 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -72,8 +72,8 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration return new NullEventProcessor(); } - HttpClient httpClient = Util.MakeHttpClient(configuration, MobileClientEnvironment.Instance); - return new DefaultEventProcessor(configuration, null, httpClient, Constants.EVENTS_PATH); + HttpClient httpClient = Util.MakeHttpClient(configuration.HttpRequestConfiguration, MobileClientEnvironment.Instance); + return new DefaultEventProcessor(configuration.EventProcessorConfiguration, null, httpClient, Constants.EVENTS_PATH); } internal static IPersistentStorage CreatePersistentStorage(Configuration configuration) diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index 832b90b2..7e5afaa5 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -35,15 +35,15 @@ internal class FeatureFlagRequestor : IFeatureFlagRequestor private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagRequestor)); private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); - private readonly IMobileConfiguration _configuration; + private readonly Configuration _configuration; private readonly User _currentUser; private readonly HttpClient _httpClient; private volatile EntityTagHeaderValue _etag; - internal FeatureFlagRequestor(IMobileConfiguration configuration, User user) + internal FeatureFlagRequestor(Configuration configuration, User user) { this._configuration = configuration; - this._httpClient = Util.MakeHttpClient(configuration, MobileClientEnvironment.Instance); + this._httpClient = Util.MakeHttpClient(configuration.HttpRequestConfiguration, MobileClientEnvironment.Instance); this._currentUser = user; } diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 3635d21a..b59f53e9 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -55,11 +55,8 @@ public class FlagChangedEventArgs internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) { Key = key; - // Note that we're calling the ImmutableJsonValue constructor directly instead of using FromJToken(), - // because this is an internal value that we know we will not be modifying even if it is mutable. - // ImmutableJsonValue will take care of deep-copying the value if the application requests it. - NewValue = new ImmutableJsonValue(newValue); - OldValue = new ImmutableJsonValue(oldValue); + NewValue = ImmutableJsonValue.FromSafeValue(newValue); + OldValue = ImmutableJsonValue.FromSafeValue(oldValue); FlagWasDeleted = flagWasDeleted; } } diff --git a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs rename to src/LaunchDarkly.XamarinSdk/ILdClient.cs index 983034e4..f14c2cc6 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdMobileClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -1,20 +1,30 @@ using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; -using LaunchDarkly.Common; using System.Threading.Tasks; using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { - public interface ILdMobileClient : ILdCommonClient + public interface ILdClient : IDisposable { + /// + /// Returns the current version number of the LaunchDarkly client. + /// + Version Version { get; } + /// /// Tests whether the client is ready to be used. /// /// true if the client is ready, or false if it is still initializing bool Initialized(); + /// + /// Tests whether the client is being used in offline mode. + /// + /// true if the client is offline + bool IsOffline(); + /// /// Returns the boolean value of a feature flag for a given flag key. /// @@ -228,5 +238,10 @@ public interface ILdMobileClient : ILdCommonClient /// /// the user to register Task IdentifyAsync(User user); + + /// + /// Flushes all pending events. + /// + void Flush(); } } diff --git a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs b/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs deleted file mode 100644 index 572d808f..00000000 --- a/src/LaunchDarkly.XamarinSdk/IMobileConfiguration.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using LaunchDarkly.Common; - -namespace LaunchDarkly.Xamarin -{ - public interface IMobileConfiguration : IBaseConfiguration - { - /// - /// The interval between feature flag updates when the app is running in the background. - /// - TimeSpan BackgroundPollingInterval { get; } - - /// - /// When streaming mode is disabled, this is the interval between feature flag updates. - /// - TimeSpan PollingInterval { get; } - - /// - /// The connection timeout to the LaunchDarkly server. - /// - TimeSpan ConnectionTimeout { get; } - - /// - /// Whether to enable feature flag updates when the app is running in the background. - /// - bool EnableBackgroundUpdating { get; } - - /// - /// The time between flushes of the event buffer. Decreasing the flush interval means - /// that the event buffer is less likely to reach capacity. The default value is 5 seconds. - /// - TimeSpan EventFlushInterval { get; } - - /// - /// Whether to enable real-time streaming flag updates. When false, feature flags are updated via polling. - /// - bool IsStreamingEnabled { get; } - - /// - /// Whether to use the REPORT HTTP verb when fetching flags from LaunchDarkly. - /// - bool UseReport { get; } - - /// - /// True if LaunchDarkly should provide additional information about how flag values were - /// calculated. The additional information will then be available through the client's "detail" - /// methods such as . Since this - /// increases the size of network requests, such information is not sent unless you set this option - /// to true. - /// - bool EvaluationReasons { get; } - - /// - /// True if the SDK should save flag values for each user in persistent storage, so they will be - /// immediately available the next time the SDK is started for the same user. This is true by - /// default; set it to false to disable this behavior. - /// - /// - /// The implementation of persistent storage depends on the target platform. In Android and iOS, it - /// uses the standard user preferences mechanism. In .NET Standard, it uses the IsolatedStorageFile - /// API, which stores file data under the current account's home directory at - /// ~/.local/share/IsolateStorage/. - /// - bool PersistFlagValues { get; } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index fe5d710a..39ffe9a3 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 0f5cd331..69b01990 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -15,7 +15,7 @@ namespace LaunchDarkly.Xamarin /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate /// a single LdClient for the lifetime of their application. /// - public sealed class LdClient : ILdMobileClient + public sealed class LdClient : ILdClient { private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); @@ -432,10 +432,7 @@ public IDictionary AllFlags() } return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => new ImmutableJsonValue(p.Value.value)); - // Note that we are calling the ImmutableJsonValue constructor directly instead of using FromJToken() - // because we do not need it to deep-copy mutable values immediately - we know that *we* won't be - // modifying those values. It will deep-copy them if and when the application tries to access them. + .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); } /// @@ -447,7 +444,7 @@ public void Track(string eventName, ImmutableJsonValue data) /// public void Track(string eventName) { - Track(eventName, ImmutableJsonValue.FromJToken(null)); + Track(eventName, ImmutableJsonValue.Null); } /// diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 47a66423..5b21b4a8 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -16,12 +16,12 @@ internal class MobileStreamingProcessor : IMobileUpdateProcessor, IStreamProcess private static readonly ILog Log = LogManager.GetLogger(typeof(MobileStreamingProcessor)); private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); - private readonly IMobileConfiguration _configuration; + private readonly Configuration _configuration; private readonly IFlagCacheManager _cacheManager; private readonly User _user; private readonly StreamManager _streamManager; - internal MobileStreamingProcessor(IMobileConfiguration configuration, + internal MobileStreamingProcessor(Configuration configuration, IFlagCacheManager cacheManager, User user, StreamManager.EventSourceCreator eventSourceCreator) @@ -34,7 +34,7 @@ internal MobileStreamingProcessor(IMobileConfiguration configuration, _streamManager = new StreamManager(this, streamProperties, - _configuration, + _configuration.StreamManagerConfiguration, MobileClientEnvironment.Instance, eventSourceCreator); } diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs index fc20b299..b8bd3f83 100644 --- a/src/LaunchDarkly.XamarinSdk/ValueType.cs +++ b/src/LaunchDarkly.XamarinSdk/ValueType.cs @@ -75,11 +75,8 @@ private static ArgumentException BadTypeException() public static ValueType Json = new ValueType { - ValueFromJson = json => new ImmutableJsonValue(json), + ValueFromJson = json => ImmutableJsonValue.FromSafeValue(json), ValueToJson = value => value.AsJToken() - // Note that we are calling the ImmutableJsonValue constructor directly instead of using FromJToken() - // because we do not need it to deep-copy mutable values immediately - we know that *we* won't be - // modifying those values. It will deep-copy them if and when the application tries to access them. }; } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index a830a66f..becc9d28 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -374,7 +374,7 @@ private void VerifyRequest(FluentMockServer server, UpdateMode mode) Assert.Null(req.Body); } - private void VerifyFlagValues(ILdMobileClient client, IDictionary flags) + private void VerifyFlagValues(ILdClient client, IDictionary flags) { Assert.True(client.Initialized()); foreach (var e in flags) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 90d7d771..cec59879 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 6929cb19..94b3e301 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -167,7 +167,7 @@ public void JsonVariationReturnsValue() string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.FromJToken(3)).AsJToken()); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.Of(3)).AsJToken()); } } @@ -176,7 +176,7 @@ public void JsonVariationReturnsDefaultForUnknownFlag() { using (var client = ClientWithFlagsJson("{}")) { - var defaultVal = ImmutableJsonValue.FromJToken(3); + var defaultVal = ImmutableJsonValue.Of(3); Assert.Equal(defaultVal, client.JsonVariation(nonexistentFlagKey, defaultVal)); } } @@ -190,8 +190,7 @@ public void JsonVariationDetailReturnsValue() using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(jsonValue, 1, reason); - var result = client.JsonVariationDetail("flag-key", ImmutableJsonValue.FromJToken(3)); - // Note, JToken.Equals() doesn't work, so we need to test each property separately + var result = client.JsonVariationDetail("flag-key", ImmutableJsonValue.Of(3)); Assert.True(JToken.DeepEquals(expected.Value, result.Value.AsJToken())); Assert.Equal(expected.VariationIndex, result.VariationIndex); Assert.Equal(expected.Reason, result.Reason); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 7f86bf55..db856cc6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -35,7 +35,7 @@ public void TrackSendsCustomEvent() using (LdClient client = MakeClient(user, "{}")) { JToken data = new JValue("hi"); - client.Track("eventkey", ImmutableJsonValue.FromJToken(data)); + client.Track("eventkey", ImmutableJsonValue.Of(data)); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { From 8fa98c7a5ad19fe0535e9adcbea5ccd997201ab4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 5 Aug 2019 12:40:51 -0700 Subject: [PATCH 215/499] make more things sealed and/or internal --- .../DefaultDeviceInfo.cs | 2 +- .../DefaultPersistentStorage.cs | 2 +- src/LaunchDarkly.XamarinSdk/Extensions.cs | 2 +- src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 2 +- .../FeatureFlagRequestor.cs | 17 +++++++---------- src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs | 2 +- src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs | 4 ++-- .../IMobileUpdateProcessor.cs | 2 +- src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs | 2 +- .../MobileConnectionManager.cs | 2 +- .../MobilePollingProcessor.cs | 2 +- .../MobileSideClientEnvironment.cs | 2 +- .../MobileStreamingProcessor.cs | 2 +- .../UserFlagDeviceCache.cs | 2 +- .../UserFlagInMemoryCache.cs | 2 +- src/LaunchDarkly.XamarinSdk/ValueType.cs | 2 +- tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 2 +- 17 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs index 704f5523..c46e50a4 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Xamarin { // This just delegates to the conditionally-compiled code in LaunchDarkly.Xamarin.PlatformSpecific. // The only reason it is a pluggable component is for unit tests; we don't currently expose IDeviceInfo. - public class DefaultDeviceInfo : IDeviceInfo + internal sealed class DefaultDeviceInfo : IDeviceInfo { public string UniqueDeviceId() { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs index 37af6cdf..175ddab9 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs @@ -2,7 +2,7 @@ namespace LaunchDarkly.Xamarin { - internal class DefaultPersistentStorage : IPersistentStorage + internal sealed class DefaultPersistentStorage : IPersistentStorage { public void Save(string key, string value) { diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs index 07d8e1ac..44562aba 100644 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ b/src/LaunchDarkly.XamarinSdk/Extensions.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Xamarin { - public static class Extensions + internal static class Extensions { public static string Base64Encode(this string plainText) { diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index b6b67d5c..ef0a1647 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Xamarin { - public class FeatureFlag : IEquatable + internal sealed class FeatureFlag : IEquatable { public JToken value; public int version; diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index 7e5afaa5..dd840484 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -30,7 +30,7 @@ internal interface IFeatureFlagRequestor : IDisposable Task FeatureFlagsAsync(); } - internal class FeatureFlagRequestor : IFeatureFlagRequestor + internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor { private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagRequestor)); private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); @@ -121,19 +121,16 @@ private async Task MakeRequest(HttpRequestMessage request) } } - protected virtual void Dispose(bool disposing) + // Sealed, non-derived class should implement Dispose() and finalize method, not Dispose(boolean) + public void Dispose() { - if (disposing) - { - _httpClient.Dispose(); - } + _httpClient.Dispose(); + GC.SuppressFinalize(this); } - // This code added to correctly implement the disposable pattern. - void IDisposable.Dispose() + ~FeatureFlagRequestor() { - Dispose(true); - GC.SuppressFinalize(this); + Dispose(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index a10d2bbc..af614883 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -6,7 +6,7 @@ namespace LaunchDarkly.Xamarin { - internal class FlagCacheManager : IFlagCacheManager + internal sealed class FlagCacheManager : IFlagCacheManager { private readonly IUserFlagCache inMemoryCache; private readonly IUserFlagCache deviceCache; diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index b59f53e9..1dec8f2d 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -9,7 +9,7 @@ namespace LaunchDarkly.Xamarin /// /// An event object that is sent to handlers for the event. /// - public class FlagChangedEventArgs + public sealed class FlagChangedEventArgs { /// /// The unique key of the feature flag whose value has changed. @@ -68,7 +68,7 @@ internal interface IFlagChangedEventManager void FlagWasUpdated(string flagKey, JToken newValue, JToken oldValue); } - internal class FlagChangedEventManager : IFlagChangedEventManager + internal sealed class FlagChangedEventManager : IFlagChangedEventManager { private static readonly ILog Log = LogManager.GetLogger(typeof(IFlagChangedEventManager)); diff --git a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs index 925576fc..cbe0c73b 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs @@ -26,7 +26,7 @@ internal interface IMobileUpdateProcessor : IDisposable /// /// Used when the client is offline or in LDD mode. /// - internal class NullUpdateProcessor : IMobileUpdateProcessor + internal sealed class NullUpdateProcessor : IMobileUpdateProcessor { Task IMobileUpdateProcessor.Start() { diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs index 99211ba4..2cecb540 100644 --- a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs +++ b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs @@ -9,7 +9,7 @@ internal interface IUserFlagCache IDictionary RetrieveFlags(User user); } - internal class NullUserFlagCache : IUserFlagCache + internal sealed class NullUserFlagCache : IUserFlagCache { public void CacheFlagsForUser(IDictionary flags, User user) { } public IDictionary RetrieveFlags(User user) => null; diff --git a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs index 0de11a80..b576892a 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin { - internal class MobileConnectionManager : IConnectionManager + internal sealed class MobileConnectionManager : IConnectionManager { internal Action ConnectionChanged; diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index 44668ae5..a76552f3 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -9,7 +9,7 @@ namespace LaunchDarkly.Xamarin { - internal class MobilePollingProcessor : IMobileUpdateProcessor + internal sealed class MobilePollingProcessor : IMobileUpdateProcessor { private static readonly ILog Log = LogManager.GetLogger(typeof(MobilePollingProcessor)); diff --git a/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs b/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs index f786d3c6..4a519a98 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin { - internal class MobileClientEnvironment : ClientEnvironment + internal sealed class MobileClientEnvironment : ClientEnvironment { internal static readonly MobileClientEnvironment Instance = new MobileClientEnvironment(); diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 5b21b4a8..89490a80 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Xamarin { - internal class MobileStreamingProcessor : IMobileUpdateProcessor, IStreamProcessor + internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor, IStreamProcessor { private static readonly ILog Log = LogManager.GetLogger(typeof(MobileStreamingProcessor)); private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs index 29e616e6..a5c83b62 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs @@ -7,7 +7,7 @@ namespace LaunchDarkly.Xamarin { - internal class UserFlagDeviceCache : IUserFlagCache + internal sealed class UserFlagDeviceCache : IUserFlagCache { private static readonly ILog Log = LogManager.GetLogger(typeof(UserFlagDeviceCache)); private readonly IPersistentStorage persister; diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs index e9c9c553..2ebd2ce6 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Xamarin { - internal class UserFlagInMemoryCache : IUserFlagCache + internal sealed class UserFlagInMemoryCache : IUserFlagCache { // A map of the key (user.Key) and their featureFlags readonly ConcurrentDictionary JSONMap = diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs index b8bd3f83..4222f1ff 100644 --- a/src/LaunchDarkly.XamarinSdk/ValueType.cs +++ b/src/LaunchDarkly.XamarinSdk/ValueType.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Xamarin { - internal class ValueType + internal sealed class ValueType { public Func ValueFromJson { get; internal set; } public Func ValueToJson { get; internal set; } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 208f9a25..ca678113 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -128,7 +128,7 @@ public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? return JsonConvert.SerializeObject(o); } - public static IDictionary DecodeFlagsJson(string flagsJson) + internal static IDictionary DecodeFlagsJson(string flagsJson) { return JsonConvert.DeserializeObject>(flagsJson); } From 827243bdfd8925e34c3fbff8d9bacc359ce7cf3b Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 5 Aug 2019 21:26:11 -0700 Subject: [PATCH 216/499] Removed unused locks, simplified all flags to match Android behavior, changed variation internal to correct behavior and had to change unit test --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 71 +++++++------------ .../LDClientEndToEndTests.cs | 2 +- 2 files changed, 28 insertions(+), 45 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 0f5cd331..cfa923d0 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -25,9 +25,6 @@ public sealed class LdClient : ILdMobileClient static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; - - readonly object _myLockObjForConnectionChange = new object(); - readonly object _myLockObjForUserUpdate = new object(); readonly Configuration _config; readonly SemaphoreSlim _connectionLock; @@ -74,32 +71,32 @@ public bool Online { var doNotAwaitResult = SetOnlineAsync(value); } - } - - /// - /// Indicates which platform the SDK is built for. - /// - /// - /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, - /// but rather which variant of the SDK is currently in use. - /// - /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android - /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done - /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard - /// variant. - /// - /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific - /// behavior such as detecting when an application has gone into the background, detecting network connectivity, - /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find - /// that these platform-specific behaviors are not working correctly, you may want to check this property to - /// make sure you are not for some reason running the .NET Standard SDK on a phone. - /// + } + + /// + /// Indicates which platform the SDK is built for. + /// + /// + /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, + /// but rather which variant of the SDK is currently in use. + /// + /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android + /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done + /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard + /// variant. + /// + /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific + /// behavior such as detecting when an application has gone into the background, detecting network connectivity, + /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find + /// that these platform-specific behaviors are not working correctly, you may want to check this property to + /// make sure you are not for some reason running the .NET Standard SDK on a phone. + /// public static PlatformType PlatformType - { - get - { - return PlatformSpecific.UserMetadata.PlatformType; - } + { + get + { + return PlatformSpecific.UserMetadata.PlatformType; + } } // private constructor prevents initialization of this class @@ -374,7 +371,7 @@ EvaluationDetail VariationInternal(string featureKey, T defaultValue, Valu EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); - if (!Initialized()) + if (flagCacheManager is null || eventProcessor is null) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); @@ -420,22 +417,8 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => /// public IDictionary AllFlags() { - if (IsOffline()) - { - Log.Warn("AllFlags() was called when client is in offline mode. Returning null."); - return null; - } - if (!Initialized()) - { - Log.Warn("AllFlags() was called before client has finished initializing. Returning null."); - return null; - } - return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => new ImmutableJsonValue(p.Value.value)); - // Note that we are calling the ImmutableJsonValue constructor directly instead of using FromJToken() - // because we do not need it to deep-copy mutable values immediately - we know that *we* won't be - // modifying those values. It will deep-copy them if and when the application tries to access them. + .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); } /// diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index a830a66f..382f0d8b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -86,7 +86,7 @@ public void InitCanTimeOutSync() using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized()); - Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + Assert.Equal("value1", client.StringVariation(_flagData1.First().Key, null)); Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && m.Text == "Client did not successfully initialize within 200 milliseconds."); } From 3a7bb49d18c6362ce1e752bf55bc3f0a02c26d7a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 6 Aug 2019 13:35:02 -0700 Subject: [PATCH 217/499] bump CommonSdk version --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 39ffe9a3..3341e1df 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index cec59879..733625e8 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From b7b4e9f151d9d7e9d948cfa6ccadd12675df8ce4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 6 Aug 2019 13:44:54 -0700 Subject: [PATCH 218/499] 1.0.0-beta20 --- CHANGELOG.md | 25 +++++++++++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72aa6537..a978d1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.0.0-beta20] - 2019-08-06 +### Added: +- `Configuration.Builder` provides a fluent builder pattern for constructing `Configuration` objects. This is now the only method for building a configuration if you want to set properties other than the SDK key. +- `ImmutableJsonValue.Null` (equivalent to `ImmutableJsonValue.Of(null)`). +- `LdClient.PlatformType`. +- Verbose debug logging for stream connections. + +### Changed: +- `Configuration` objects are now immutable. +- In `Configuration`, `EventQueueCapacity` and `EventQueueFrency` have been renamed to `EventCapacity` and `EventFlushInterval`, for consistency with other LaunchDarkly SDKs. +- `ImmutableJsonValue.FromJToken()` was renamed to `ImmutableJsonValue.Of()`. +- In `FlagChangedEventArgs`, `NewValue` and `OldValue` now have the type `ImmutableJsonValue` instead of `JToken`. +- `ILdMobileClient` is now named `ILdClient`. + +### Fixed: +- Fixed a bug where setting a user's custom attribute to a null value could cause an exception during JSON serialization of event data. + +### Removed: +- `ConfigurationExtensions` (use `Configuration.Builder`). +- `Configuration.SamplingInterval`. +- `UserExtensions` (use `User.Builder`). +- `User` constructors (use `User.WithKey` or `User.Builder`). +- `User` property setters. +- `IBaseConfiguration` and `ICommonLdClient` interfaces. + ## [1.0.0-beta19] - 2019-07-31 ### Added: - `User.Builder` provides a fluent builder pattern for constructing `User` objects. This is now the only method for building a user if you want to set properties other than `Key`. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 3341e1df..2407f099 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -4,7 +4,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta19 + 1.0.0-beta20 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From 94a88b03b0f8a16912066d8e093804ea4b16104d Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 6 Aug 2019 14:36:13 -0700 Subject: [PATCH 219/499] Added streaming mode check when entering background, added unit tests for backgrounding offline behavior, removed redundant class names in BackgroundDetection.android --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 7 +- .../BackgroundDetection.android.cs | 4 +- .../LDClientEndToEndTests.cs | 64 +++++++++++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 69b01990..aa925e75 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -497,7 +497,7 @@ public async Task IdentifyAsync(User user) bool StartUpdateProcessor(TimeSpan maxWaitTime) { - if (Online) + if (Online && !Config.Offline) return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); else return true; @@ -505,7 +505,7 @@ bool StartUpdateProcessor(TimeSpan maxWaitTime) Task StartUpdateProcessorAsync() { - if (Online) + if (Online && !Config.Offline) return updateProcessor.Start(); else return Task.FromResult(true); @@ -632,7 +632,8 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { Log.Debug("Background updating is disabled"); } - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); + if (Config.IsStreamingEnabled) + persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } else { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs index 3412324e..a3ddc5d1 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs @@ -35,12 +35,12 @@ public void OnActivityDestroyed(Activity activity) public void OnActivityPaused(Activity activity) { - BackgroundDetection.UpdateBackgroundMode(true); + UpdateBackgroundMode(true); } public void OnActivityResumed(Activity activity) { - BackgroundDetection.UpdateBackgroundMode(false); + UpdateBackgroundMode(false); } public void OnActivitySaveInstanceState(Activity activity, Bundle outState) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index becc9d28..bc3b1cfe 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -340,6 +340,70 @@ await WithServerAsync(async server => }); } + [Fact(Skip = SkipIfCannotCreateHttpServer)] + public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() + { + WithServer(server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); + using (var client = TestUtil.CreateClient(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + } + }); + } + + [Fact(Skip = SkipIfCannotCreateHttpServer)] + public async Task BackgroundOfflineClientUsesCachedFlagsAsyncAfterStartUpdateProcessorAsync() + { + await WithServerAsync(async server => + { + SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + + var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. + + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + }); + } + private IConfigurationBuilder BaseConfig(FluentMockServer server) { return Configuration.BuilderInternal(_mobileKey) From 0268dd326323aacd7b0845bfe8bfd26185620caf Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 6 Aug 2019 15:06:41 -0700 Subject: [PATCH 220/499] 1.0.0-beta21 --- CHANGELOG.md | 5 ++++- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 4 ++-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a978d1bf..12e0ed1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). -## [1.0.0-beta20] - 2019-08-06 +## [1.0.0-beta21] - 2019-08-06 ### Added: - `Configuration.Builder` provides a fluent builder pattern for constructing `Configuration` objects. This is now the only method for building a configuration if you want to set properties other than the SDK key. - `ImmutableJsonValue.Null` (equivalent to `ImmutableJsonValue.Of(null)`). @@ -28,6 +28,9 @@ This project adheres to [Semantic Versioning](http://semver.org). - `User` property setters. - `IBaseConfiguration` and `ICommonLdClient` interfaces. +## [1.0.0-beta20] - 2019-08-06 +Incomplete release, replaced by beta21. + ## [1.0.0-beta19] - 2019-07-31 ### Added: - `User.Builder` provides a fluent builder pattern for constructing `User` objects. This is now the only method for building a user if you want to set properties other than `Key`. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 2407f099..e26f1498 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -4,7 +4,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta20 + 1.0.0-beta21 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk @@ -24,7 +24,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 733625e8..118cb7d8 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From da45bd4acc21a6b79dbe6dc178d0f9698f897c8e Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 6 Aug 2019 16:46:47 -0700 Subject: [PATCH 221/499] Removed redundant config online check in start update processor, removed persister BACKGROUNDED_WHILE_STREAMING in favor of static var --- src/LaunchDarkly.XamarinSdk/Constants.cs | 1 - src/LaunchDarkly.XamarinSdk/LdClient.cs | 15 +-- .../Resources/Resource.designer.cs | 101 ------------------ 3 files changed, 8 insertions(+), 109 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Constants.cs b/src/LaunchDarkly.XamarinSdk/Constants.cs index 16d2a154..cdea0913 100644 --- a/src/LaunchDarkly.XamarinSdk/Constants.cs +++ b/src/LaunchDarkly.XamarinSdk/Constants.cs @@ -21,6 +21,5 @@ internal static class Constants public const string PING = "ping"; public const string EVENTS_PATH = "/mobile/events/bulk"; public const string UNIQUE_ID_KEY = "unique_id_key"; - public const string BACKGROUNDED_WHILE_STREAMING = "didEnterBackgroundKey"; } } diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index aa925e75..56c4dcaf 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -22,6 +22,8 @@ public sealed class LdClient : ILdClient static volatile LdClient _instance; static volatile User _user; + static bool backgroundedWhileStreaming; + static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; @@ -497,7 +499,7 @@ public async Task IdentifyAsync(User user) bool StartUpdateProcessor(TimeSpan maxWaitTime) { - if (Online && !Config.Offline) + if (Online) return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); else return true; @@ -505,7 +507,7 @@ bool StartUpdateProcessor(TimeSpan maxWaitTime) Task StartUpdateProcessorAsync() { - if (Online && !Config.Offline) + if (Online) return updateProcessor.Start(); else return Task.FromResult(true); @@ -633,7 +635,7 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh Log.Debug("Background updating is disabled"); } if (Config.IsStreamingEnabled) - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); + backgroundedWhileStreaming = true; } else { @@ -644,10 +646,9 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh void ResetProcessorForForeground() { - string didBackground = persister.GetValue(Constants.BACKGROUNDED_WHILE_STREAMING); - if (didBackground != null && didBackground.Equals("true")) - { - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "false"); + if (backgroundedWhileStreaming) + { + backgroundedWhileStreaming = false; ClearUpdateProcessor(); _disableStreaming = false; } diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs index bdd99699..f352a1ff 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -26,107 +26,6 @@ static Resource() public static void UpdateIdValues() { - global::LaunchDarkly.XamarinSdk.Resource.Attribute.font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.font; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderCerts; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderPackage; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderQuery; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontWeight; - global::LaunchDarkly.XamarinSdk.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::LaunchDarkly.XamarinSdk.Resource.Color.notification_action_color_filter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_action_color_filter; - global::LaunchDarkly.XamarinSdk.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_icon_bg_color; - global::LaunchDarkly.XamarinSdk.Resource.Color.ripple_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_light; - global::LaunchDarkly.XamarinSdk.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_text_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_subtext_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_action_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_action_background; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_icon_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_icon_background; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_tile_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_container; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_divider; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_image; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_text; - global::LaunchDarkly.XamarinSdk.Resource.Id.actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.actions; - global::LaunchDarkly.XamarinSdk.Resource.Id.async = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.async; - global::LaunchDarkly.XamarinSdk.Resource.Id.blocking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.blocking; - global::LaunchDarkly.XamarinSdk.Resource.Id.chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.chronometer; - global::LaunchDarkly.XamarinSdk.Resource.Id.forever = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.forever; - global::LaunchDarkly.XamarinSdk.Resource.Id.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon; - global::LaunchDarkly.XamarinSdk.Resource.Id.icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon_group; - global::LaunchDarkly.XamarinSdk.Resource.Id.info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.info; - global::LaunchDarkly.XamarinSdk.Resource.Id.italic = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.italic; - global::LaunchDarkly.XamarinSdk.Resource.Id.line1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line1; - global::LaunchDarkly.XamarinSdk.Resource.Id.line3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line3; - global::LaunchDarkly.XamarinSdk.Resource.Id.normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.normal; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_background; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column_container; - global::LaunchDarkly.XamarinSdk.Resource.Id.right_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_icon; - global::LaunchDarkly.XamarinSdk.Resource.Id.right_side = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_side; - global::LaunchDarkly.XamarinSdk.Resource.Id.tag_transition_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tag_transition_group; - global::LaunchDarkly.XamarinSdk.Resource.Id.text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text; - global::LaunchDarkly.XamarinSdk.Resource.Id.text2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text2; - global::LaunchDarkly.XamarinSdk.Resource.Id.time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.time; - global::LaunchDarkly.XamarinSdk.Resource.Id.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title; - global::LaunchDarkly.XamarinSdk.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action_tombstone; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_custom_big; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_icon_group; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_time; - global::LaunchDarkly.XamarinSdk.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; From dbba4668139bfe79145f0f88004eb7f4bf7a1976 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 6 Aug 2019 17:29:33 -0700 Subject: [PATCH 222/499] Simplified backgroundedWhileStreaming, made backgroundedWhileStreaming private not static, added braces to if --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 56c4dcaf..4de32752 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -22,7 +22,7 @@ public sealed class LdClient : ILdClient static volatile LdClient _instance; static volatile User _user; - static bool backgroundedWhileStreaming; + private bool backgroundedWhileStreaming; static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; @@ -634,24 +634,20 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { Log.Debug("Background updating is disabled"); } - if (Config.IsStreamingEnabled) - backgroundedWhileStreaming = true; + if (Config.IsStreamingEnabled) + { + backgroundedWhileStreaming = true; + } } else { - ResetProcessorForForeground(); + if (backgroundedWhileStreaming) + { + backgroundedWhileStreaming = false; + _disableStreaming = false; + } await RestartUpdateProcessorAsync(Config.PollingInterval); } } - - void ResetProcessorForForeground() - { - if (backgroundedWhileStreaming) - { - backgroundedWhileStreaming = false; - ClearUpdateProcessor(); - _disableStreaming = false; - } - } } } From 90555501c3ed2d271120f917370c24df8005d4ff Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Tue, 6 Aug 2019 17:35:34 -0700 Subject: [PATCH 223/499] Remove unnecessary backgroundedWhileStreaming, always reset _disableStreaming to false on foreground --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 4de32752..017e1fa5 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -22,8 +22,6 @@ public sealed class LdClient : ILdClient static volatile LdClient _instance; static volatile User _user; - private bool backgroundedWhileStreaming; - static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; @@ -634,18 +632,10 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { Log.Debug("Background updating is disabled"); } - if (Config.IsStreamingEnabled) - { - backgroundedWhileStreaming = true; - } } else { - if (backgroundedWhileStreaming) - { - backgroundedWhileStreaming = false; - _disableStreaming = false; - } + _disableStreaming = false; await RestartUpdateProcessorAsync(Config.PollingInterval); } } From 70389ca3938808db36e3ae806101231ca29d9e97 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Fri, 9 Aug 2019 14:23:38 -0700 Subject: [PATCH 224/499] Changed Initialized behavior to new spec, added testing, changed comment explanation for Initialized --- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 14 +++++- src/LaunchDarkly.XamarinSdk/LdClient.cs | 46 ++++++++++++++----- .../LDClientEndToEndTests.cs | 12 ++++- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index f14c2cc6..706b1090 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -14,9 +14,19 @@ public interface ILdClient : IDisposable Version Version { get; } /// - /// Tests whether the client is ready to be used. + /// When you first start the client, once Init or InitAsync has returned, + /// Initialized() should be true if and only if either 1. it connected to LaunchDarkly + /// and successfully retrieved flags, or 2. it started in offline mode so + /// there's no need to connect to LaunchDarkly. So if the client timed out trying to + /// connect to LD, then Initialized is false (even if we do have cached flags). + /// If the client connected and got a 401 error, Initialized is false. + /// This serves the purpose of letting the app know that + /// there was a problem of some kind. Initialized() will be temporarily false during the + /// time in between calling Identify and receiving the new user's flags. There is one case where + /// Initialized() could become false: if you switch users with Identify and the client is unable + /// to get the new user's flags from LauncDarkly. /// - /// true if the client is ready, or false if it is still initializing + /// true if the client has connected to LaunchDarkly and has flags or if the config is set offline, or false if it couldn't connect. bool Initialized(); /// diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index c05af439..15fdd915 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -22,6 +22,8 @@ public sealed class LdClient : ILdClient static volatile LdClient _instance; static volatile User _user; + static bool initialized; + static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; @@ -37,7 +39,7 @@ public sealed class LdClient : ILdClient readonly IPersistentStorage persister; // These LdClient fields are not readonly because they change according to online status - volatile IMobileUpdateProcessor updateProcessor; + internal volatile IMobileUpdateProcessor updateProcessor; volatile bool _disableStreaming; volatile bool _online; @@ -95,7 +97,7 @@ public static PlatformType PlatformType { get { - return PlatformSpecific.UserMetadata.PlatformType; + return UserMetadata.PlatformType; } } @@ -114,6 +116,11 @@ public static PlatformType PlatformType _connectionLock = new SemaphoreSlim(1, 1); + if (configuration.Offline) + { + initialized = true; + } + persister = Factory.CreatePersistentStorage(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); @@ -371,12 +378,6 @@ EvaluationDetail VariationInternal(string featureKey, T defaultValue, Valu EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); - if (flagCacheManager is null || eventProcessor is null) - { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); - return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); - } - var flag = flagCacheManager.FlagForUser(featureKey, User); if (flag == null) { @@ -436,7 +437,7 @@ public void Track(string eventName) /// public bool Initialized() { - return Online && updateProcessor.Initialized(); + return initialized; } /// @@ -471,6 +472,7 @@ public async Task IdentifyAsync(User user) try { _user = newUser; + initialized = false; await RestartUpdateProcessorAsync(Config.PollingInterval); } finally @@ -484,17 +486,39 @@ public async Task IdentifyAsync(User user) bool StartUpdateProcessor(TimeSpan maxWaitTime) { if (Online) - return AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + { + var successfulConnection = AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + if (successfulConnection) + { + initialized = true; + } + else + { + initialized = false; + } + return successfulConnection; + } else + { return true; + } } Task StartUpdateProcessorAsync() { if (Online) - return updateProcessor.Start(); + { + var successfulConnection = updateProcessor.Start(); + if (successfulConnection.IsCompleted) + { + initialized = true; + } + return successfulConnection; + } else + { return Task.FromResult(true); + } } async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 5faa44f2..18fa5da4 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -85,6 +85,7 @@ public void InitCanTimeOutSync() var config = BaseConfig(server).IsStreamingEnabled(false).Build(); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { + Assert.False(Initialized(client)); Assert.False(client.Initialized()); Assert.Equal("value1", client.StringVariation(_flagData1.First().Key, null)); Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && @@ -139,6 +140,7 @@ await WithServerAsync(async server => // will complete successfully with an uninitialized client. using (var client = await TestUtil.CreateClientAsync(config, _user)) { + Assert.False(Initialized(client)); Assert.False(client.Initialized()); } } @@ -243,6 +245,7 @@ public void OfflineClientUsesCachedFlagsSync() using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); } // At this point the SDK should have written the flags to persistent storage for this user key. @@ -254,6 +257,7 @@ public void OfflineClientUsesCachedFlagsSync() using (var client = TestUtil.CreateClient(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); } }); } @@ -280,6 +284,7 @@ await WithServerAsync(async server => using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); } }); } @@ -340,6 +345,11 @@ await WithServerAsync(async server => }); } + private bool Initialized(LdClient client) + { + return client.Online && client.updateProcessor.Initialized(); + } + private IConfigurationBuilder BaseConfig(FluentMockServer server) { return Configuration.BuilderInternal(_mobileKey) @@ -376,7 +386,7 @@ private void VerifyRequest(FluentMockServer server, UpdateMode mode) private void VerifyFlagValues(ILdClient client, IDictionary flags) { - Assert.True(client.Initialized()); + Assert.True(Initialized((LdClient) client)); foreach (var e in flags) { Assert.Equal(e.Value, client.StringVariation(e.Key, null)); From 02fa10cd36b58262d1e395d5586ec26a0c5fedc9 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Fri, 9 Aug 2019 15:50:45 -0700 Subject: [PATCH 225/499] Fix merge bug with constant that doesn't exist anymore --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index a2eedc98..34cb3cca 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -642,7 +642,6 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { Log.Debug("Background updating is disabled"); } - persister.Save(Constants.BACKGROUNDED_WHILE_STREAMING, "true"); } else { From a0c6ae2421eaeb53fe9df81174e390f5aed58780 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Fri, 9 Aug 2019 16:48:47 -0700 Subject: [PATCH 226/499] Simplified initialized assignment, removed unnecssary static keyword --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 34cb3cca..6f6463dd 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -22,7 +22,7 @@ public sealed class LdClient : ILdClient static volatile LdClient _instance; static volatile User _user; - static bool initialized; + bool initialized; static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; @@ -488,14 +488,7 @@ bool StartUpdateProcessor(TimeSpan maxWaitTime) if (Online) { var successfulConnection = AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); - if (successfulConnection) - { - initialized = true; - } - else - { - initialized = false; - } + initialized = successfulConnection; return successfulConnection; } else From 6018077394f3e0ea17774166cb8989e1905e1bd9 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Fri, 9 Aug 2019 16:51:11 -0700 Subject: [PATCH 227/499] Improved Initialized comment in ILdClient --- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index 706b1090..ed8a251f 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -14,6 +14,9 @@ public interface ILdClient : IDisposable Version Version { get; } /// + /// Returns a boolean value indicating LaunchDarkly connection and flag state within the client. + /// + /// /// When you first start the client, once Init or InitAsync has returned, /// Initialized() should be true if and only if either 1. it connected to LaunchDarkly /// and successfully retrieved flags, or 2. it started in offline mode so @@ -22,10 +25,10 @@ public interface ILdClient : IDisposable /// If the client connected and got a 401 error, Initialized is false. /// This serves the purpose of letting the app know that /// there was a problem of some kind. Initialized() will be temporarily false during the - /// time in between calling Identify and receiving the new user's flags. There is one case where - /// Initialized() could become false: if you switch users with Identify and the client is unable - /// to get the new user's flags from LauncDarkly. - /// + /// time in between calling Identify and receiving the new user's flags. It will also be false + /// if you switch users with Identify and the client is unable + /// to get the new user's flags from LaunchDarkly. + /// /// true if the client has connected to LaunchDarkly and has flags or if the config is set offline, or false if it couldn't connect. bool Initialized(); From 4b8c8efc65732ecc4d8463b8a8ce0246f0bd18ae Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 12 Aug 2019 12:18:11 -0700 Subject: [PATCH 228/499] don't use default .NET HTTP handler (#67) --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 120 ++++++++++++------ .../ConfigurationBuilder.cs | 21 ++- .../LaunchDarkly.XamarinSdk.csproj | 2 +- .../PlatformSpecific/Http.android.cs | 11 ++ .../PlatformSpecific/Http.ios.cs | 10 ++ .../PlatformSpecific/Http.netstandard.cs | 9 ++ .../PlatformSpecific/Http.shared.cs | 25 ++++ .../ConfigurationTest.cs | 6 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientEventTests.cs | 2 +- .../Info.plist | 101 ++++++++------- .../Properties/AssemblyInfo.cs | 6 +- 12 files changed, 218 insertions(+), 97 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs create mode 100644 src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index e9deb58e..d3279706 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -26,7 +26,7 @@ public sealed class Configuration private readonly TimeSpan _eventFlushInterval; private readonly int _eventCapacity; private readonly Uri _eventsUri; - private readonly HttpClientHandler _httpClientHandler; + private readonly HttpMessageHandler _httpMessageHandler; private readonly TimeSpan _httpClientTimeout; private readonly bool _inlineUsersInEvents; private readonly bool _isStreamingEnabled; @@ -64,12 +64,12 @@ public sealed class Configuration /// public bool AllAttributesPrivate => _allAttributesPrivate; - /// - /// The interval between feature flag updates when the application is running in the background. - /// - /// - /// This is only relevant on mobile platforms. - /// + /// + /// The interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. + /// public TimeSpan BackgroundPollingInterval => _backgroundPollingInterval; /// @@ -77,17 +77,17 @@ public sealed class Configuration /// public Uri BaseUri => _baseUri; - /// - /// The connection timeout to the LaunchDarkly server. - /// + /// + /// The connection timeout to the LaunchDarkly server. + /// public TimeSpan ConnectionTimeout { get; internal set; } - /// - /// Whether to enable feature flag updates when the application is running in the background. - /// - /// - /// This is only relevant on mobile platforms. - /// + /// + /// Whether to enable feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. + /// public bool EnableBackgroundUpdating => _enableBackgroundUpdating; /// @@ -126,9 +126,9 @@ public sealed class Configuration public Uri EventsUri => _eventsUri; /// - /// The object to be used for sending HTTP requests. This is exposed for testing purposes. + /// The object to be used for sending HTTP requests, if a specific implementation is desired. /// - public HttpClientHandler HttpClientHandler => _httpClientHandler; + public HttpMessageHandler HttpMessageHandler => _httpMessageHandler; /// /// The connection timeout. The default value is 10 seconds. @@ -142,7 +142,7 @@ public sealed class Configuration /// The default is false: events will only include the user key, except for one "index" event that /// provides the full details for the user. /// - public bool InlineUsersInEvents => _inlineUsersInEvents; + public bool InlineUsersInEvents => _inlineUsersInEvents; /// /// Whether or not the streaming API should be used to receive flag updates. @@ -187,7 +187,7 @@ public sealed class Configuration /// removed, even if you did not use the /// method in . /// - public ISet PrivateAttributeNames => _privateAttributeNames; + public IImmutableSet PrivateAttributeNames => _privateAttributeNames; /// /// The timeout when reading data from the streaming connection. @@ -227,8 +227,8 @@ public sealed class Configuration /// public int UserKeysCapacity => _userKeysCapacity; - /// - /// The interval at which the event processor will reset its set of known user keys. + /// + /// The interval at which the event processor will reset its set of known user keys. /// public TimeSpan UserKeysFlushInterval => _userKeysFlushInterval; @@ -236,9 +236,9 @@ public sealed class Configuration /// Default value for . /// public static TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5); - - /// - /// Minimum value for . + + /// + /// Minimum value for . /// public static TimeSpan MinimumPollingInterval = TimeSpan.FromMinutes(5); @@ -267,13 +267,33 @@ public static Configuration Default(string mobileKey) return Builder(mobileKey).Build(); } - /// /// Creates a ConfigurationBuilder for constructing a configuration object using a fluent syntax. /// /// /// This is the only method for building a Configuration if you are setting properties /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of /// properties, after which you call to get the resulting /// Configuration instance. /// /// ///
        ///     var config = Configuration.Builder("my-sdk-key")
        ///         .EventQueueFrequency(TimeSpan.FromSeconds(90))
        ///         .StartWaitTime(TimeSpan.FromSeconds(5))
        ///         .Build();
        /// 
///
/// the mobile SDK key for your LaunchDarkly environment - /// a builder object public static IConfigurationBuilder Builder(string mobileKey) { - if (String.IsNullOrEmpty(mobileKey)) - { - throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); + /// + /// Creates a ConfigurationBuilder for constructing a configuration object using a fluent syntax. + /// + /// + /// This is the only method for building a Configuration if you are setting properties + /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of + /// properties, after which you call to get the resulting + /// Configuration instance. + /// + /// + ///
+        ///     var config = Configuration.Builder("my-sdk-key")
+        ///         .EventQueueFrequency(TimeSpan.FromSeconds(90))
+        ///         .StartWaitTime(TimeSpan.FromSeconds(5))
+        ///         .Build();
+        /// 
+ ///
+ /// the mobile SDK key for your LaunchDarkly environment + /// a builder object + public static IConfigurationBuilder Builder(string mobileKey) + { + if (String.IsNullOrEmpty(mobileKey)) + { + throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); } - return new ConfigurationBuilder(mobileKey); } + return new ConfigurationBuilder(mobileKey); + } /// /// Exposed for test code that needs to access the internal methods of ConfigurationBuilder that @@ -281,8 +301,10 @@ public static Configuration Default(string mobileKey) /// /// the mobile SDK key /// a builder object - internal static ConfigurationBuilder BuilderInternal(string mobileKey) { - return new ConfigurationBuilder(mobileKey); } + internal static ConfigurationBuilder BuilderInternal(string mobileKey) + { + return new ConfigurationBuilder(mobileKey); + } /// /// Creates a ConfigurationBuilder starting with the properties of an existing Configuration. @@ -296,7 +318,31 @@ public static IConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { - _allAttributesPrivate = builder._allAttributesPrivate; _backgroundPollingInterval = builder._backgroundPollingInterval; _baseUri = builder._baseUri; _connectionTimeout = builder._connectionTimeout; _enableBackgroundUpdating = builder._enableBackgroundUpdating; _evaluationReasons = builder._evaluationReasons; _eventFlushInterval = builder._eventFlushInterval; _eventCapacity = builder._eventCapacity; _eventsUri = builder._eventsUri; _httpClientHandler = builder._httpClientHandler; _httpClientTimeout = builder._httpClientTimeout; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; _offline = builder._offline; _persistFlagValues = builder._persistFlagValues; _pollingInterval = builder._pollingInterval; _privateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); _readTimeout = builder._readTimeout; _reconnectTime = builder._reconnectTime; _streamUri = builder._streamUri; _useReport = builder._useReport; _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; + _allAttributesPrivate = builder._allAttributesPrivate; + _backgroundPollingInterval = builder._backgroundPollingInterval; + _baseUri = builder._baseUri; + _connectionTimeout = builder._connectionTimeout; + _enableBackgroundUpdating = builder._enableBackgroundUpdating; + _evaluationReasons = builder._evaluationReasons; + _eventFlushInterval = builder._eventFlushInterval; + _eventCapacity = builder._eventCapacity; + _eventsUri = builder._eventsUri; + _httpMessageHandler = builder._httpMessageHandler; + _httpClientTimeout = builder._httpClientTimeout; + _inlineUsersInEvents = builder._inlineUsersInEvents; + _isStreamingEnabled = builder._isStreamingEnabled; + _mobileKey = builder._mobileKey; + _offline = builder._offline; + _persistFlagValues = builder._persistFlagValues; + _pollingInterval = builder._pollingInterval; + _privateAttributeNames = builder._privateAttributeNames is null ? null : + builder._privateAttributeNames.ToImmutableHashSet(); + _readTimeout = builder._readTimeout; + _reconnectTime = builder._reconnectTime; + _streamUri = builder._streamUri; + _useReport = builder._useReport; + _userKeysCapacity = builder._userKeysCapacity; + _userKeysFlushInterval = builder._userKeysFlushInterval; _connectionManager = builder._connectionManager; _deviceInfo = builder._deviceInfo; @@ -320,7 +366,7 @@ private class EventProcessorAdapter : IEventProcessorConfiguration public Uri EventsUri => Config.EventsUri; public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; public bool InlineUsersInEvents => Config.InlineUsersInEvents; - public ISet PrivateAttributeNames => Config.PrivateAttributeNames; + public IImmutableSet PrivateAttributeNames => Config.PrivateAttributeNames; public TimeSpan ReadTimeout => Config.ReadTimeout; public TimeSpan ReconnectTime => Config.ReconnectTime; public int UserKeysCapacity => Config.UserKeysCapacity; @@ -331,14 +377,14 @@ private class HttpRequestAdapter : IHttpRequestConfiguration { internal Configuration Config { get; set; } public string HttpAuthorizationKey => Config.MobileKey; - public HttpClientHandler HttpClientHandler => Config.HttpClientHandler; + public HttpMessageHandler HttpMessageHandler => Config.HttpMessageHandler; } private class StreamManagerAdapter : IStreamManagerConfiguration { internal Configuration Config { get; set; } public string HttpAuthorizationKey => Config.MobileKey; - public HttpClientHandler HttpClientHandler => Config.HttpClientHandler; + public HttpMessageHandler HttpMessageHandler => Config.HttpMessageHandler; public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; public TimeSpan ReadTimeout => Config.ReadTimeout; public TimeSpan ReconnectTime => Config.ReconnectTime; diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index e1fcc82f..84212c7a 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -106,11 +106,18 @@ public interface IConfigurationBuilder IConfigurationBuilder EventsUri(Uri eventsUri); /// - /// Sets the object to be used for sending HTTP requests. This is exposed for testing purposes. + /// Sets the object to be used for sending HTTP requests, if a specific implementation is desired. /// - /// the HttpClientHandler to use + /// + /// This is exposed mainly for testing purposes; you should not normally need to change it. + /// By default, on mobile platforms it will use the appropriate native HTTP handler for the + /// current platform, if any (e.g. Xamarin.Android.Net.AndroidClientHandler). If this is + /// null, the SDK will call the default constructor without + /// specifying a handler, which may or may not result in using a native HTTP handler. + /// + /// the HttpMessageHandler to use /// the same builder - IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler); + IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler); /// /// Sets the connection timeout. The default value is 10 seconds. @@ -268,7 +275,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal int _eventCapacity = Configuration.DefaultEventCapacity; internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; internal Uri _eventsUri = Configuration.DefaultEventsUri; - internal HttpClientHandler _httpClientHandler = new HttpClientHandler(); + internal HttpMessageHandler _httpMessageHandler = PlatformSpecific.Http.GetHttpMessageHandler(); // see Http.shared.cs internal TimeSpan _httpClientTimeout = Configuration.DefaultHttpClientTimeout; internal bool _inlineUsersInEvents = false; internal bool _isStreamingEnabled = true; @@ -309,7 +316,7 @@ internal ConfigurationBuilder(Configuration copyFrom) _eventCapacity = copyFrom.EventCapacity; _eventFlushInterval = copyFrom.EventFlushInterval; _eventsUri = copyFrom.EventsUri; - _httpClientHandler = copyFrom.HttpClientHandler; + _httpMessageHandler = copyFrom.HttpMessageHandler; _httpClientTimeout = copyFrom.HttpClientTimeout; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _isStreamingEnabled = copyFrom.IsStreamingEnabled; @@ -394,9 +401,9 @@ public IConfigurationBuilder EventsUri(Uri eventsUri) return this; } - public IConfigurationBuilder HttpClientHandler(HttpClientHandler httpClientHandler) + public IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler) { - _httpClientHandler = httpClientHandler; + _httpMessageHandler = httpMessageHandler; return this; } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index e26f1498..1aafbd69 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs new file mode 100644 index 00000000..adc54f51 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs @@ -0,0 +1,11 @@ +using System.Net.Http; +using Xamarin.Android.Net; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class Http + { + private static HttpMessageHandler PlatformCreateHttpMessageHandler() => + new AndroidClientHandler(); + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs new file mode 100644 index 00000000..0076e11d --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs @@ -0,0 +1,10 @@ +using System.Net.Http; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class Http + { + private static HttpMessageHandler PlatformCreateHttpMessageHandler() => + new NSUrlSessionHandler(); + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs new file mode 100644 index 00000000..4465fd65 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs @@ -0,0 +1,9 @@ +using System.Net.Http; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class Http + { + private static HttpMessageHandler PlatformCreateHttpMessageHandler() => null; + } +} diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs new file mode 100644 index 00000000..ad70e696 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs @@ -0,0 +1,25 @@ +using System.Net.Http; + +namespace LaunchDarkly.Xamarin.PlatformSpecific +{ + internal static partial class Http + { + private static HttpMessageHandler _httpMessageHandler = PlatformCreateHttpMessageHandler(); + + /// + /// If our default configuration should use a specific + /// implementation, returns that implementation. + /// + /// + /// The handler is not stateful, so it can be a shared instance. If we don't need to use a + /// specific implementation, this returns null. This is just the default for + /// , so the application can still override it. If it is + /// null and the application lets it remain null, then Xamarin will make its + /// own decision based on logic we don't have access to; in practice that seems to result + /// in picking the same handler that our platform-specific logic would specify, but that + /// may be dependent on project configuration, so we decided to explicitly set a default. + /// + /// an HTTP handler implementation or null + public static HttpMessageHandler GetHttpMessageHandler() => _httpMessageHandler; + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index b9f76e20..53e0438a 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -66,14 +66,14 @@ public void CannotSetTooSmallBackgroundPollingInterval() } [Fact] - public void CanSetHttpClientHandler() + public void CanSetHttpMessageHandler() { var handler = new HttpClientHandler(); var config = Configuration.Builder("AnyOtherSdkKey") - .HttpClientHandler(handler) + .HttpMessageHandler(handler) .Build(); - Assert.Equal(handler, config.HttpClientHandler); + Assert.Equal(handler, config.HttpMessageHandler); } } } \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 118cb7d8..646d691d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index db856cc6..7f86bf55 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -35,7 +35,7 @@ public void TrackSendsCustomEvent() using (LdClient client = MakeClient(user, "{}")) { JToken data = new JValue("hi"); - client.Track("eventkey", ImmutableJsonValue.Of(data)); + client.Track("eventkey", ImmutableJsonValue.FromJToken(data)); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist index e17844fd..b14a26b7 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Info.plist @@ -1,48 +1,61 @@ - + - CFBundleDisplayName - LaunchDarkly.XamarinSdk.iOS.Tests - CFBundleIdentifier - com.launchdarkly.XamarinSdkTests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - MinimumOSVersion - 10.0 - UIDeviceFamily - - 1 - 2 - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIMainStoryboardFile~ipad - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - XSAppIconAssets - Assets.xcassets/AppIcon.appiconset + CFBundleDisplayName + LaunchDarkly.XamarinSdk.iOS.Tests + CFBundleIdentifier + com.launchdarkly.XamarinSdkTests + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIMainStoryboardFile~ipad + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + CFBundleName + LaunchDarkly.XamarinSdk.iOS.Tests + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + - \ No newline at end of file + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs index b2eb5309..f68b19b0 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs @@ -5,12 +5,12 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.iOS.Toasts")] +[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.iOS.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.iOS.Toasts")] -[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.iOS.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] From f6d44e2fd211231c80cb4e0c02142854c479b8b4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 12 Aug 2019 13:08:42 -0700 Subject: [PATCH 229/499] fix test state contamination in cached flag tests --- .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 6 + .../LDClientEndToEndTests.cs | 247 ++++++++++++------ 2 files changed, 166 insertions(+), 87 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index e020d44e..007dab21 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Common.Logging; +using LaunchDarkly.Client; using WireMock.Server; using Xunit; @@ -30,6 +31,11 @@ public void Dispose() TestUtil.ClearClient(); } + protected void ClearCachedFlags(User user) + { + PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key); + } + protected void WithServer(Action a) { var s = MakeServer(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index a9c29ae8..de480274 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -87,7 +87,7 @@ public void InitCanTimeOutSync() { Assert.False(Initialized(client)); Assert.False(client.Initialized()); - Assert.Equal("value1", client.StringVariation(_flagData1.First().Key, null)); + Assert.Null(client.StringVariation(_flagData1.First().Key, null)); Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && m.Text == "Client did not successfully initialize within 200 milliseconds."); } @@ -241,23 +241,35 @@ public void OfflineClientUsesCachedFlagsSync() { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = TestUtil.CreateClient(config, _user)) + ClearCachedFlags(_user); + try { - VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); + } + } + finally { - VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); + ClearCachedFlags(_user); } }); } @@ -269,22 +281,34 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = await TestUtil.CreateClientAsync(config, _user)) + ClearCachedFlags(_user); + try { - VerifyFlagValues(client, _flagData1); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + VerifyFlagValues(client, _flagData1); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + VerifyFlagValues(client, _flagData1); + Assert.True(client.Initialized()); + } + } + finally { - VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); + ClearCachedFlags(_user); } }); } @@ -296,23 +320,35 @@ public void OfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = TestUtil.CreateClient(config, _user)) + ClearCachedFlags(_user); + try { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = TestUtil.CreateClient(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + } + finally { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); + ClearCachedFlags(_user); } }); } @@ -324,23 +360,35 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = await TestUtil.CreateClientAsync(config, _user)) + ClearCachedFlags(_user); + try { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + } + finally { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); + ClearCachedFlags(_user); } }); } @@ -357,27 +405,39 @@ public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor( { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = TestUtil.CreateClient(config, _user)) + ClearCachedFlags(_user); + try { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = TestUtil.CreateClient(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + } + } + finally { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); + ClearCachedFlags(_user); } }); } @@ -389,27 +449,39 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); - using (var client = await TestUtil.CreateClientAsync(config, _user)) + ClearCachedFlags(_user); + try { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + var config = BaseConfig(server) + .UseReport(false) + .IsStreamingEnabled(false) + .PersistFlagValues(true) + .Build(); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over, but with a server that doesn't respond immediately. When the client times + // out, we should still see the earlier flag values. - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + server.Reset(); // the offline client shouldn't be making any requests, but just in case + var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) + { + BackgroundDetection.UpdateBackgroundMode(true); + VerifyFlagValues(client, _flagData1); + BackgroundDetection.UpdateBackgroundMode(false); + VerifyFlagValues(client, _flagData1); + } + } + finally { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); + ClearCachedFlags(_user); } }); } @@ -419,7 +491,8 @@ private IConfigurationBuilder BaseConfig(FluentMockServer server) return Configuration.BuilderInternal(_mobileKey) .EventProcessor(new MockEventProcessor()) .BaseUri(new Uri(server.GetUrl())) - .StreamUri(new Uri(server.GetUrl())); + .StreamUri(new Uri(server.GetUrl())) + .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination } private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) From 2c3e65271a74fdf6f0ca77d1d8b34c83aaa8fb90 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 12 Aug 2019 13:49:22 -0700 Subject: [PATCH 230/499] Addressing PR feedback about keeping warn logging for variation when client isn't initialized --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 6f6463dd..f53a5e2c 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -381,10 +381,18 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => var flag = flagCacheManager.FlagForUser(featureKey, User); if (flag == null) { - Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); - eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.FLAG_NOT_FOUND)); - return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); + return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); + } + else + { + Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); + eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + EvaluationErrorKind.FLAG_NOT_FOUND)); + return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); + } } featureFlagEvent = new FeatureFlagEvent(featureKey, flag); @@ -393,6 +401,10 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (flag.value == null || flag.value.Type == JTokenType.Null) { valueJson = defaultJson; + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); + } result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); } else From 4de70dfdbc92161f328e24cb83ab6a3be98b21e0 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:19:00 -0700 Subject: [PATCH 231/499] Removed warn log from incorrect default return, added check for when returning cached value but client is not initialized --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index f53a5e2c..56251f8f 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -383,7 +383,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => { if (!Initialized()) { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); + Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); } else @@ -394,6 +394,13 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); } } + else + { + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); + } + } featureFlagEvent = new FeatureFlagEvent(featureKey, flag); EvaluationDetail result; @@ -401,10 +408,6 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (flag.value == null || flag.value.Type == JTokenType.Null) { valueJson = defaultJson; - if (!Initialized()) - { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default"); - } result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); } else From b586979c3d4237b9c45b68f2a3ff068a8d52b713 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 20 Aug 2019 18:40:56 -0700 Subject: [PATCH 232/499] send event for evaluation even if client isn't inited --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 + .../LdClientEventTests.cs | 57 +++++++++++++++++++ .../MockComponents.cs | 26 ++++++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 56251f8f..f9e5243c 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -384,6 +384,8 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (!Initialized()) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); + eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + EvaluationErrorKind.CLIENT_NOT_READY)); return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); } else diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 7f86bf55..039d1821 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -138,6 +138,33 @@ public void VariationSendsFeatureEventForUnknownFlag() Assert.Null(fe.Reason); }); } + } + + [Fact] + public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() + { + var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") + .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) + .EventProcessor(eventProcessor); + config.EventProcessor(eventProcessor); + + using (LdClient client = TestUtil.CreateClient(config.Build(), user)) + { + string result = client.StringVariation("flag", "b"); + Assert.Equal("b", result); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => + { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Null(fe.Version); + Assert.Equal("b", fe.Default); + Assert.Null(fe.Reason); + }); + } } [Fact] @@ -219,6 +246,36 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() Assert.Equal(expectedReason, fe.Reason); }); } + } + + [Fact] + public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() + { + var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") + .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) + .EventProcessor(eventProcessor); + config.EventProcessor(eventProcessor); + + using (LdClient client = TestUtil.CreateClient(config.Build(), user)) + { + EvaluationDetail result = client.StringVariationDetail("flag", "b"); + var expectedReason = new EvaluationReason.Error(EvaluationErrorKind.CLIENT_NOT_READY); + Assert.Equal("b", result.Value); + Assert.Equal(expectedReason, result.Reason); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + FeatureRequestEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.Key); + Assert.Equal("b", fe.Value); + Assert.Null(fe.Variation); + Assert.Null(fe.Version); + Assert.Equal("b", fe.Default); + Assert.False(fe.TrackEvents); + Assert.Null(fe.DebugEventsUntilDate); + Assert.Equal(expectedReason, fe.Reason); + }); + } } private void CheckIdentifyEvent(Event e, User u) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 69f96cd0..e96ba01d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -104,7 +104,7 @@ public FeatureFlag FlagForUser(string flagKey, User user) { var flags = FlagsForUser(user); FeatureFlag featureFlag; - if (flags.TryGetValue(flagKey, out featureFlag)) + if (flags != null && flags.TryGetValue(flagKey, out featureFlag)) { return featureFlag; } @@ -208,4 +208,28 @@ public Task Start() return Task.FromResult(true); } } + + internal class MockUpdateProcessorThatNeverInitializes : IMobileUpdateProcessor + { + public static Func Factory() + { + return (config, manager, user) => new MockUpdateProcessorThatNeverInitializes(); + } + + public bool IsRunning => false; + + public void Dispose() + { + } + + public bool Initialized() + { + return false; + } + + public Task Start() + { + return new TaskCompletionSource().Task; // will never be completed + } + } } From 218637466bead4d87f45ea16be63347527d0b9c0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 20 Aug 2019 19:19:21 -0700 Subject: [PATCH 233/499] make sure Identify/IdentifyAsync only completes when we have new flags --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 10 +-- .../LdClientTests.cs | 75 ++++++++++++++++++- .../MockComponents.cs | 26 +++++++ .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 22 +++--- 4 files changed, 113 insertions(+), 20 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index f9e5243c..2a73159b 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -514,20 +514,20 @@ bool StartUpdateProcessor(TimeSpan maxWaitTime) } } - Task StartUpdateProcessorAsync() + async Task StartUpdateProcessorAsync() { if (Online) { - var successfulConnection = updateProcessor.Start(); - if (successfulConnection.IsCompleted) + var successfulConnection = await updateProcessor.Start(); + if (successfulConnection) { initialized = true; } return successfulConnection; } else - { - return Task.FromResult(true); + { + return true; } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index d232fd4b..877e273e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using LaunchDarkly.Client; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -7,7 +9,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class DefaultLdClientTests : BaseTest + public class LdClientTests : BaseTest { static readonly string appKey = "some app key"; static readonly User simpleUser = User.WithKey("user-key"); @@ -55,7 +57,76 @@ public void IdentifyUpdatesTheUser() client.Identify(updatedUser); Assert.Equal(client.User.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes } - } + } + + [Fact] + public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => client.IdentifyAsync(user)); + + [Fact] + public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user))); + + private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) + { + var userA = User.WithKey("a"); + var userB = User.WithKey("b"); + + var flagKey = "flag"; + var userAFlags = TestUtil.MakeSingleFlagData(flagKey, "a-value"); + var userBFlags = TestUtil.MakeSingleFlagData(flagKey, "b-value"); + + var startedIdentifyUserB = new SemaphoreSlim(0, 1); + var canFinishIdentifyUserB = new SemaphoreSlim(0, 1); + var finishedIdentifyUserB = new SemaphoreSlim(0, 1); + + Func updateProcessorFactory = (c, flags, user) => + new MockUpdateProcessorFromLambda(user, async () => + { + switch (user.Key) + { + case "a": + flags.CacheFlagsFromService(userAFlags, user); + break; + + case "b": + startedIdentifyUserB.Release(); + await canFinishIdentifyUserB.WaitAsync(); + flags.CacheFlagsFromService(userBFlags, user); + break; + } + }); + + var config = TestUtil.ConfigWithFlagsJson(userA, appKey, "{}") + .UpdateProcessorFactory(updateProcessorFactory) + .Build(); + + ClearCachedFlags(userA); + ClearCachedFlags(userB); + + using (var client = await LdClient.InitAsync(config, userA)) + { + Assert.True(client.Initialized()); + Assert.Equal("a-value", client.StringVariation(flagKey, null)); + + var identifyUserBTask = Task.Run(async () => + { + await identifyTask(client, userB); + finishedIdentifyUserB.Release(); + }); + + await startedIdentifyUserB.WaitAsync(); + + Assert.False(client.Initialized()); + Assert.Null(client.StringVariation(flagKey, null)); + + canFinishIdentifyUserB.Release(); + await finishedIdentifyUserB.WaitAsync(); + + Assert.True(client.Initialized()); + Assert.Equal("b-value", client.StringVariation(flagKey, null)); + } + } [Fact] public void IdentifyWithNullUserThrowsException() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index e96ba01d..25e94762 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -209,6 +209,32 @@ public Task Start() } } + internal class MockUpdateProcessorFromLambda : IMobileUpdateProcessor + { + private readonly User _user; + private readonly Func _startFn; + private bool _initialized; + + public MockUpdateProcessorFromLambda(User user, Func startFn) + { + _user = user; + _startFn = startFn; + } + + public Task Start() + { + return _startFn().ContinueWith(t => + { + _initialized = true; + return true; + }); + } + + public bool Initialized() => _initialized; + + public void Dispose() { } + } + internal class MockUpdateProcessorThatNeverInitializes : IMobileUpdateProcessor { public static Func Factory() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index ca678113..4b5e3ce3 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -113,19 +113,15 @@ public static void ClearClient() }); } - public static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) - { - JObject fo = new JObject { { "value", value } }; - if (variation != null) - { - fo["variation"] = new JValue(variation.Value); - } - if (reason != null) - { - fo["reason"] = JToken.FromObject(reason); - } - JObject o = new JObject { { flagKey, fo } }; - return JsonConvert.SerializeObject(o); + internal static Dictionary MakeSingleFlagData(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) + { + var flag = new FeatureFlag { value = value, variation = variation, reason = reason }; + return new Dictionary { { flagKey, flag } }; + } + + internal static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) + { + return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); } internal static IDictionary DecodeFlagsJson(string flagsJson) From 82c087e469603120642407cedf2fa835dc3170c5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 21 Aug 2019 10:04:34 -0700 Subject: [PATCH 234/499] add helper scripts --- .gitignore | 1 + CONTRIBUTING.md | 10 ++++++++++ scripts/build-test-package.sh | 32 ++++++++++++++++++++++++++++++++ scripts/update-version.sh | 12 ++++++++++++ 4 files changed, 55 insertions(+) create mode 100755 scripts/build-test-package.sh create mode 100755 scripts/update-version.sh diff --git a/.gitignore b/.gitignore index af1d0262..a13ec91a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ [Bb]in/ packages/ TestResults/ +test-packages/ # globs Makefile.in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51fbe5eb..f6fa0311 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,3 +50,13 @@ You can run the mobile test projects from Visual Studio (the iOS tests require M Note that the mobile unit tests currently do not cover background-mode behavior or connectivity detection. ### Packaging/releasing + +Run `./scripts/update-version.sh ` to set the project version. + +Run `./scripts/release.sh` to build the project for all target frameworks and upload the package to NuGet. You must have already configured the necessary NuGet key locally. + +To verify that the package can be built without uploading it, run `./scripts/package.sh`. + +### Building a temporary package + +If you need to build a `.nupkg` for testing another application (in cases where linking directly to this project is not an option), run `./scripts/build-test-package.sh`. This will create a package with a unique version string in `./test-packages`. You can then set your other project to use `test-packages` as a NuGet package source. diff --git a/scripts/build-test-package.sh b/scripts/build-test-package.sh new file mode 100755 index 00000000..73482a2b --- /dev/null +++ b/scripts/build-test-package.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +# build-test-package.sh +# Temporarily changes the project version to a unique prerelease version string based on the current +# date and time, builds a .nupkg package, and places the package in ./test-packages. You can then use +# the test-packages directory as a package source to use this package in our testing tools. + +TEST_VERSION="0.0.1-$(date +%Y%m%d.%H%M%S)" +PROJECT_FILE=src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +SAVE_PROJECT_FILE="${PROJECT_FILE}.orig" +TEST_PACKAGE_DIR=./test-packages + +mkdir -p "${TEST_PACKAGE_DIR}" + +cp "${PROJECT_FILE}" "${SAVE_PROJECT_FILE}" + +"$(dirname "$0")/update-version.sh" "${TEST_VERSION}" + +trap 'mv "${SAVE_PROJECT_FILE}" "${PROJECT_FILE}"' EXIT + +msbuild /restore + +NUPKG_FILE="src/LaunchDarkly.XamarinSdk/bin/Debug/LaunchDarkly.XamarinSdk.${TEST_VERSION}.nupkg" +if [ -f "${NUPKG_FILE}" ]; then + mv "${NUPKG_FILE}" "${TEST_PACKAGE_DIR}" + echo; echo; echo "Success! Created test package version ${TEST_VERSION} in ${TEST_PACKAGE_DIR}" +else + echo; echo "Unknown problem - did not build the expected package file" + exit 1 +fi diff --git a/scripts/update-version.sh b/scripts/update-version.sh new file mode 100755 index 00000000..cbe23cdc --- /dev/null +++ b/scripts/update-version.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# update-version.sh +# Updates the version string in the project file. + +NEW_VERSION="$1" + +PROJECT_FILE=./src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +TEMP_FILE="${PROJECT_FILE}.tmp" + +sed "s#^\( *\)[^<]*#\1${NEW_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" +mv "${TEMP_FILE}" "${PROJECT_FILE}" From 972fc8759e312c1c3ad5e45ceb8b45880ef62f1e Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 21 Aug 2019 12:04:41 -0700 Subject: [PATCH 235/499] Changed Online to use WaitSafely instead of storing result in a var --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index f9e5243c..9e52270d 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -71,7 +71,7 @@ public bool Online get => _online; set { - var doNotAwaitResult = SetOnlineAsync(value); + AsyncUtils.WaitSafely(() => SetOnlineAsync(value)); } } From e0acb8f956784339d5fb9677083d500abc157146 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 21 Aug 2019 14:36:47 -0700 Subject: [PATCH 236/499] Added identical value check to SetOnlineAsync --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 9a3d6299..93122746 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -283,6 +283,10 @@ void SetupConnectionManager() public async Task SetOnlineAsync(bool value) { + if (value == _online) + { + return; + } await _connectionLock.WaitAsync(); _online = value; try From b82dd4c91c08a734b2dfc40517262e76e2bc8557 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 21 Aug 2019 14:56:36 -0700 Subject: [PATCH 237/499] Move value check inside of lock for Android unit test --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 93122746..0d1fc6ba 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -283,11 +283,11 @@ void SetupConnectionManager() public async Task SetOnlineAsync(bool value) { + await _connectionLock.WaitAsync(); if (value == _online) { return; } - await _connectionLock.WaitAsync(); _online = value; try { From a17153e16a48a70d9c136247d0230f4429763654 Mon Sep 17 00:00:00 2001 From: torchhound <5600929+torchhound@users.noreply.github.com> Date: Wed, 21 Aug 2019 15:28:36 -0700 Subject: [PATCH 238/499] Added second value check inside Online, moved value check into try in SetOnlineAsync --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 0d1fc6ba..90dce51d 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -71,6 +71,10 @@ public bool Online get => _online; set { + if (value == _online) + { + return; + } AsyncUtils.WaitSafely(() => SetOnlineAsync(value)); } } @@ -284,13 +288,13 @@ void SetupConnectionManager() public async Task SetOnlineAsync(bool value) { await _connectionLock.WaitAsync(); - if (value == _online) - { - return; - } - _online = value; try { + if (value == _online) + { + return; + } + _online = value; if (_online) { await RestartUpdateProcessorAsync(Config.PollingInterval); From d9d01dc59909caa756c1abfc480a34c6032a7330 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 23 Aug 2019 16:23:54 -0700 Subject: [PATCH 239/499] disable REPORT mode because it doesn't work in Android yet --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 3 +- .../ConfigurationBuilder.cs | 18 --- .../FeatureFlagRequestorTests.cs | 111 +++++++++--------- .../LDClientEndToEndTests.cs | 26 ++-- .../MobileStreamingProcessorTests.cs | 63 +++++----- 5 files changed, 103 insertions(+), 118 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index d3279706..3f78fe58 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -216,7 +216,8 @@ public sealed class Configuration /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. /// However, some network gateways do not support REPORT. /// - public bool UseReport => _useReport; + internal bool UseReport => _useReport; + // UseReport is currently disabled due to Android HTTP issues (ch47341), but it's still implemented internally /// /// The number of user keys that the event processor can remember at any one time. diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 84212c7a..e3782890 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -228,18 +228,6 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder StreamUri(Uri streamUri); - /// - /// Sets whether to use the HTTP REPORT method for feature flag requests. - /// - /// - /// By default, polling and streaming connections are made with the GET method, witht the user data - /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. - /// However, some network gateways do not support REPORT. - /// - /// whether to use REPORT mode - /// the same builder - IConfigurationBuilder UseReport(bool useReport); - /// /// Sets the number of user keys that the event processor can remember at any one time. /// @@ -485,12 +473,6 @@ public IConfigurationBuilder StreamUri(Uri streamUri) return this; } - public IConfigurationBuilder UseReport(bool useReport) - { - _useReport = useReport; - return this; - } - public IConfigurationBuilder UserKeysCapacity(int userKeysCapacity) { _userKeysCapacity = userKeysCapacity; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 0f68f833..76ccb663 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -25,7 +25,7 @@ await WithServerAsync(async server => server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - .UseReport(false).Build(); + .Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { @@ -51,7 +51,7 @@ await WithServerAsync(async server => server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - .UseReport(false).EvaluationReasons(true).Build(); + .EvaluationReasons(true).Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) { @@ -69,58 +69,59 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] - public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() - { - await WithServerAsync(async server => - { - server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - - var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - .UseReport(true).Build(); - - using (var requestor = new FeatureFlagRequestor(config, _user)) - { - var resp = await requestor.FeatureFlagsAsync(); - Assert.Equal(200, resp.statusCode); - Assert.Equal(_allDataJson, resp.jsonResponse); - - var req = server.GetLastRequest(); - Assert.Equal("REPORT", req.Method); - Assert.Equal($"/msdk/evalx/user", req.Path); - Assert.Equal("", req.RawQuery); - Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - - //Assert.Equal("{\"key\":\"foo\"}", req.Body); - // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, WireMock.Net - // is not currently able to detect the body for REPORT requests: https://github.com/WireMock-Net/WireMock.Net/issues/290 - } - }); - } - - [Fact(Skip = SkipIfCannotCreateHttpServer)] - public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() - { - await WithServerAsync(async server => - { - server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - - var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - .UseReport(true).EvaluationReasons(true).Build(); - - using (var requestor = new FeatureFlagRequestor(config, _user)) - { - var resp = await requestor.FeatureFlagsAsync(); - Assert.Equal(200, resp.statusCode); - Assert.Equal(_allDataJson, resp.jsonResponse); - - var req = server.GetLastRequest(); - Assert.Equal("REPORT", req.Method); - Assert.Equal($"/msdk/evalx/user", req.Path); - Assert.Equal("?withReasons=true", req.RawQuery); - Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - } - }); - } + // Report mode is currently disabled - ch47341 + //[Fact(Skip = SkipIfCannotCreateHttpServer)] + //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() + //{ + // await WithServerAsync(async server => + // { + // server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + // .UseReport(true).Build(); + + // using (var requestor = new FeatureFlagRequestor(config, _user)) + // { + // var resp = await requestor.FeatureFlagsAsync(); + // Assert.Equal(200, resp.statusCode); + // Assert.Equal(_allDataJson, resp.jsonResponse); + + // var req = server.GetLastRequest(); + // Assert.Equal("REPORT", req.Method); + // Assert.Equal($"/msdk/evalx/user", req.Path); + // Assert.Equal("", req.RawQuery); + // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + + // //Assert.Equal("{\"key\":\"foo\"}", req.Body); + // // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, WireMock.Net + // // is not currently able to detect the body for REPORT requests: https://github.com/WireMock-Net/WireMock.Net/issues/290 + // } + // }); + //} + + //[Fact(Skip = SkipIfCannotCreateHttpServer)] + //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() + //{ + // await WithServerAsync(async server => + // { + // server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); + + // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + // .UseReport(true).EvaluationReasons(true).Build(); + + // using (var requestor = new FeatureFlagRequestor(config, _user)) + // { + // var resp = await requestor.FeatureFlagsAsync(); + // Assert.Equal(200, resp.statusCode); + // Assert.Equal(_allDataJson, resp.jsonResponse); + + // var req = server.GetLastRequest(); + // Assert.Equal("REPORT", req.Method); + // Assert.Equal($"/msdk/evalx/user", req.Path); + // Assert.Equal("?withReasons=true", req.RawQuery); + // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + // } + // }); + //} } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index de480274..be54844e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -48,7 +48,7 @@ public void InitGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -65,7 +65,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -107,7 +107,7 @@ public void InitFailsOn401Sync(UpdateMode mode) { try { - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); using (var client = TestUtil.CreateClient(config, _user)) { } } catch (Exception e) @@ -133,7 +133,7 @@ await WithServerAsync(async server => using (var log = new LogSinkScope()) { - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that @@ -155,7 +155,7 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(false).Build(); + var config = BaseConfig(server).IsStreamingEnabled(false).Build(); var name = "Sue"; var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); @@ -186,7 +186,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -214,7 +214,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server).UseReport(false).IsStreamingEnabled(mode.IsStreaming).Build(); + var config = BaseConfig(server, mode).Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -245,7 +245,6 @@ public void OfflineClientUsesCachedFlagsSync() try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -285,7 +284,6 @@ await WithServerAsync(async server => try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -324,7 +322,6 @@ public void OfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -364,7 +361,6 @@ await WithServerAsync(async server => try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -409,7 +405,6 @@ public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor( try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -453,7 +448,6 @@ await WithServerAsync(async server => try { var config = BaseConfig(server) - .UseReport(false) .IsStreamingEnabled(false) .PersistFlagValues(true) .Build(); @@ -495,6 +489,12 @@ private IConfigurationBuilder BaseConfig(FluentMockServer server) .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination } + private IConfigurationBuilder BaseConfig(FluentMockServer server, UpdateMode mode) + { + return BaseConfig(server) + .IsStreamingEnabled(mode.IsStreaming); + } + private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) { server.ForAllRequests(r => diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index d1fcd60c..0b5b470b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -49,7 +49,7 @@ private IMobileUpdateProcessor MobileStreamingProcessorStarted() [Fact] public void StreamUriInGetModeHasUser() { - var config = configBuilder.UseReport(false).Build(); + var config = configBuilder.Build(); MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(HttpMethod.Get, props.Method); @@ -59,41 +59,42 @@ public void StreamUriInGetModeHasUser() [Fact] public void StreamUriInGetModeHasReasonsParameterIfConfigured() { - var config = configBuilder.UseReport(false).EvaluationReasons(true).Build(); + var config = configBuilder.EvaluationReasons(true).Build(); MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser + "?withReasons=true"), props.StreamUri); } - [Fact] - public void StreamUriInReportModeHasNoUser() - { - var config = configBuilder.UseReport(true).Build(); - MobileStreamingProcessorStarted(); - var props = eventSourceFactory.ReceivedProperties; - Assert.Equal(new HttpMethod("REPORT"), props.Method); - Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH), props.StreamUri); - } - - [Fact] - public void StreamUriInReportModeHasReasonsParameterIfConfigured() - { - var config = configBuilder.UseReport(true).EvaluationReasons(true).Build(); - MobileStreamingProcessorStarted(); - var props = eventSourceFactory.ReceivedProperties; - Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + "?withReasons=true"), props.StreamUri); - } - - [Fact] - public async Task StreamRequestBodyInReportModeHasUser() - { - configBuilder.UseReport(true); - MobileStreamingProcessorStarted(); - var props = eventSourceFactory.ReceivedProperties; - var body = Assert.IsType(props.RequestBody); - var s = await body.ReadAsStringAsync(); - Assert.Equal(user.AsJson(), s); - } + // Report mode is currently disabled - ch47341 + //[Fact] + //public void StreamUriInReportModeHasNoUser() + //{ + // var config = configBuilder.UseReport(true).Build(); + // MobileStreamingProcessorStarted(); + // var props = eventSourceFactory.ReceivedProperties; + // Assert.Equal(new HttpMethod("REPORT"), props.Method); + // Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH), props.StreamUri); + //} + + //[Fact] + //public void StreamUriInReportModeHasReasonsParameterIfConfigured() + //{ + // var config = configBuilder.UseReport(true).EvaluationReasons(true).Build(); + // MobileStreamingProcessorStarted(); + // var props = eventSourceFactory.ReceivedProperties; + // Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + "?withReasons=true"), props.StreamUri); + //} + + //[Fact] + //public async Task StreamRequestBodyInReportModeHasUser() + //{ + // configBuilder.UseReport(true); + // MobileStreamingProcessorStarted(); + // var props = eventSourceFactory.ReceivedProperties; + // var body = Assert.IsType(props.RequestBody); + // var s = await body.ReadAsStringAsync(); + // Assert.Equal(user.AsJson(), s); + //} [Fact] public void PUTstoresFeatureFlags() From 50f963cc74e74f82259682a1853479b78bbe05ef Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 26 Aug 2019 14:47:10 -0700 Subject: [PATCH 240/499] refactor connection state management, replace Online property with Offline, etc. (#74) --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 4 +- .../ConfigurationBuilder.cs | 6 +- .../ConnectionManager.cs | 283 ++++++++++++++ ....cs => DefaultConnectivityStateManager.cs} | 8 +- src/LaunchDarkly.XamarinSdk/Factory.cs | 48 ++- ...anager.cs => IConnectivityStateManager.cs} | 3 +- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 142 ++++--- .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 364 +++++++----------- src/LaunchDarkly.XamarinSdk/LockUtils.cs | 47 +++ .../MobilePollingProcessor.cs | 4 +- .../MobileStreamingProcessor.cs | 10 +- .../Resources/Resource.designer.cs | 101 +++++ .../LDClientEndToEndTests.cs | 121 ++++-- .../LdClientEventTests.cs | 5 +- .../LdClientTests.cs | 42 +- .../MobileStreamingProcessorTests.cs | 2 +- .../MockComponents.cs | 6 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 2 +- 19 files changed, 790 insertions(+), 410 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/ConnectionManager.cs rename src/LaunchDarkly.XamarinSdk/{MobileConnectionManager.cs => DefaultConnectivityStateManager.cs} (75%) rename src/LaunchDarkly.XamarinSdk/{IConnectionManager.cs => IConnectivityStateManager.cs} (50%) create mode 100644 src/LaunchDarkly.XamarinSdk/LockUtils.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 3f78fe58..1d3949a3 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -43,7 +43,7 @@ public sealed class Configuration private readonly TimeSpan _userKeysFlushInterval; // Settable only for testing - internal readonly IConnectionManager _connectionManager; + internal readonly IConnectivityStateManager _connectivityStateManager; internal readonly IDeviceInfo _deviceInfo; internal readonly IEventProcessor _eventProcessor; internal readonly IFlagCacheManager _flagCacheManager; @@ -345,7 +345,7 @@ internal Configuration(ConfigurationBuilder builder) _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; - _connectionManager = builder._connectionManager; + _connectivityStateManager = builder._connectivityStateManager; _deviceInfo = builder._deviceInfo; _eventProcessor = builder._eventProcessor; _flagCacheManager = builder._flagCacheManager; diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index e3782890..72cdd11a 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -280,7 +280,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; // Internal properties only settable for testing - internal IConnectionManager _connectionManager; + internal IConnectivityStateManager _connectivityStateManager; internal IDeviceInfo _deviceInfo; internal IEventProcessor _eventProcessor; internal IFlagCacheManager _flagCacheManager; @@ -491,9 +491,9 @@ public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterva // and then call these methods before you have called any of the public methods (since // only these methods return ConfigurationBuilder rather than IConfigurationBuilder). - internal ConfigurationBuilder ConnectionManager(IConnectionManager connectionManager) + internal ConfigurationBuilder ConnectivityStateManager(IConnectivityStateManager connectivityStateManager) { - _connectionManager = connectionManager; + _connectivityStateManager = connectivityStateManager; return this; } diff --git a/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs new file mode 100644 index 00000000..a7e4aca8 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs @@ -0,0 +1,283 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Common.Logging; +using LaunchDarkly.Common; + +namespace LaunchDarkly.Xamarin +{ + /// + /// Manages our connection to LaunchDarkly, if any, and encapsulates all of the state that + /// determines whether we should have a connection or not. + /// + /// + /// Whenever the state of this object is modified by , + /// , , + /// or , it will decide whether to make a new connection, drop an existing + /// connection, both, or neither. If the caller wants to know when a new connection (if any) is + /// ready, it should await the returned task. + /// + /// The object begins in a non-started state, so regardless of what properties are set, it will not + /// make a connection until after has been called. + /// + internal sealed class ConnectionManager : IDisposable + { + private static readonly ILog Log = LogManager.GetLogger(typeof(ConnectionManager)); + + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private bool _disposed = false; + private bool _started = false; + private bool _initialized = false; + private bool _forceOffline = false; + private bool _networkEnabled = false; + private IMobileUpdateProcessor _updateProcessor = null; + private Func _updateProcessorFactory = null; + + // Note that these properties do not have simple setter methods, because the setters all + // need to return Tasks. + + /// + /// True if we are in offline mode ( was set to true). + /// + public bool ForceOffline => LockUtils.WithReadLock(_lock, () => _forceOffline); + + /// + /// True if we have been told there is network connectivity ( + /// was set to true). + /// + public bool NetworkEnabled => LockUtils.WithReadLock(_lock, () => _networkEnabled); + + /// + /// True if we made a successful LaunchDarkly connection or do not need to make one (see + /// ). + /// + public bool Initialized => LockUtils.WithReadLock(_lock, () => _initialized); + + /// + /// Sets whether the client should always be offline, and attempts to connect if appropriate. + /// + /// + /// Besides updating the value of the property, we do the + /// following: + /// + /// If forceOffline is true, we drop our current connection (if any), and we will not + /// make any connections no matter what other properties are changed as long as this property is + /// still true. + /// + /// If forceOffline is false and we already have a connection, nothing happens. + /// + /// If forceOffline is false and we have no connection, but other conditions disallow + /// making a connection (or we do not have an update processor factory), nothing happens. + /// + /// If forceOffline is false, and we do not yet have a connection, and no other + /// conditions disallow making a connection, and we have an update processor factory, + /// we create an update processor and tell it to start. + /// + /// The returned task is immediately completed unless we are making a new connection, in which + /// case it is completed when the update processor signals success or failure. The task yields + /// a true result if we successfully made a connection or if we decided not to connect + /// because we are in offline mode. In other words, the result is true if + /// is true. + /// + /// true if the client should always be offline + /// a task as described above + public Task SetForceOffline(bool forceOffline) + { + return LockUtils.WithWriteLock(_lock, () => + { + if (_disposed || _forceOffline == forceOffline) + { + return Task.FromResult(false); + } + _forceOffline = forceOffline; + Log.InfoFormat("Offline mode is now {0}", forceOffline); + return OpenOrCloseConnectionIfNecessary(); // not awaiting + }); + } + + /// + /// Sets whether we should be able to make network connections, and attempts to connect if appropriate. + /// + /// + /// Besides updating the value of the property, we do the + /// following: + /// + /// If networkEnabled is false, we drop our current connection (if any), and we will not + /// make any connections no matter what other properties are changed as long as this property is + /// still true. + /// + /// If networkEnabled is true and we already have a connection, nothing happens. + /// + /// If networkEnabled is true and we have no connection, but other conditions disallow + /// making a connection (or we do not have an update processor factory), nothing happens. + /// + /// If networkEnabled is true, and we do not yet have a connection, and no other + /// conditions disallow making a connection, and we have an update processor factory, + /// we create an update processor and tell it to start. + /// + /// The returned task is immediately completed unless we are making a new connection, in which + /// case it is completed when the update processor signals success or failure. The task yields + /// a true result if we successfully made a connection or if we decided not to connect + /// because we are in offline mode. In other words, the result is true if + /// is true. + /// + /// true if we think we can make network connections + /// a task as described above + public Task SetNetworkEnabled(bool networkEnabled) + { + return LockUtils.WithWriteLock(_lock, () => + { + if (_disposed || _networkEnabled == networkEnabled) + { + return Task.FromResult(false); + } + _networkEnabled = networkEnabled; + Log.InfoFormat("Network availability is now {0}", networkEnabled); + return OpenOrCloseConnectionIfNecessary(); // not awaiting + }); + } + + /// + /// Sets the factory function for creating an update processor, and attempts to connect if + /// appropriate. + /// + /// + /// The factory function encapsulates all the information that takes into + /// account when making a connection, i.e. whether we are in streaming or polling mode, the + /// polling interval, and the curent user. ConnectionManager itself has no knowledge of + /// those things. + /// + /// Besides updating the private factory function field, we do the following: + /// + /// If the function is null, we drop our current connection (if any), and we will not make + /// any connections no matter what other properties are changed as long as it is still null. + /// + /// If it is non-null and we already have the same factory function, nothing happens. + /// + /// If it is non-null and we do not already have the same factory function, but other conditions + /// disallow making a connection, nothing happens. + /// + /// If it is non-null and we do not already have the same factory function, and no other + /// conditions disallow making a connection, we create an update processor and tell it to start. + /// In this case, we also reset to false if resetInitialized is + /// true. + /// + /// The returned task is immediately completed unless we are making a new connection, in which + /// case it is completed when the update processor signals success or failure. The task yields + /// a true result if we successfully made a connection or if we decided not to connect + /// because we are in offline mode. In other words, the result is true if + /// is true. + /// + /// a factory function or null + /// true if we should reset the initialized state (e.g. if we + /// are switching users + /// a task as described above + public Task SetUpdateProcessorFactory(Func updateProcessorFactory, bool resetInitialized) + { + return LockUtils.WithWriteLock(_lock, () => + { + if (_disposed || _updateProcessorFactory == updateProcessorFactory) + { + return Task.FromResult(false); + } + _updateProcessorFactory = updateProcessorFactory; + _updateProcessor?.Dispose(); + _updateProcessor = null; + if (resetInitialized) + { + _initialized = false; + } + return OpenOrCloseConnectionIfNecessary(); // not awaiting + }); + } + + /// + /// Tells the ConnectionManager that it can go ahead and connect if appropriate. + /// + /// a task which will yield true if this method results in a successful connection, or + /// if we are in offline mode and don't need to make a connection + public Task Start() + { + return LockUtils.WithWriteLock(_lock, () => + { + if (_started) + { + return Task.FromResult(_initialized); + } + _started = true; + return OpenOrCloseConnectionIfNecessary(); // not awaiting + }); + } + + public void Dispose() + { + IMobileUpdateProcessor processor = null; + LockUtils.WithWriteLock(_lock, () => + { + if (_disposed) + { + return; + } + processor = _updateProcessor; + _updateProcessor = null; + _updateProcessorFactory = null; + _disposed = true; + }); + processor?.Dispose(); + } + + // This method is called while _lock is being held. If we're starting up a new connection, we do + // *not* wait for it to succeed; we return a Task that will be completed once it succeeds. In all + // other cases we return an immediately-completed Task. + + private Task OpenOrCloseConnectionIfNecessary() + { + if (!_started) + { + return Task.FromResult(false); + } + if (_networkEnabled && !_forceOffline) + { + if (_updateProcessor == null && _updateProcessorFactory != null) + { + _updateProcessor = _updateProcessorFactory(); + return _updateProcessor.Start() + .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); + } + } + else + { + _updateProcessor?.Dispose(); + _updateProcessor = null; + _initialized = true; + return Task.FromResult(true); + } + return Task.FromResult(false); + } + + // When this method is called, we are no longer holding the lock. + + private bool SetInitializedIfUpdateProcessorStartedSuccessfully(Task task) + { + if (task.IsCompleted) + { + if (task.IsFaulted) + { + // Don't let exceptions from the update processor propagate up into the SDK. Just say we didn't initialize. + Log.ErrorFormat("Failed to initialize LaunchDarkly connection: {0}", Util.ExceptionMessage(task.Exception)); + return false; + } + var success = task.Result; + if (success) + { + LockUtils.WithWriteLock(_lock, () => + { + _initialized = true; + }); + return true; + } + } + return false; + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs similarity index 75% rename from src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs rename to src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs index b576892a..8e39748a 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileConnectionManager.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs @@ -3,18 +3,18 @@ namespace LaunchDarkly.Xamarin { - internal sealed class MobileConnectionManager : IConnectionManager + internal sealed class DefaultConnectivityStateManager : IConnectivityStateManager { - internal Action ConnectionChanged; + public Action ConnectionChanged { get; set; } - internal MobileConnectionManager() + internal DefaultConnectivityStateManager() { UpdateConnectedStatus(); Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } bool isConnected; - bool IConnectionManager.IsConnected + bool IConnectivityStateManager.IsConnected { get { return isConnected; } set diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 043f854e..65ce2cba 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -27,38 +27,34 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura } } - internal static IConnectionManager CreateConnectionManager(Configuration configuration) + internal static IConnectivityStateManager CreateConnectivityStateManager(Configuration configuration) { - return configuration._connectionManager ?? new MobileConnectionManager(); + return configuration._connectivityStateManager ?? new DefaultConnectivityStateManager(); } - internal static IMobileUpdateProcessor CreateUpdateProcessor(Configuration configuration, User user, - IFlagCacheManager flagCacheManager, TimeSpan? overridePollingInterval, - bool disableStreaming) + internal static Func CreateUpdateProcessorFactory(Configuration configuration, User user, + IFlagCacheManager flagCacheManager, bool inBackground) { - if (configuration.Offline) + return () => { - Log.InfoFormat("Starting LaunchDarkly client in offline mode"); - return new NullUpdateProcessor(); - } - - if (configuration._updateProcessorFactory != null) - { - return configuration._updateProcessorFactory(configuration, flagCacheManager, user); - } + if (configuration._updateProcessorFactory != null) + { + return configuration._updateProcessorFactory(configuration, flagCacheManager, user); + } - if (configuration.IsStreamingEnabled && !disableStreaming) - { - return new MobileStreamingProcessor(configuration, flagCacheManager, user, null); - } - else - { - var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); - return new MobilePollingProcessor(featureFlagRequestor, - flagCacheManager, - user, - overridePollingInterval ?? configuration.PollingInterval); - } + if (configuration.IsStreamingEnabled && !inBackground) + { + return new MobileStreamingProcessor(configuration, flagCacheManager, user, null); + } + else + { + var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); + return new MobilePollingProcessor(featureFlagRequestor, + flagCacheManager, + user, + inBackground ? configuration.BackgroundPollingInterval : configuration.PollingInterval); + } + }; } internal static IEventProcessor CreateEventProcessor(Configuration configuration) diff --git a/src/LaunchDarkly.XamarinSdk/IConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs similarity index 50% rename from src/LaunchDarkly.XamarinSdk/IConnectionManager.cs rename to src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs index fe7dbaf3..ec36679e 100644 --- a/src/LaunchDarkly.XamarinSdk/IConnectionManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs @@ -2,8 +2,9 @@ namespace LaunchDarkly.Xamarin { - public interface IConnectionManager + internal interface IConnectivityStateManager { bool IsConnected { get; set; } + Action ConnectionChanged { get; set; } } } diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index ed8a251f..fb4dc3d2 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -8,35 +8,82 @@ namespace LaunchDarkly.Xamarin { public interface ILdClient : IDisposable { - /// - /// Returns the current version number of the LaunchDarkly client. - /// - Version Version { get; } - /// /// Returns a boolean value indicating LaunchDarkly connection and flag state within the client. /// /// - /// When you first start the client, once Init or InitAsync has returned, - /// Initialized() should be true if and only if either 1. it connected to LaunchDarkly + /// When you first start the client, once Init or InitAsync has returned, + /// Initialized should be true if and only if either 1. it connected to LaunchDarkly /// and successfully retrieved flags, or 2. it started in offline mode so /// there's no need to connect to LaunchDarkly. So if the client timed out trying to - /// connect to LD, then Initialized is false (even if we do have cached flags). + /// connect to LD, then Initialized is false (even if we do have cached flags). /// If the client connected and got a 401 error, Initialized is false. /// This serves the purpose of letting the app know that - /// there was a problem of some kind. Initialized() will be temporarily false during the - /// time in between calling Identify and receiving the new user's flags. It will also be false - /// if you switch users with Identify and the client is unable + /// there was a problem of some kind. Initialized will be temporarily false during the + /// time in between calling Identify and receiving the new user's flags. It will also be false + /// if you switch users with Identify and the client is unable /// to get the new user's flags from LaunchDarkly. /// - /// true if the client has connected to LaunchDarkly and has flags or if the config is set offline, or false if it couldn't connect. - bool Initialized(); + bool Initialized { get; } + + /// + /// Indicates whether the SDK is configured to be always offline. + /// + /// + /// This is initially true if you set it to true in the configuration with . + /// However, you can change it at any time to allow the client to go online, or force it to go offline, + /// using or . + /// + /// When Offline is false, the SDK connects to LaunchDarkly if possible, but this does not guarantee + /// that the connection is successful. There is currently no mechanism to detect whether the SDK is currently + /// connected to LaunchDarkly. + /// + bool Offline { get; } /// - /// Tests whether the client is being used in offline mode. + /// Sets whether the SDK should be always offline. /// - /// true if the client is offline - bool IsOffline(); + /// + /// This is equivalent to , but as a synchronous method. + /// + /// If you set the property to true, any existing connection will be dropped, and the method immediately + /// returns false. + /// + /// If you set it to false when it was previously true, but no connection can be made because the network + /// is not available, the method immediately returns false, but the SDK will attempt to connect later if + /// the network becomes available. + /// + /// If you set it to false when it was previously true, and the network is available, the SDK will attempt + /// to connect to LaunchDarkly. If the connection succeeds within the interval maxWaitTime, the + /// method returns true. If the connection permanently fails (e.g. if the mobile key is invalid), the + /// method returns false. If the connection attempt is still in progress after maxWaitTime elapses, + /// the method returns false, but the connection might succeed later. + /// + /// true if the client should be always offline + /// the maximum length of time to wait for a connection + /// true if a new connection was successfully made + bool SetOffline(bool value, TimeSpan maxWaitTime); + + /// + /// Sets whether the SDK should be always offline. + /// + /// + /// This is equivalent to , but as an asynchronous method. + /// + /// If you set the property to true, any existing connection will be dropped, and the task immediately + /// yields false. + /// + /// If you set it to false when it was previously true, but no connection can be made because the network + /// is not available, the task immediately yields false, but the SDK will attempt to connect later if + /// the network becomes available. + /// + /// If you set it to false when it was previously true, and the network is available, the SDK will attempt + /// to connect to LaunchDarkly. If and when the connection succeeds, the task yields true. If and when the + /// connection permanently fails (e.g. if the mobile key is invalid), the task yields false. + /// + /// true if the client should be always offline + /// a task that yields true if a new connection was successfully made + Task SetOfflineAsync(bool value); /// /// Returns the boolean value of a feature flag for a given flag key. @@ -161,24 +208,6 @@ public interface ILdClient : IDisposable /// the name of the event void Track(string eventName); - /// - /// Gets or sets the online status of the client. - /// - /// - /// The setter is equivalent to calling ; if you are going from offline to - /// online, it does not wait until the connection has been established. If you want to wait for the - /// connection, call and then use await. - /// - /// true if online; otherwise, false. - bool Online { get; set; } - - /// - /// Sets the client to be online or not. - /// - /// a Task - /// true if the client should be online - Task SetOnlineAsync(bool value); - /// /// Returns a map from feature flag keys to feature flag values for the current user. /// @@ -220,37 +249,38 @@ public interface ILdClient : IDisposable event EventHandler FlagChanged; /// - /// Changes the current user. + /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates + /// an analytics event to tell LaunchDarkly about the user. /// /// - /// This both sets the current user for the purpose of flag evaluations and also generates an analytics event to - /// tell LaunchDarkly about the user. - /// - /// Identify waits and blocks the current thread until the SDK has received feature flag values for the - /// new user from LaunchDarkly. If you do not want to wait, consider . + /// This is equivalent to , but as a synchronous method. + /// + /// If the SDK is online, Identify waits to receive feature flag values for the new user from + /// LaunchDarkly. If it receives the new flag values before maxWaitTime has elapsed, it returns true. + /// If the timeout elapses, it returns false (although the SDK might still receive the flag values later). + /// If we do not need to request flags from LaunchDarkly because we are in offline mode, it returns true. + /// + /// If you do not want to wait, you can either set maxWaitTime to zero or call . /// /// the new user - void Identify(User user); + /// the maximum time to wait for the new flag values + /// true if new flag values were obtained + bool Identify(User user, TimeSpan maxWaitTime); /// - /// Changes the current user. + /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates + /// an analytics event to tell LaunchDarkly about the user. /// /// - /// This both sets the current user for the purpose of flag evaluations and also generates an analytics event to - /// tell LaunchDarkly about the user. - /// - /// IdentifyAsync is meant to be used from asynchronous code. It returns a Task that is resolved once the - /// SDK has received feature flag values for the new user from LaunchDarkly. - /// - /// - /// // Within asynchronous code, use await to wait for the task to be resolved - /// await client.IdentifyAsync(user); + /// This is equivalent to , but as an asynchronous method. /// - /// // Or, if you want to let the flag values be retrieved in the background instead of waiting: - /// Task.Run(() => client.IdentifyAsync(user)); - /// - /// the user to register - Task IdentifyAsync(User user); + /// If the SDK is online, the returned task is completed once the SDK has received feature flag values for the + /// new user from LaunchDarkly, or received an unrecoverable error; it yields true for success or false for an + /// error. If the SDK is offline, the returned task is completed immediately and yields true. + /// + /// the new user + /// a task that yields true if new flag values were obtained + Task IdentifyAsync(User user); /// /// Flushes all pending events. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index be708926..9e0a07d4 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -74,6 +74,6 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 90dce51d..0a03cb63 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -19,65 +19,62 @@ public sealed class LdClient : ILdClient { private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); - static volatile LdClient _instance; - static volatile User _user; - - bool initialized; - - static readonly object _createInstanceLock = new object(); static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; - readonly Configuration _config; - readonly SemaphoreSlim _connectionLock; + static readonly object _createInstanceLock = new object(); + static volatile LdClient _instance; + // Immutable client state + readonly Configuration _config; + readonly ConnectionManager _connectionManager; readonly IDeviceInfo deviceInfo; - readonly IConnectionManager connectionManager; + readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor eventProcessor; readonly IFlagCacheManager flagCacheManager; internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing readonly IPersistentStorage persister; - // These LdClient fields are not readonly because they change according to online status - internal volatile IMobileUpdateProcessor updateProcessor; - volatile bool _disableStreaming; - volatile bool _online; + // Mutable client state (some state is also in the ConnectionManager) + readonly ReaderWriterLockSlim _userLock = new ReaderWriterLockSlim(); + volatile User _user; + volatile bool _inBackground; /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot /// create a new client instance unless you first call on this one. - /// - /// Use the designated static methods or - /// to set this LdClient instance. /// - /// The LdClient instance. + /// + /// Use the static factory methods or + /// to set this LdClient instance. + /// public static LdClient Instance => _instance; + /// + /// Returns the current version number of the LaunchDarkly client. + /// + public static Version Version => MobileClientEnvironment.Instance.Version; + /// /// The Configuration instance used to setup the LdClient. /// - /// The Configuration instance. public Configuration Config => _config; /// - /// The User for the LdClient operations. + /// The current user for all SDK operations. /// - /// The User. - public User User => _user; + /// + /// This is initially the user specified for or + /// , but can be changed later with + /// or . + /// + public User User => LockUtils.WithReadLock(_userLock, () => _user); - /// - public bool Online - { - get => _online; - set - { - if (value == _online) - { - return; - } - AsyncUtils.WaitSafely(() => SetOnlineAsync(value)); - } - } + /// + public bool Offline => _connectionManager.ForceOffline; + + /// + public bool Initialized => _connectionManager.Initialized; /// /// Indicates which platform the SDK is built for. @@ -97,11 +94,18 @@ public bool Online /// that these platform-specific behaviors are not working correctly, you may want to check this property to /// make sure you are not for some reason running the .NET Standard SDK on a phone. /// - public static PlatformType PlatformType + public static PlatformType PlatformType => UserMetadata.PlatformType; + + /// + public event EventHandler FlagChanged { - get + add + { + flagChangedEventManager.FlagChanged += value; + } + remove { - return UserMetadata.PlatformType; + flagChangedEventManager.FlagChanged -= value; } } @@ -118,13 +122,6 @@ public static PlatformType PlatformType _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - _connectionLock = new SemaphoreSlim(1, 1); - - if (configuration.Offline) - { - initialized = true; - } - persister = Factory.CreatePersistentStorage(configuration); deviceInfo = Factory.CreateDeviceInfo(configuration); flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); @@ -132,16 +129,47 @@ public static PlatformType PlatformType _user = DecorateUser(user); flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); - connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null, false); eventProcessor = Factory.CreateEventProcessor(configuration); + _connectionManager = new ConnectionManager(); + _connectionManager.SetForceOffline(configuration.Offline); + if (configuration.Offline) + { + Log.InfoFormat("Starting LaunchDarkly client in offline mode"); + } + _connectionManager.SetUpdateProcessorFactory( + Factory.CreateUpdateProcessorFactory(configuration, User, flagCacheManager, _inBackground), + true + ); + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); - SetupConnectionManager(); + _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); + _connectivityStateManager.ConnectionChanged += networkAvailable => + { + Log.DebugFormat("Setting online to {0} due to a connectivity change event", networkAvailable); + _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result + }; + _connectionManager.SetNetworkEnabled(_connectivityStateManager.IsConnected); + BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; } + void Start(TimeSpan maxWaitTime) + { + var success = AsyncUtils.WaitSafely(() => _connectionManager.Start(), maxWaitTime); + if (!success) + { + Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", + maxWaitTime.TotalMilliseconds); + } + } + + async Task StartAsync() + { + await _connectionManager.Start(); + } + /// /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching feature flags. @@ -216,16 +244,7 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim } var c = CreateInstance(config, user); - - if (c.Online) - { - if (!c.StartUpdateProcessor(maxWaitTime)) - { - Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", - maxWaitTime.TotalMilliseconds); - } - } - + c.Start(maxWaitTime); return c; } @@ -243,19 +262,11 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim /// The client configuration object /// The user needed for client operations. Must not be null. /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static Task InitAsync(Configuration config, User user) + public static async Task InitAsync(Configuration config, User user) { var c = CreateInstance(config, user); - - if (c.Online) - { - Task t = c.StartUpdateProcessorAsync(); - return t.ContinueWith((result) => c); - } - else - { - return Task.FromResult(c); - } + await c.StartAsync(); + return c; } static LdClient CreateInstance(Configuration configuration, User user) @@ -269,110 +280,78 @@ static LdClient CreateInstance(Configuration configuration, User user) var c = new LdClient(configuration, user); _instance = c; - Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); + Log.InfoFormat("Initialized LaunchDarkly Client {0}", Version); return c; } } - void SetupConnectionManager() + /// + public bool SetOffline(bool value, TimeSpan maxWaitTime) { - if (connectionManager is MobileConnectionManager mobileConnectionManager) - { - mobileConnectionManager.ConnectionChanged += MobileConnectionManager_ConnectionChanged; - Log.InfoFormat("The mobile client connection changed online to {0}", - connectionManager.IsConnected); - } - _online = connectionManager.IsConnected; + return AsyncUtils.WaitSafely(() => SetOfflineAsync(value), maxWaitTime); } - public async Task SetOnlineAsync(bool value) + /// + public async Task SetOfflineAsync(bool value) { - await _connectionLock.WaitAsync(); - try - { - if (value == _online) - { - return; - } - _online = value; - if (_online) - { - await RestartUpdateProcessorAsync(Config.PollingInterval); - } - else - { - ClearUpdateProcessor(); - } - } - finally - { - _connectionLock.Release(); - } - - return; - } - - void MobileConnectionManager_ConnectionChanged(bool isOnline) - { - Log.DebugFormat("Setting online to {0} due to a connectivity change event", isOnline); - Online = isOnline; + await _connectionManager.SetForceOffline(value); } - /// + /// public bool BoolVariation(string key, bool defaultValue = false) { return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); } - /// + /// public string StringVariation(string key, string defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); } - /// + /// public float FloatVariation(string key, float defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); } - /// + /// public int IntVariation(string key, int defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); } - /// + /// public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); @@ -389,7 +368,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => var flag = flagCacheManager.FlagForUser(featureKey, User); if (flag == null) { - if (!Initialized()) + if (!Initialized) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, @@ -406,7 +385,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } else { - if (!Initialized()) + if (!Initialized) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); } @@ -440,51 +419,44 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => return result; } - /// + /// public IDictionary AllFlags() { return flagCacheManager.FlagsForUser(User) .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); } - /// + /// public void Track(string eventName, ImmutableJsonValue data) { eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); } - /// + /// public void Track(string eventName) { Track(eventName, ImmutableJsonValue.Null); } - /// - public bool Initialized() - { - return initialized; - } - - /// - public bool IsOffline() - { - return !_online; - } - - /// + /// public void Flush() { eventProcessor.Flush(); } - /// - public void Identify(User user) + /// + public bool Identify(User user, TimeSpan maxWaitTime) { - AsyncUtils.WaitSafely(() => IdentifyAsync(user)); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return AsyncUtils.WaitSafely(() => IdentifyAsync(user), maxWaitTime); } - /// - public async Task IdentifyAsync(User user) + /// + public async Task IdentifyAsync(User user) { if (user == null) { @@ -493,71 +465,17 @@ public async Task IdentifyAsync(User user) User newUser = DecorateUser(user); - await _connectionLock.WaitAsync(); - try + LockUtils.WithWriteLock(_userLock, () => { _user = newUser; - initialized = false; - await RestartUpdateProcessorAsync(Config.PollingInterval); - } - finally - { - _connectionLock.Release(); - } + }); eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(newUser)); - } - - bool StartUpdateProcessor(TimeSpan maxWaitTime) - { - if (Online) - { - var successfulConnection = AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); - initialized = successfulConnection; - return successfulConnection; - } - else - { - return true; - } - } - - async Task StartUpdateProcessorAsync() - { - if (Online) - { - var successfulConnection = await updateProcessor.Start(); - if (successfulConnection) - { - initialized = true; - } - return successfulConnection; - } - else - { - return true; - } - } - - async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) - { - ClearAndSetUpdateProcessor(pollingInterval); - await StartUpdateProcessorAsync(); - } - void ClearAndSetUpdateProcessor(TimeSpan pollingInterval) - { - ClearUpdateProcessor(); - updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval, _disableStreaming); - } - - void ClearUpdateProcessor() - { - if (updateProcessor != null) - { - updateProcessor.Dispose(); - updateProcessor = new NullUpdateProcessor(); - } + return await _connectionManager.SetUpdateProcessorFactory( + Factory.CreateUpdateProcessorFactory(_config, user, flagCacheManager, _inBackground), + true + ); } User DecorateUser(User user) @@ -604,7 +522,7 @@ void Dispose(bool disposing) Log.InfoFormat("Shutting down the LaunchDarkly client"); BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; - updateProcessor.Dispose(); + _connectionManager.Dispose(); eventProcessor.Dispose(); // Reset the static Instance to null *if* it was referring to this instance @@ -617,31 +535,9 @@ void Dispose(bool disposing) Interlocked.CompareExchange(ref _instance, null, this); } - /// - public Version Version - { - get - { - return MobileClientEnvironment.Instance.Version; - } - } - - /// - public event EventHandler FlagChanged - { - add - { - flagChangedEventManager.FlagChanged += value; - } - remove - { - flagChangedEventManager.FlagChanged -= value; - } - } - internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) { - AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); + _ = OnBackgroundModeChangedAsync(sender, args); // do not wait for the result } internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) @@ -649,23 +545,23 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh Log.DebugFormat("Background mode is changing to {0}", args.IsInBackground); if (args.IsInBackground) { - ClearUpdateProcessor(); - _disableStreaming = true; - if (Config.EnableBackgroundUpdating) - { - Log.Debug("Background updating is enabled, starting polling processor"); - await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); - } - else + _inBackground = true; + if (!Config.EnableBackgroundUpdating) { Log.Debug("Background updating is disabled"); + await _connectionManager.SetUpdateProcessorFactory(null, false); + return; } + Log.Debug("Background updating is enabled, starting polling processor"); } else { - _disableStreaming = false; - await RestartUpdateProcessorAsync(Config.PollingInterval); + _inBackground = false; } + await _connectionManager.SetUpdateProcessorFactory( + Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, _inBackground), + false // don't reset initialized state because the user is still the same + ); } } } diff --git a/src/LaunchDarkly.XamarinSdk/LockUtils.cs b/src/LaunchDarkly.XamarinSdk/LockUtils.cs new file mode 100644 index 00000000..338c35ea --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/LockUtils.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; + +namespace LaunchDarkly.Xamarin +{ + internal static class LockUtils + { + public static T WithReadLock(ReaderWriterLockSlim rwLock, Func fn) + { + rwLock.EnterReadLock(); + try + { + return fn(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public static T WithWriteLock(ReaderWriterLockSlim rwLock, Func fn) + { + rwLock.EnterWriteLock(); + try + { + return fn(); + } + finally + { + rwLock.ExitWriteLock(); + } + } + + public static void WithWriteLock(ReaderWriterLockSlim rwLock, Action a) + { + rwLock.EnterWriteLock(); + try + { + a(); + } + finally + { + rwLock.ExitWriteLock(); + } + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index a76552f3..4c9864f7 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -93,9 +93,7 @@ private async Task UpdateTaskAsync() } catch (Exception ex) { - Log.ErrorFormat("Error Updating features: '{0}'", - ex, - Util.ExceptionMessage(ex)); + Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(ex)); } } diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 89490a80..77a30a44 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -46,9 +46,9 @@ bool IMobileUpdateProcessor.Initialized() return _streamManager.Initialized; } - Task IMobileUpdateProcessor.Start() + async Task IMobileUpdateProcessor.Start() { - return _streamManager.Start(); + return await _streamManager.Start(); } #endregion @@ -100,8 +100,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT } catch (Exception ex) { - Log.ErrorFormat("Error parsing PATCH message {0}: '{1}'", - ex, messageData, Util.ExceptionMessage(ex)); + Log.ErrorFormat("Error parsing PATCH message {0}: {1}", messageData, Util.ExceptionMessage(ex)); } break; } @@ -116,8 +115,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT } catch (Exception ex) { - Log.ErrorFormat("Error parsing DELETE message {0}: '{1}'", - ex, messageData, Util.ExceptionMessage(ex)); + Log.ErrorFormat("Error parsing DELETE message {0}: {1}", messageData, Util.ExceptionMessage(ex)); } break; } diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs index f352a1ff..bdd99699 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -26,6 +26,107 @@ static Resource() public static void UpdateIdValues() { + global::LaunchDarkly.XamarinSdk.Resource.Attribute.font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.font; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderCerts; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderPackage; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderQuery; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontWeight; + global::LaunchDarkly.XamarinSdk.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::LaunchDarkly.XamarinSdk.Resource.Color.notification_action_color_filter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_action_color_filter; + global::LaunchDarkly.XamarinSdk.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_icon_bg_color; + global::LaunchDarkly.XamarinSdk.Resource.Color.ripple_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_light; + global::LaunchDarkly.XamarinSdk.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_text_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_subtext_size; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad; + global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_action_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_action_background; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_icon_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_icon_background; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_tile_bg; + global::LaunchDarkly.XamarinSdk.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_container; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_divider; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_image; + global::LaunchDarkly.XamarinSdk.Resource.Id.action_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_text; + global::LaunchDarkly.XamarinSdk.Resource.Id.actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.actions; + global::LaunchDarkly.XamarinSdk.Resource.Id.async = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.async; + global::LaunchDarkly.XamarinSdk.Resource.Id.blocking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.blocking; + global::LaunchDarkly.XamarinSdk.Resource.Id.chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.chronometer; + global::LaunchDarkly.XamarinSdk.Resource.Id.forever = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.forever; + global::LaunchDarkly.XamarinSdk.Resource.Id.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon; + global::LaunchDarkly.XamarinSdk.Resource.Id.icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon_group; + global::LaunchDarkly.XamarinSdk.Resource.Id.info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.info; + global::LaunchDarkly.XamarinSdk.Resource.Id.italic = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.italic; + global::LaunchDarkly.XamarinSdk.Resource.Id.line1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line1; + global::LaunchDarkly.XamarinSdk.Resource.Id.line3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line3; + global::LaunchDarkly.XamarinSdk.Resource.Id.normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.normal; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_background; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column; + global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column_container; + global::LaunchDarkly.XamarinSdk.Resource.Id.right_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_icon; + global::LaunchDarkly.XamarinSdk.Resource.Id.right_side = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_side; + global::LaunchDarkly.XamarinSdk.Resource.Id.tag_transition_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tag_transition_group; + global::LaunchDarkly.XamarinSdk.Resource.Id.text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text; + global::LaunchDarkly.XamarinSdk.Resource.Id.text2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text2; + global::LaunchDarkly.XamarinSdk.Resource.Id.time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.time; + global::LaunchDarkly.XamarinSdk.Resource.Id.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title; + global::LaunchDarkly.XamarinSdk.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action_tombstone; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_custom_big; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_icon_group; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_time; + global::LaunchDarkly.XamarinSdk.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index be54844e..dc7caa71 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -85,8 +85,7 @@ public void InitCanTimeOutSync() var config = BaseConfig(server).IsStreamingEnabled(false).Build(); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { - Assert.False(Initialized(client)); - Assert.False(client.Initialized()); + Assert.False(client.Initialized); Assert.Null(client.StringVariation(_flagData1.First().Key, null)); Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && m.Text == "Client did not successfully initialize within 200 milliseconds."); @@ -105,20 +104,11 @@ public void InitFailsOn401Sync(UpdateMode mode) using (var log = new LogSinkScope()) { - try - { - var config = BaseConfig(server, mode).Build(); - using (var client = TestUtil.CreateClient(config, _user)) { } - } - catch (Exception e) + var config = BaseConfig(server, mode).Build(); + using (var client = TestUtil.CreateClient(config, _user)) { - // Currently the exact class of this exception is undefined: the polling processor throws - // LaunchDarkly.Client.UnsuccessfulResponseException, while the streaming processor throws - // a lower-level exception that is defined by LaunchDarkly.EventSource. - Assert.Contains("401", e.Message); - return; + Assert.False(client.Initialized); } - throw new Exception("Expected exception from LdClient.Init"); } }); } @@ -140,8 +130,7 @@ await WithServerAsync(async server => // will complete successfully with an uninitialized client. using (var client = await TestUtil.CreateClientAsync(config, _user)) { - Assert.False(Initialized(client)); - Assert.False(client.Initialized()); + Assert.False(client.Initialized); } } }); @@ -155,7 +144,7 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - var config = BaseConfig(server).IsStreamingEnabled(false).Build(); + var config = BaseConfig(server, UpdateMode.Polling).Build(); var name = "Sue"; var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); @@ -196,7 +185,9 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) server.Reset(); SetupResponse(server, _flagData2, mode); - client.Identify(_otherUser); + var success = client.Identify(_otherUser, TimeSpan.FromSeconds(5)); + Assert.True(success); + Assert.True(client.Initialized); Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes VerifyRequest(server, mode); @@ -224,7 +215,9 @@ await WithServerAsync(async server => server.Reset(); SetupResponse(server, _flagData2, mode); - await client.IdentifyAsync(_otherUser); + var success = await client.IdentifyAsync(_otherUser); + Assert.True(success); + Assert.True(client.Initialized); Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes VerifyRequest(server, mode); @@ -234,6 +227,32 @@ await WithServerAsync(async server => }); } + [Theory(Skip = SkipIfCannotCreateHttpServer)] + [MemberData(nameof(PollingAndStreaming))] + public void IdentifyCanTimeOutSync(UpdateMode mode) + { + WithServer(server => + { + SetupResponse(server, _flagData1, mode); + + var config = BaseConfig(server, mode).Build(); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyRequest(server, mode); + VerifyFlagValues(client, _flagData1); + var user1RequestPath = server.GetLastRequest().Path; + + server.Reset(); + server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); + + var success = client.Identify(_otherUser, TimeSpan.FromMilliseconds(100)); + Assert.False(success); + Assert.False(client.Initialized); + Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + } + }); + } + [Fact(Skip = SkipIfCannotCreateHttpServer)] public void OfflineClientUsesCachedFlagsSync() { @@ -244,14 +263,13 @@ public void OfflineClientUsesCachedFlagsSync() ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); + Assert.True(client.Initialized); } // At this point the SDK should have written the flags to persistent storage for this user key. @@ -263,7 +281,7 @@ public void OfflineClientUsesCachedFlagsSync() using (var client = TestUtil.CreateClient(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); + Assert.True(client.Initialized); } } finally @@ -283,8 +301,7 @@ await WithServerAsync(async server => ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -301,7 +318,7 @@ await WithServerAsync(async server => using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized()); + Assert.True(client.Initialized); } } finally @@ -321,8 +338,7 @@ public void OfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = TestUtil.CreateClient(config, _user)) @@ -360,8 +376,7 @@ await WithServerAsync(async server => ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -389,11 +404,6 @@ await WithServerAsync(async server => }); } - private bool Initialized(LdClient client) - { - return client.Online && client.updateProcessor.Initialized(); - } - [Fact(Skip = SkipIfCannotCreateHttpServer)] public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() { @@ -404,8 +414,7 @@ public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor( ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = TestUtil.CreateClient(config, _user)) @@ -447,8 +456,7 @@ await WithServerAsync(async server => ClearCachedFlags(_user); try { - var config = BaseConfig(server) - .IsStreamingEnabled(false) + var config = BaseConfig(server, UpdateMode.Polling) .PersistFlagValues(true) .Build(); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -480,6 +488,30 @@ await WithServerAsync(async server => }); } + [Theory(Skip = SkipIfCannotCreateHttpServer)] + [MemberData(nameof(PollingAndStreaming))] + public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) + { + await WithServerAsync(async server => + { + ClearCachedFlags(_user); + var config = BaseConfig(server, mode) + .Offline(true) + .PersistFlagValues(false) + .Build(); + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + VerifyNoFlagValues(client, _flagData1); + + SetupResponse(server, _flagData1, mode); + + await client.SetOfflineAsync(false); + + VerifyFlagValues(client, _flagData1); + } + }); + } + private IConfigurationBuilder BaseConfig(FluentMockServer server) { return Configuration.BuilderInternal(_mobileKey) @@ -523,13 +555,22 @@ private void VerifyRequest(FluentMockServer server, UpdateMode mode) private void VerifyFlagValues(ILdClient client, IDictionary flags) { - Assert.True(Initialized((LdClient) client)); + Assert.True(client.Initialized); foreach (var e in flags) { Assert.Equal(e.Value, client.StringVariation(e.Key, null)); } } + private void VerifyNoFlagValues(ILdClient client, IDictionary flags) + { + Assert.True(client.Initialized); + foreach (var e in flags) + { + Assert.Null(client.StringVariation(e.Key, null)); + } + } + private JToken FlagJson(string key, string value) { var o = new JObject(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 039d1821..0679b318 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -1,4 +1,5 @@ -using LaunchDarkly.Client; +using System; +using LaunchDarkly.Client; using Newtonsoft.Json.Linq; using Xunit; @@ -22,7 +23,7 @@ public void IdentifySendsIdentifyEvent() using (LdClient client = MakeClient(user, "{}")) { User user1 = User.WithKey("userkey1"); - client.Identify(user1); + client.Identify(user1, TimeSpan.FromSeconds(1)); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), // there's always an initial identify event e => CheckIdentifyEvent(e, user1)); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 877e273e..37e4a759 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -54,7 +54,8 @@ public void IdentifyUpdatesTheUser() using (var client = Client()) { var updatedUser = User.WithKey("some new key"); - client.Identify(updatedUser); + var success = client.Identify(updatedUser, TimeSpan.FromSeconds(1)); + Assert.True(success); Assert.Equal(client.User.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes } } @@ -65,7 +66,7 @@ public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() [Fact] public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user))); + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(1)))); private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) { @@ -106,7 +107,7 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func @@ -117,13 +118,13 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func(() => client.Identify(null)); + Assert.Throws(() => client.Identify(null, TimeSpan.Zero)); } } @@ -175,31 +176,18 @@ public void CanCreateNewClientAfterDisposingOfSharedInstance() }); } - [Fact] - public void ConnectionManagerShouldKnowIfOnlineOrNot() - { - using (var client = Client()) - { - var connMgr = client.Config._connectionManager as MockConnectionManager; - connMgr.ConnectionChanged += (bool obj) => client.Online = obj; - connMgr.Connect(true); - Assert.False(client.IsOffline()); - connMgr.Connect(false); - Assert.False(client.Online); - } - } - [Fact] public void ConnectionChangeShouldStopUpdateProcessor() { var mockUpdateProc = new MockPollingProcessor(null); + var mockConnectivityStateManager = new MockConnectivityStateManager(true); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .UpdateProcessorFactory(mockUpdateProc.AsFactory()).Build(); + .UpdateProcessorFactory(mockUpdateProc.AsFactory()) + .ConnectivityStateManager(mockConnectivityStateManager) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { - var connMgr = client.Config._connectionManager as MockConnectionManager; - connMgr.ConnectionChanged += (bool obj) => client.Online = obj; - connMgr.Connect(false); + mockConnectivityStateManager.Connect(false); Assert.False(mockUpdateProc.IsRunning); } } @@ -241,7 +229,7 @@ public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { - client.Identify(userWithNullKey); + client.Identify(userWithNullKey, TimeSpan.FromSeconds(1)); Assert.Equal(uniqueId, client.User.Key); Assert.True(client.User.Anonymous); } @@ -256,7 +244,7 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { - client.Identify(userWithEmptyKey); + client.Identify(userWithEmptyKey, TimeSpan.FromSeconds(1)); Assert.Equal(uniqueId, client.User.Key); Assert.True(client.User.Anonymous); } @@ -281,7 +269,7 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { - client.Identify(user); + client.Identify(user, TimeSpan.FromSeconds(1)); User newUser = client.User; Assert.NotEqual(user.Key, newUser.Key); Assert.Equal(user.Avatar, newUser.Avatar); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 0b5b470b..9f887898 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -31,7 +31,7 @@ public MobileStreamingProcessorTests() eventSourceFactory = new TestEventSourceFactory(mockEventSource); mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); configBuilder = Configuration.BuilderInternal("someKey") - .ConnectionManager(new MockConnectionManager(true)) + .ConnectivityStateManager(new MockConnectivityStateManager(true)) .FlagCacheManager(mockFlagCacheMgr) .IsStreamingEnabled(true); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 25e94762..a72bae29 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -6,11 +6,11 @@ namespace LaunchDarkly.Xamarin.Tests { - internal class MockConnectionManager : IConnectionManager + internal class MockConnectivityStateManager : IConnectivityStateManager { - public Action ConnectionChanged; + public Action ConnectionChanged { get; set; } - public MockConnectionManager(bool isOnline) + public MockConnectivityStateManager(bool isOnline) { isConnected = isOnline; } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 4b5e3ce3..75efc5f6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -140,7 +140,7 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe return Configuration.BuilderInternal(appKey) .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) - .ConnectionManager(new MockConnectionManager(true)) + .ConnectivityStateManager(new MockConnectivityStateManager(true)) .EventProcessor(new MockEventProcessor()) .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) .PersistentStorage(new MockPersistentStorage()) From e2ce9b0e7e78b60a7ba64e4256b0ae414d27ffd0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 26 Aug 2019 18:58:11 -0700 Subject: [PATCH 241/499] remove HttpClientTimeout in configuration, use ConnectionTimeout (#77) --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 14 +++------- .../ConfigurationBuilder.cs | 27 ++++++++----------- .../FeatureFlagRequestor.cs | 4 +-- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 1d3949a3..d898c71d 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -27,7 +27,6 @@ public sealed class Configuration private readonly int _eventCapacity; private readonly Uri _eventsUri; private readonly HttpMessageHandler _httpMessageHandler; - private readonly TimeSpan _httpClientTimeout; private readonly bool _inlineUsersInEvents; private readonly bool _isStreamingEnabled; private readonly string _mobileKey; @@ -80,7 +79,7 @@ public sealed class Configuration /// /// The connection timeout to the LaunchDarkly server. /// - public TimeSpan ConnectionTimeout { get; internal set; } + public TimeSpan ConnectionTimeout => _connectionTimeout; /// /// Whether to enable feature flag updates when the application is running in the background. @@ -130,11 +129,6 @@ public sealed class Configuration /// public HttpMessageHandler HttpMessageHandler => _httpMessageHandler; - /// - /// The connection timeout. The default value is 10 seconds. - /// - public TimeSpan HttpClientTimeout => _httpClientTimeout; - /// /// Sets whether to include full user details in every analytics event. /// @@ -250,7 +244,6 @@ public sealed class Configuration internal static readonly TimeSpan DefaultEventFlushInterval = TimeSpan.FromSeconds(5); internal static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); internal static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); - internal static readonly TimeSpan DefaultHttpClientTimeout = TimeSpan.FromSeconds(10); internal static readonly int DefaultUserKeysCapacity = 1000; internal static readonly TimeSpan DefaultUserKeysFlushInterval = TimeSpan.FromMinutes(5); internal static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(60); @@ -329,7 +322,6 @@ internal Configuration(ConfigurationBuilder builder) _eventCapacity = builder._eventCapacity; _eventsUri = builder._eventsUri; _httpMessageHandler = builder._httpMessageHandler; - _httpClientTimeout = builder._httpClientTimeout; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; @@ -365,7 +357,7 @@ private class EventProcessorAdapter : IEventProcessorConfiguration public int EventCapacity => Config.EventCapacity; public TimeSpan EventFlushInterval => Config.EventFlushInterval; public Uri EventsUri => Config.EventsUri; - public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; + public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; public bool InlineUsersInEvents => Config.InlineUsersInEvents; public IImmutableSet PrivateAttributeNames => Config.PrivateAttributeNames; public TimeSpan ReadTimeout => Config.ReadTimeout; @@ -386,7 +378,7 @@ private class StreamManagerAdapter : IStreamManagerConfiguration internal Configuration Config { get; set; } public string HttpAuthorizationKey => Config.MobileKey; public HttpMessageHandler HttpMessageHandler => Config.HttpMessageHandler; - public TimeSpan HttpClientTimeout => Config.HttpClientTimeout; + public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; public TimeSpan ReadTimeout => Config.ReadTimeout; public TimeSpan ReconnectTime => Config.ReconnectTime; } diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 72cdd11a..be9cc152 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -61,6 +61,16 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder BaseUri(Uri baseUri); + /// + /// Sets the connection timeout for all HTTP requests. + /// + /// + /// The default value is 10 seconds. + /// + /// the connection timeout + /// the same builder + IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout); + /// /// Set to true if LaunchDarkly should provide additional information about how flag values were /// calculated. @@ -119,13 +129,6 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler); - /// - /// Sets the connection timeout. The default value is 10 seconds. - /// - /// the connection timeout - /// the same builder - IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout); - /// /// Sets whether to include full user details in every analytics event. /// @@ -257,14 +260,13 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal bool _allAttributesPrivate = false; internal TimeSpan _backgroundPollingInterval; internal Uri _baseUri = Configuration.DefaultUri; - internal TimeSpan _connectionTimeout; + internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; internal bool _enableBackgroundUpdating; internal bool _evaluationReasons = false; internal int _eventCapacity = Configuration.DefaultEventCapacity; internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; internal Uri _eventsUri = Configuration.DefaultEventsUri; internal HttpMessageHandler _httpMessageHandler = PlatformSpecific.Http.GetHttpMessageHandler(); // see Http.shared.cs - internal TimeSpan _httpClientTimeout = Configuration.DefaultHttpClientTimeout; internal bool _inlineUsersInEvents = false; internal bool _isStreamingEnabled = true; internal string _mobileKey; @@ -305,7 +307,6 @@ internal ConfigurationBuilder(Configuration copyFrom) _eventFlushInterval = copyFrom.EventFlushInterval; _eventsUri = copyFrom.EventsUri; _httpMessageHandler = copyFrom.HttpMessageHandler; - _httpClientTimeout = copyFrom.HttpClientTimeout; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _isStreamingEnabled = copyFrom.IsStreamingEnabled; _mobileKey = copyFrom.MobileKey; @@ -395,12 +396,6 @@ public IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHa return this; } - public IConfigurationBuilder HttpClientTimeout(TimeSpan httpClientTimeout) - { - _httpClientTimeout = httpClientTimeout; - return this; - } - public IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) { _inlineUsersInEvents = inlineUsersInEvents; diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index dd840484..db5ae4ae 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -79,7 +79,7 @@ private Uri MakeRequestUriWithPath(string path) private async Task MakeRequest(HttpRequestMessage request) { - using (var cts = new CancellationTokenSource(_configuration.HttpClientTimeout)) + using (var cts = new CancellationTokenSource(_configuration.ConnectionTimeout)) { if (_etag != null) { @@ -116,7 +116,7 @@ private async Task MakeRequest(HttpRequestMessage request) } //Otherwise this was a request timeout. throw new TimeoutException("Get item with URL: " + request.RequestUri + - " timed out after : " + _configuration.HttpClientTimeout); + " timed out after : " + _configuration.ConnectionTimeout); } } } From be3e3173ceef6077f01490c56a9cd32e58855656 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 27 Aug 2019 11:21:27 -0700 Subject: [PATCH 242/499] misc fixes to background mode logic, add tests for it (#78) --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 2 + .../ConfigurationBuilder.cs | 28 +- .../DefaultBackgroundModeManager.cs | 20 ++ src/LaunchDarkly.XamarinSdk/Factory.cs | 3 +- .../IBackgroundModeManager.cs | 10 + src/LaunchDarkly.XamarinSdk/LdClient.cs | 34 ++- .../MobilePollingProcessor.cs | 36 ++- .../BackgroundDetection.shared.cs | 2 +- .../LDClientEndToEndTests.cs | 270 +++++++----------- .../MobilePollingProcessorTests.cs | 3 +- .../MockComponents.cs | 11 + 11 files changed, 226 insertions(+), 193 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs create mode 100644 src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index d898c71d..a3cfa2c3 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -42,6 +42,7 @@ public sealed class Configuration private readonly TimeSpan _userKeysFlushInterval; // Settable only for testing + internal readonly IBackgroundModeManager _backgroundModeManager; internal readonly IConnectivityStateManager _connectivityStateManager; internal readonly IDeviceInfo _deviceInfo; internal readonly IEventProcessor _eventProcessor; @@ -337,6 +338,7 @@ internal Configuration(ConfigurationBuilder builder) _userKeysCapacity = builder._userKeysCapacity; _userKeysFlushInterval = builder._userKeysFlushInterval; + _backgroundModeManager = builder._backgroundModeManager; _connectivityStateManager = builder._connectivityStateManager; _deviceInfo = builder._deviceInfo; _eventProcessor = builder._eventProcessor; diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index be9cc152..24d751f6 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -71,6 +71,19 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout); + /// Sets whether to enable feature flag polling when the application is in the background. + /// + /// + /// By default, on Android and iOS the SDK can still receive feature flag updates when an application + /// is in the background, but it will use polling rather than maintaining a streaming connection (and + /// will use rather than ). + /// If you set EnableBackgroundUpdating to false, it will not check for feature flag updates + /// until the application returns to the foreground. + /// + /// true if background updating should be allowed + /// the same builder + IConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating); + /// /// Set to true if LaunchDarkly should provide additional information about how flag values were /// calculated. @@ -261,7 +274,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal TimeSpan _backgroundPollingInterval; internal Uri _baseUri = Configuration.DefaultUri; internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; - internal bool _enableBackgroundUpdating; + internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; internal int _eventCapacity = Configuration.DefaultEventCapacity; internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; @@ -282,6 +295,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; // Internal properties only settable for testing + internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; internal IDeviceInfo _deviceInfo; internal IEventProcessor _eventProcessor; @@ -486,6 +500,18 @@ public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterva // and then call these methods before you have called any of the public methods (since // only these methods return ConfigurationBuilder rather than IConfigurationBuilder). + internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backgroundModeManager) + { + _backgroundModeManager = backgroundModeManager; + return this; + } + + internal IConfigurationBuilder BackgroundPollingIntervalWithoutMinimum(TimeSpan backgroundPollingInterval) + { + _backgroundPollingInterval = backgroundPollingInterval; + return this; + } + internal ConfigurationBuilder ConnectivityStateManager(IConnectivityStateManager connectivityStateManager) { _connectivityStateManager = connectivityStateManager; diff --git a/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs new file mode 100644 index 00000000..0f35b823 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs @@ -0,0 +1,20 @@ +using System; +using LaunchDarkly.Xamarin.PlatformSpecific; + +namespace LaunchDarkly.Xamarin +{ + internal class DefaultBackgroundModeManager : IBackgroundModeManager + { + public event EventHandler BackgroundModeChanged + { + add + { + BackgroundDetection.BackgroundModeChanged += value; + } + remove + { + BackgroundDetection.BackgroundModeChanged -= value; + } + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 65ce2cba..c77846f4 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -52,7 +52,8 @@ internal static Func CreateUpdateProcessorFactory(Config return new MobilePollingProcessor(featureFlagRequestor, flagCacheManager, user, - inBackground ? configuration.BackgroundPollingInterval : configuration.PollingInterval); + inBackground ? configuration.BackgroundPollingInterval : configuration.PollingInterval, + inBackground ? configuration.BackgroundPollingInterval : TimeSpan.Zero); } }; } diff --git a/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs new file mode 100644 index 00000000..205d4061 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs @@ -0,0 +1,10 @@ +using System; +using LaunchDarkly.Xamarin.PlatformSpecific; + +namespace LaunchDarkly.Xamarin +{ + internal interface IBackgroundModeManager + { + event EventHandler BackgroundModeChanged; + } +} diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 0a03cb63..a336dbbf 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -28,6 +28,7 @@ public sealed class LdClient : ILdClient // Immutable client state readonly Configuration _config; readonly ConnectionManager _connectionManager; + readonly IBackgroundModeManager _backgroundModeManager; readonly IDeviceInfo deviceInfo; readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor eventProcessor; @@ -36,7 +37,7 @@ public sealed class LdClient : ILdClient readonly IPersistentStorage persister; // Mutable client state (some state is also in the ConnectionManager) - readonly ReaderWriterLockSlim _userLock = new ReaderWriterLockSlim(); + readonly ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim(); volatile User _user; volatile bool _inBackground; @@ -68,7 +69,7 @@ public sealed class LdClient : ILdClient /// , but can be changed later with /// or . /// - public User User => LockUtils.WithReadLock(_userLock, () => _user); + public User User => LockUtils.WithReadLock(_stateLock, () => _user); /// public bool Offline => _connectionManager.ForceOffline; @@ -152,7 +153,8 @@ public event EventHandler FlagChanged }; _connectionManager.SetNetworkEnabled(_connectivityStateManager.IsConnected); - BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; + _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); + _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; } void Start(TimeSpan maxWaitTime) @@ -465,7 +467,7 @@ public async Task IdentifyAsync(User user) User newUser = DecorateUser(user); - LockUtils.WithWriteLock(_userLock, () => + LockUtils.WithWriteLock(_stateLock, () => { _user = newUser; }); @@ -521,7 +523,7 @@ void Dispose(bool disposing) { Log.InfoFormat("Shutting down the LaunchDarkly client"); - BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; + _backgroundModeManager.BackgroundModeChanged -= OnBackgroundModeChanged; _connectionManager.Dispose(); eventProcessor.Dispose(); @@ -542,10 +544,20 @@ internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventA internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) { - Log.DebugFormat("Background mode is changing to {0}", args.IsInBackground); - if (args.IsInBackground) + var goingIntoBackground = args.IsInBackground; + var wasInBackground = LockUtils.WithWriteLock(_stateLock, () => + { + var oldValue = _inBackground; + _inBackground = goingIntoBackground; + return oldValue; + }); + if (goingIntoBackground == wasInBackground) + { + return; + } + Log.DebugFormat("Background mode is changing to {0}", goingIntoBackground); + if (goingIntoBackground) { - _inBackground = true; if (!Config.EnableBackgroundUpdating) { Log.Debug("Background updating is disabled"); @@ -554,12 +566,8 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh } Log.Debug("Background updating is enabled, starting polling processor"); } - else - { - _inBackground = false; - } await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, _inBackground), + Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, goingIntoBackground), false // don't reset initialized state because the user is still the same ); } diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index 4c9864f7..0ed96ee3 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -15,8 +15,9 @@ internal sealed class MobilePollingProcessor : IMobileUpdateProcessor private readonly IFeatureFlagRequestor _featureFlagRequestor; private readonly IFlagCacheManager _flagCacheManager; - private readonly User user; - private readonly TimeSpan pollingInterval; + private readonly User _user; + private readonly TimeSpan _pollingInterval; + private readonly TimeSpan _initialDelay; private readonly TaskCompletionSource _startTask; private readonly TaskCompletionSource _stopTask; private const int UNINITIALIZED = 0; @@ -27,23 +28,32 @@ internal sealed class MobilePollingProcessor : IMobileUpdateProcessor internal MobilePollingProcessor(IFeatureFlagRequestor featureFlagRequestor, IFlagCacheManager cacheManager, User user, - TimeSpan pollingInterval) + TimeSpan pollingInterval, + TimeSpan initialDelay) { this._featureFlagRequestor = featureFlagRequestor; this._flagCacheManager = cacheManager; - this.user = user; - this.pollingInterval = pollingInterval; + this._user = user; + this._pollingInterval = pollingInterval; + this._initialDelay = initialDelay; _startTask = new TaskCompletionSource(); _stopTask = new TaskCompletionSource(); } Task IMobileUpdateProcessor.Start() { - if (pollingInterval.Equals(TimeSpan.Zero)) + if (_pollingInterval.Equals(TimeSpan.Zero)) throw new Exception("Timespan for polling can't be zero"); - Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0}", pollingInterval); - + if (_initialDelay > TimeSpan.Zero) + { + Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0} (waiting {1} first)", _pollingInterval, _initialDelay); + } + else + { + Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0}", _pollingInterval); + } + Task.Run(() => UpdateTaskLoopAsync()); return _startTask.Task; } @@ -55,10 +65,14 @@ bool IMobileUpdateProcessor.Initialized() private async Task UpdateTaskLoopAsync() { + if (_initialDelay > TimeSpan.Zero) + { + await Task.Delay(_initialDelay); + } while (!_disposed) { await UpdateTaskAsync(); - await Task.Delay(pollingInterval); + await Task.Delay(_pollingInterval); } } @@ -71,9 +85,9 @@ private async Task UpdateTaskAsync() { var flagsAsJsonString = response.jsonResponse; var flagsDictionary = JsonConvert.DeserializeObject>(flagsAsJsonString); - _flagCacheManager.CacheFlagsFromService(flagsDictionary, user); + _flagCacheManager.CacheFlagsFromService(flagsDictionary, _user); - //We can't use bool in CompareExchange because it is not a reference type. + // We can't use bool in CompareExchange because it is not a reference type. if (Interlocked.CompareExchange(ref _initialized, INITIALIZED, UNINITIALIZED) == UNINITIALIZED) { _startTask.SetResult(true); diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs index 996ec6bf..146f2e46 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs @@ -38,7 +38,7 @@ public static event EventHandler BackgroundModeC } } - internal static void UpdateBackgroundMode(bool isInBackground) + private static void UpdateBackgroundMode(bool isInBackground) { var args = new BackgroundModeChangedEventArgs(isInBackground); var handlers = _backgroundModeChanged; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index dc7caa71..220b2950 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; using WireMock.Server; using Xunit; @@ -48,7 +52,7 @@ public void InitGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -65,7 +69,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -82,7 +86,7 @@ public void InitCanTimeOutSync() using (var log = new LogSinkScope()) { - var config = BaseConfig(server).IsStreamingEnabled(false).Build(); + var config = BaseConfig(server, builder => builder.IsStreamingEnabled(false)); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized); @@ -104,7 +108,7 @@ public void InitFailsOn401Sync(UpdateMode mode) using (var log = new LogSinkScope()) { - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = TestUtil.CreateClient(config, _user)) { Assert.False(client.Initialized); @@ -123,7 +127,7 @@ await WithServerAsync(async server => using (var log = new LogSinkScope()) { - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that @@ -144,7 +148,7 @@ await WithServerAsync(async server => { server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - var config = BaseConfig(server, UpdateMode.Polling).Build(); + var config = BaseConfig(server, UpdateMode.Polling); var name = "Sue"; var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); @@ -175,7 +179,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -205,7 +209,7 @@ await WithServerAsync(async server => { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyRequest(server, mode); @@ -235,7 +239,7 @@ public void IdentifyCanTimeOutSync(UpdateMode mode) { SetupResponse(server, _flagData1, mode); - var config = BaseConfig(server, mode).Build(); + var config = BaseConfig(server, mode); using (var client = TestUtil.CreateClient(config, _user)) { VerifyRequest(server, mode); @@ -263,13 +267,10 @@ public void OfflineClientUsesCachedFlagsSync() ClearCachedFlags(_user); try { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); + var config = BaseConfig(server, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized); } // At this point the SDK should have written the flags to persistent storage for this user key. @@ -281,7 +282,6 @@ public void OfflineClientUsesCachedFlagsSync() using (var client = TestUtil.CreateClient(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized); } } finally @@ -301,9 +301,7 @@ await WithServerAsync(async server => ClearCachedFlags(_user); try { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); + var config = BaseConfig(server, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -318,7 +316,6 @@ await WithServerAsync(async server => using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { VerifyFlagValues(client, _flagData1); - Assert.True(client.Initialized); } } finally @@ -329,161 +326,87 @@ await WithServerAsync(async server => } [Fact(Skip = SkipIfCannotCreateHttpServer)] - public void OfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() + public async Task BackgroundModeForcesPollingAsync() { - WithServer(server => - { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - - ClearCachedFlags(_user); - try - { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); - using (var client = TestUtil.CreateClient(config, _user)) - { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } - - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + var mockBackgroundModeManager = new MockBackgroundModeManager(); + var backgroundInterval = TimeSpan.FromMilliseconds(50); + var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) - { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } - } - finally - { - ClearCachedFlags(_user); - } - }); - } - - [Fact(Skip = SkipIfCannotCreateHttpServer)] - public async Task OfflineClientUsesCachedFlagsAsyncAfterStartUpdateProcessorAsync() - { + ClearCachedFlags(_user); await WithServerAsync(async server => { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + var config = BaseConfig(server, UpdateMode.Streaming, builder => builder + .BackgroundModeManager(mockBackgroundModeManager) + .BackgroundPollingIntervalWithoutMinimum(backgroundInterval) + .PersistFlagValues(false)); - ClearCachedFlags(_user); - try - { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); - using (var client = await TestUtil.CreateClientAsync(config, _user)) - { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + SetupResponse(server, _flagData1, UpdateMode.Streaming); - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. - - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) - { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } - } - finally + using (var client = await TestUtil.CreateClientAsync(config, _user)) { - ClearCachedFlags(_user); - } - }); - } + VerifyFlagValues(client, _flagData1); - [Fact(Skip = SkipIfCannotCreateHttpServer)] - public void BackgroundOfflineClientUsesCachedFlagsSyncAfterStartUpdateProcessor() - { - WithServer(server => - { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + // SetupResponse makes the server *only* respond to the right endpoint for the update mode that we + // specified, so here we only want it to succeed if it gets a polling request, not streaming. + var requestReceivedSignal = SetupResponse(server, _flagData2, UpdateMode.Polling); + mockBackgroundModeManager.UpdateBackgroundMode(true); - ClearCachedFlags(_user); - try - { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); - using (var client = TestUtil.CreateClient(config, _user)) - { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + // There's no way for us to directly observe when the new update processor has finished both doing the + // request and updating the flag store; we can only detect, via requestReceivedSignal, when the server + // has received the request. So we need to add a hacky delay here. + await requestReceivedSignal.WaitAsync(); + await Task.Delay(hackyUpdateDelay); + VerifyFlagValues(client, _flagData2); - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // Now switch back to streaming + requestReceivedSignal = SetupResponse(server, _flagData1, UpdateMode.Streaming); + mockBackgroundModeManager.UpdateBackgroundMode(false); - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) - { - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - } - } - finally - { - ClearCachedFlags(_user); + // Again, we have no way to really guarantee how long this state change will take + await requestReceivedSignal.WaitAsync(); + await Task.Delay(hackyUpdateDelay); + VerifyFlagValues(client, _flagData1); } }); } [Fact(Skip = SkipIfCannotCreateHttpServer)] - public async Task BackgroundOfflineClientUsesCachedFlagsAsyncAfterStartUpdateProcessorAsync() + public async Task BackgroundModePollingCanBeDisabledAsync() { + var mockBackgroundModeManager = new MockBackgroundModeManager(); + var backgroundInterval = TimeSpan.FromMilliseconds(50); + var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); + + ClearCachedFlags(_user); await WithServerAsync(async server => { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this + var config = BaseConfig(server, UpdateMode.Streaming, builder => builder + .BackgroundModeManager(mockBackgroundModeManager) + .EnableBackgroundUpdating(false) + .BackgroundPollingInterval(backgroundInterval) + .PersistFlagValues(false)); - ClearCachedFlags(_user); - try + SetupResponse(server, _flagData1, UpdateMode.Streaming); + + using (var client = await TestUtil.CreateClientAsync(config, _user)) { - var config = BaseConfig(server, UpdateMode.Polling) - .PersistFlagValues(true) - .Build(); - using (var client = await TestUtil.CreateClientAsync(config, _user)) - { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } + VerifyFlagValues(client, _flagData1); - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. + // The SDK should *not* hit this polling endpoint, but we're providing some data there so we can + // detect whether it does. + SetupResponse(server, _flagData2, UpdateMode.Polling); + mockBackgroundModeManager.UpdateBackgroundMode(true); - server.Reset(); // the offline client shouldn't be making any requests, but just in case - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) - { - BackgroundDetection.UpdateBackgroundMode(true); - VerifyFlagValues(client, _flagData1); - BackgroundDetection.UpdateBackgroundMode(false); - VerifyFlagValues(client, _flagData1); - } - } - finally - { - ClearCachedFlags(_user); + await Task.Delay(hackyUpdateDelay); + VerifyFlagValues(client, _flagData1); // we should *not* have done a poll + + // Now switch back to streaming + var requestReceivedSignal = SetupResponse(server, _flagData1, UpdateMode.Streaming); + mockBackgroundModeManager.UpdateBackgroundMode(false); + + await requestReceivedSignal.WaitAsync(); + await Task.Delay(hackyUpdateDelay); + VerifyFlagValues(client, _flagData1); } }); } @@ -495,10 +418,7 @@ public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) await WithServerAsync(async server => { ClearCachedFlags(_user); - var config = BaseConfig(server, mode) - .Offline(true) - .PersistFlagValues(false) - .Build(); + var config = BaseConfig(server, mode, builder => builder.Offline(true).PersistFlagValues(false)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyNoFlagValues(client, _flagData1); @@ -512,29 +432,49 @@ await WithServerAsync(async server => }); } - private IConfigurationBuilder BaseConfig(FluentMockServer server) + private Configuration BaseConfig(FluentMockServer server, Func extraConfig = null) { - return Configuration.BuilderInternal(_mobileKey) - .EventProcessor(new MockEventProcessor()) + var builderInternal = Configuration.BuilderInternal(_mobileKey) + .EventProcessor(new MockEventProcessor()); + builderInternal .BaseUri(new Uri(server.GetUrl())) .StreamUri(new Uri(server.GetUrl())) - .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination + .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination + var builder = extraConfig == null ? builderInternal : extraConfig(builderInternal); + return builder.Build(); } - private IConfigurationBuilder BaseConfig(FluentMockServer server, UpdateMode mode) + private Configuration BaseConfig(FluentMockServer server, UpdateMode mode, Func extraConfig = null) { - return BaseConfig(server) - .IsStreamingEnabled(mode.IsStreaming); + return BaseConfig(server, builder => + { + builder.IsStreamingEnabled(mode.IsStreaming); + return extraConfig == null ? builder : extraConfig(builder); + }); } - private void SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) + // + private SemaphoreSlim SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) { - server.ForAllRequests(r => - mode.IsStreaming ? r.WithEventsBody(StreamingData(data)) : r.WithJsonBody(PollingData(data))); + var signal = new SemaphoreSlim(0, 1); + server.ResetMappings(); + var resp = Response.Create().WithCallback(req => + { + signal.Release(); + var respBuilder = mode.IsStreaming ? + Response.Create().WithEventsBody(StreamingData(data)) : + Response.Create().WithJsonBody(PollingData(data)); + return ((Response)respBuilder).ResponseMessage; + }); + // Note: in streaming mode, since WireMock.Net doesn't seem to support streaming responses, the fake response will close // after the end of the data-- so the SDK will enter retry mode and we may get another identical streaming request. For // the purposes of these tests, that doesn't matter. The correct processing of a chunked stream is tested in the // LaunchDarkly.EventSource tests, and the retry logic is tested in LaunchDarkly.CommonSdk. + + server.Given(Request.Create().WithPath(path => Regex.IsMatch(path, mode.FlagsPathRegex))) + .RespondWith(resp); + return signal; } private void VerifyRequest(FluentMockServer server, UpdateMode mode) @@ -611,5 +551,7 @@ public class UpdateMode IsStreaming = false, FlagsPathRegex = "^/msdk/evalx/users/[^/?]+" }; + + public override string ToString() => IsStreaming ? "Streaming" : "Polling"; } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs index 1aeca942..10127382 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs @@ -21,8 +21,7 @@ IMobileUpdateProcessor Processor() var stubbedFlagCache = new UserFlagInMemoryCache(); mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); user = User.WithKey("user1Key"); - var timeSpan = TimeSpan.FromSeconds(1); - return new MobilePollingProcessor(mockFeatureFlagRequestor, mockFlagCacheManager, user, timeSpan); + return new MobilePollingProcessor(mockFeatureFlagRequestor, mockFlagCacheManager, user, TimeSpan.FromSeconds(30), TimeSpan.Zero); } [Fact] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index a72bae29..d8984938 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -2,10 +2,21 @@ using System.Collections.Generic; using System.Threading.Tasks; using LaunchDarkly.Client; +using LaunchDarkly.Xamarin.PlatformSpecific; using Newtonsoft.Json; namespace LaunchDarkly.Xamarin.Tests { + internal class MockBackgroundModeManager : IBackgroundModeManager + { + public event EventHandler BackgroundModeChanged; + + public void UpdateBackgroundMode(bool isInBackground) + { + BackgroundModeChanged?.Invoke(this, new BackgroundModeChangedEventArgs(isInBackground)); + } + } + internal class MockConnectivityStateManager : IConnectivityStateManager { public Action ConnectionChanged { get; set; } From 240a2ef4bbf08b47d410ef719bdf800e644401a1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 27 Aug 2019 20:19:07 -0700 Subject: [PATCH 243/499] fix comments --- src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 24d751f6..008e5230 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -48,7 +48,8 @@ public interface IConfigurationBuilder /// Sets the interval between feature flag updates when the application is running in the background. /// /// - /// This is only relevant on mobile platforms. + /// This is only relevant on mobile platforms. The default is ; + /// the minimum is . /// /// the background polling interval /// the same builder @@ -71,6 +72,7 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout); + /// /// Sets whether to enable feature flag polling when the application is in the background. /// /// @@ -196,7 +198,8 @@ public interface IConfigurationBuilder /// Sets the polling interval (when streaming is disabled). /// /// - /// Values less than the default of 30 seconds will be changed to the default. + /// The default is ; the minimum is + /// . /// /// the rule update polling interval /// the same builder From d78754c6e201165c4ac7e3ddc2de1dae9f29fd86 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 28 Aug 2019 10:25:00 -0700 Subject: [PATCH 244/499] misc fixes for flaky tests --- .../AndroidSpecificTests.cs | 2 +- tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs | 2 +- tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index 337a3dc3..1d6fa17f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class AndroidSpecificTests + public class AndroidSpecificTests : BaseTest { [Fact] public void SdkReturnsAndroidPlatformType() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 37e4a759..d7803018 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -66,7 +66,7 @@ public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() [Fact] public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(1)))); + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(4)))); private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) { diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index 63bbb60d..5f2d776f 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -3,7 +3,7 @@ namespace LaunchDarkly.Xamarin.Tests { - public class IOsSpecificTests + public class IOsSpecificTests : BaseTest { [Fact] public void SdkReturnsIOsPlatformType() From 6866f85b4dd9e0c12f94b43a71a2fe80dce87fc2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 28 Aug 2019 18:17:56 -0700 Subject: [PATCH 245/499] update WireMock, re-enable HTTP tests for Android --- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 2 +- .../Properties/AssemblyInfo.cs | 5 +- .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 35 ++-- .../FeatureFlagRequestorTests.cs | 19 +- .../LDClientEndToEndTests.cs | 28 +-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 184 ++++++++++-------- .../AssemblyInfo.cs | 4 + .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 3 +- 9 files changed, 153 insertions(+), 129 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index da88579f..5d6e2429 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -147,7 +147,7 @@ 3.4.1 - 1.0.20 + 1.0.22 2.4.1 diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs index bdf7f78e..20e589a2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Android.App; +using Xunit; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -28,3 +28,6 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] + +// We must disable all parallel test running by XUnit in order for LogSink to work. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index 007dab21..b101b5a2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,8 +1,11 @@ using System; +using System.Net.Http; using System.Threading.Tasks; using Common.Logging; using LaunchDarkly.Client; +using WireMock.Logging; using WireMock.Server; +using WireMock.Settings; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -10,16 +13,6 @@ namespace LaunchDarkly.Xamarin.Tests [Collection("serialize all tests")] public class BaseTest : IDisposable { -#if __ANDROID__ - // WireMock.Net currently doesn't work on Android, so we can't run tests that need an embedded HTTP server. - // Mark such tests with [Fact(Skip = SkipIfCannotCreateHttpServer)] or [Theory(Skip = SkipIfCannotCreateHttpServer)] - // and they'll be skipped on Android (but not on other platforms, because "Skip = null" is a no-op). - // https://github.com/WireMock-Net/WireMock.Net/issues/292 - public const string SkipIfCannotCreateHttpServer = "can't run this test because we can't create an embedded HTTP server on this platform; see BaseTest.cs"; -#else - public const string SkipIfCannotCreateHttpServer = null; -#endif - public BaseTest() { LogManager.Adapter = new LogSinkFactoryAdapter(); @@ -64,20 +57,18 @@ protected async Task WithServerAsync(Func a) protected FluentMockServer MakeServer() { -#pragma warning disable RECS0110 // Condition is always 'true' or always 'false' - if (SkipIfCannotCreateHttpServer != null) + // currently we don't need to customize any server settings + var server = FluentMockServer.Start(); + + // Perform an initial request to make sure the server has warmed up. On Android in particular, startup + // of the very first server instance in the test run seems to be very slow, which may cause the first + // request made by unit tests to time out. + using (var client = new HttpClient()) { - // Until WireMock.Net supports all of our platforms, we'll need to mark any tests that use an embedded server - // with [ConditionalFact(Condition = TestCondition.CanRunHttpServer)] or [ConditionalFact(Condition = TestCondition.CanRunHttpServer)] - // instead of [Fact] or [Theory]; otherwise, you'll see this error. - throw new Exception("tried to create an embedded HTTP server on a platform that doesn't support it; see BaseTest.cs"); + AsyncUtils.WaitSafely(() => client.GetAsync(server.Urls[0])); } -#pragma warning restore RECS0110 // Condition is always 'true' or always 'false' - - // currently we don't need to customize any server settings -#pragma warning disable CS0162 // Unreachable code detected - return FluentMockServer.Start(); -#pragma warning restore CS0162 // Unreachable code detected + server.ResetLogEntries(); // so the initial request doesn't interfere with test postconditions + return server; } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 76ccb663..462c1c97 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -11,13 +13,14 @@ public class FeatureFlagRequestorTests : BaseTest private const string _mobileKey = "FAKE_KEY"; private static readonly User _user = User.WithKey("foo"); + private const string _userJson = "{\"key\":\"foo\",\"anonymous\":false}"; private const string _encodedUser = "eyJrZXkiOiJmb28iLCJhbm9ueW1vdXMiOmZhbHNlLCJjdXN0b20iOnt9fQ=="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync() { await WithServerAsync(async server => @@ -43,7 +46,7 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task GetFlagsUsesCorrectUriAndMethodInGetModeWithReasonsAsync() { await WithServerAsync(async server => @@ -69,8 +72,8 @@ await WithServerAsync(async server => }); } - // Report mode is currently disabled - ch47341 - //[Fact(Skip = SkipIfCannotCreateHttpServer)] + // Report mode is currently disabled - ch47341 + //[Fact] //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() //{ // await WithServerAsync(async server => @@ -91,15 +94,12 @@ await WithServerAsync(async server => // Assert.Equal($"/msdk/evalx/user", req.Path); // Assert.Equal("", req.RawQuery); // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - - // //Assert.Equal("{\"key\":\"foo\"}", req.Body); - // // Here, ideally, we would verify that the request body contained the expected user data. Unfortunately, WireMock.Net - // // is not currently able to detect the body for REPORT requests: https://github.com/WireMock-Net/WireMock.Net/issues/290 + // TestUtil.AssertJsonEquals(JToken.Parse(_userJson), TestUtil.NormalizeJsonUser(JToken.Parse(req.Body))); // } // }); //} - //[Fact(Skip = SkipIfCannotCreateHttpServer)] + //[Fact] //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() //{ // await WithServerAsync(async server => @@ -120,6 +120,7 @@ await WithServerAsync(async server => // Assert.Equal($"/msdk/evalx/user", req.Path); // Assert.Equal("?withReasons=true", req.RawQuery); // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + // TestUtil.AssertJsonEquals(JToken.Parse(_userJson), TestUtil.NormalizeJsonUser(JToken.Parse(req.Body))); // } // }); //} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 220b2950..eab4f949 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -44,7 +44,7 @@ public class LdClientEndToEndTests : BaseTest { new object[] { UpdateMode.Streaming } } }; - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public void InitGetsFlagsSync(UpdateMode mode) { @@ -61,7 +61,7 @@ public void InitGetsFlagsSync(UpdateMode mode) }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task InitGetsFlagsAsync(UpdateMode mode) { @@ -77,7 +77,7 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public void InitCanTimeOutSync() { WithServer(server => @@ -98,7 +98,7 @@ public void InitCanTimeOutSync() }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public void InitFailsOn401Sync(UpdateMode mode) { @@ -117,7 +117,7 @@ public void InitFailsOn401Sync(UpdateMode mode) }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task InitFailsOn401Async(UpdateMode mode) { @@ -140,7 +140,7 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() { // Note, we don't care about polling mode vs. streaming mode for this functionality. @@ -171,7 +171,7 @@ await WithServerAsync(async server => }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { @@ -201,7 +201,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) { @@ -231,7 +231,7 @@ await WithServerAsync(async server => }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public void IdentifyCanTimeOutSync(UpdateMode mode) { @@ -257,7 +257,7 @@ public void IdentifyCanTimeOutSync(UpdateMode mode) }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public void OfflineClientUsesCachedFlagsSync() { WithServer(server => @@ -291,7 +291,7 @@ public void OfflineClientUsesCachedFlagsSync() }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task OfflineClientUsesCachedFlagsAsync() { await WithServerAsync(async server => @@ -325,7 +325,7 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task BackgroundModeForcesPollingAsync() { var mockBackgroundModeManager = new MockBackgroundModeManager(); @@ -370,7 +370,7 @@ await WithServerAsync(async server => }); } - [Fact(Skip = SkipIfCannotCreateHttpServer)] + [Fact] public async Task BackgroundModePollingCanBeDisabledAsync() { var mockBackgroundModeManager = new MockBackgroundModeManager(); @@ -411,7 +411,7 @@ await WithServerAsync(async server => }); } - [Theory(Skip = SkipIfCannotCreateHttpServer)] + [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 646d691d..e98a23f2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 75efc5f6..e362d879 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -1,21 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using LaunchDarkly.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin.Tests -{ - public static class TestUtil - { - // Any tests that are going to access the static LdClient.Instance must hold this lock, - // to avoid interfering with tests that use CreateClient. - private static readonly SemaphoreSlim ClientInstanceLock = new SemaphoreSlim(1); - - private static ThreadLocal InClientLock = new ThreadLocal(); - +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using LaunchDarkly.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public static class TestUtil + { + // Any tests that are going to access the static LdClient.Instance must hold this lock, + // to avoid interfering with tests that use CreateClient. + private static readonly SemaphoreSlim ClientInstanceLock = new SemaphoreSlim(1); + + private static ThreadLocal InClientLock = new ThreadLocal(); + public static T WithClientLock(Func f) { // This cumbersome combination of a ThreadLocal and a SemaphoreSlim is simply because 1. we have to use @@ -75,44 +76,44 @@ public static async Task WithClientLockAsync(Func> f) InClientLock.Value = false; ClientInstanceLock.Release(); } - } - - // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can - // instantiate their own independent clients. Application code cannot do this because - // the LdClient.Instance setter has internal scope. - public static LdClient CreateClient(Configuration config, User user, TimeSpan? timeout = null) - { - return WithClientLock(() => + } + + // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can + // instantiate their own independent clients. Application code cannot do this because + // the LdClient.Instance setter has internal scope. + public static LdClient CreateClient(Configuration config, User user, TimeSpan? timeout = null) + { + return WithClientLock(() => { - ClearClient(); - LdClient client = LdClient.Init(config, user, timeout ?? TimeSpan.FromSeconds(1)); - client.DetachInstance(); - return client; - }); - } - - // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can - // instantiate their own independent clients. Application code cannot do this because - // the LdClient.Instance setter has internal scope. - public static async Task CreateClientAsync(Configuration config, User user) - { - return await WithClientLockAsync(async () => - { - ClearClient(); - LdClient client = await LdClient.InitAsync(config, user); - client.DetachInstance(); + ClearClient(); + LdClient client = LdClient.Init(config, user, timeout ?? TimeSpan.FromSeconds(1)); + client.DetachInstance(); return client; - }); - } - - public static void ClearClient() - { - WithClientLock(() => - { + }); + } + + // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can + // instantiate their own independent clients. Application code cannot do this because + // the LdClient.Instance setter has internal scope. + public static async Task CreateClientAsync(Configuration config, User user) + { + return await WithClientLockAsync(async () => + { + ClearClient(); + LdClient client = await LdClient.InitAsync(config, user); + client.DetachInstance(); + return client; + }); + } + + public static void ClearClient() + { + WithClientLock(() => + { LdClient.Instance?.Dispose(); - }); - } - + }); + } + internal static Dictionary MakeSingleFlagData(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) { var flag = new FeatureFlag { value = value, variation = variation, reason = reason }; @@ -121,30 +122,53 @@ internal static Dictionary MakeSingleFlagData(string flagKe internal static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) { - return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); - } - - internal static IDictionary DecodeFlagsJson(string flagsJson) - { - return JsonConvert.DeserializeObject>(flagsJson); - } - - internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) - { - var flags = DecodeFlagsJson(flagsJson); - IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); - if (user != null && user.Key != null) - { - stubbedFlagCache.CacheFlagsForUser(flags, user); - } - - return Configuration.BuilderInternal(appKey) - .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) - .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .EventProcessor(new MockEventProcessor()) - .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) - .PersistentStorage(new MockPersistentStorage()) - .DeviceInfo(new MockDeviceInfo("")); - } - } -} + return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); + } + + internal static IDictionary DecodeFlagsJson(string flagsJson) + { + return JsonConvert.DeserializeObject>(flagsJson); + } + + internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) + { + var flags = DecodeFlagsJson(flagsJson); + IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); + if (user != null && user.Key != null) + { + stubbedFlagCache.CacheFlagsForUser(flags, user); + } + + return Configuration.BuilderInternal(appKey) + .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) + .ConnectivityStateManager(new MockConnectivityStateManager(true)) + .EventProcessor(new MockEventProcessor()) + .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) + .PersistentStorage(new MockPersistentStorage()) + .DeviceInfo(new MockDeviceInfo("")); + } + + public static void AssertJsonEquals(JToken expected, JToken actual) + { + if (!JToken.DeepEquals(expected, actual)) + { + Assert.Equal(expected.ToString(), actual.ToString()); // will print the values with the failure + } + } + + public static JToken NormalizeJsonUser(JToken json) + { + // It's undefined whether a user with no custom attributes will have "custom":{} or not + if (json is JObject o && o.ContainsKey("custom") && o["custom"] is JObject co) + { + if (co.Count == 0) + { + JObject o1 = new JObject(o); + o1.Remove("custom"); + return o1; + } + } + return json; + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs new file mode 100644 index 00000000..c4e33f39 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using Xunit; + +// We must disable all parallel test running by XUnit in order for LogSink to work. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index db128f57..3f8703c3 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -83,7 +83,7 @@ 4.0.0.497661 - 1.0.20 + 1.0.22 @@ -194,6 +194,7 @@ SharedTestCode\WireMockExtensions.cs + From e48a0b0988149171518b2a0560c7851b00335957 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 28 Aug 2019 23:19:44 -0700 Subject: [PATCH 246/499] more stable way of synchronizing the background mode tests --- .../LDClientEndToEndTests.cs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index eab4f949..f7775556 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -330,7 +330,6 @@ public async Task BackgroundModeForcesPollingAsync() { var mockBackgroundModeManager = new MockBackgroundModeManager(); var backgroundInterval = TimeSpan.FromMilliseconds(50); - var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); ClearCachedFlags(_user); await WithServerAsync(async server => @@ -346,25 +345,26 @@ await WithServerAsync(async server => { VerifyFlagValues(client, _flagData1); - // SetupResponse makes the server *only* respond to the right endpoint for the update mode that we - // specified, so here we only want it to succeed if it gets a polling request, not streaming. - var requestReceivedSignal = SetupResponse(server, _flagData2, UpdateMode.Polling); + // Set it up so that when the client switches to background mode and does a polling request, it will + // receive _flagData2, and we will be notified of that via a change event. SetupResponse will only + // configure the polling endpoint, so if the client makes a streaming request here it'll fail. + SetupResponse(server, _flagData2, UpdateMode.Polling); + var receivedChangeSignal = new SemaphoreSlim(0, 1); + client.FlagChanged += (sender, args) => + { + receivedChangeSignal.Release(); + }; + mockBackgroundModeManager.UpdateBackgroundMode(true); - // There's no way for us to directly observe when the new update processor has finished both doing the - // request and updating the flag store; we can only detect, via requestReceivedSignal, when the server - // has received the request. So we need to add a hacky delay here. - await requestReceivedSignal.WaitAsync(); - await Task.Delay(hackyUpdateDelay); + await receivedChangeSignal.WaitAsync(); VerifyFlagValues(client, _flagData2); // Now switch back to streaming - requestReceivedSignal = SetupResponse(server, _flagData1, UpdateMode.Streaming); + SetupResponse(server, _flagData1, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); - // Again, we have no way to really guarantee how long this state change will take - await requestReceivedSignal.WaitAsync(); - await Task.Delay(hackyUpdateDelay); + await receivedChangeSignal.WaitAsync(); VerifyFlagValues(client, _flagData1); } }); @@ -400,13 +400,18 @@ await WithServerAsync(async server => await Task.Delay(hackyUpdateDelay); VerifyFlagValues(client, _flagData1); // we should *not* have done a poll + var receivedChangeSignal = new SemaphoreSlim(0, 1); + client.FlagChanged += (sender, args) => + { + receivedChangeSignal.Release(); + }; + // Now switch back to streaming - var requestReceivedSignal = SetupResponse(server, _flagData1, UpdateMode.Streaming); + SetupResponse(server, _flagData2, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); - await requestReceivedSignal.WaitAsync(); - await Task.Delay(hackyUpdateDelay); - VerifyFlagValues(client, _flagData1); + await receivedChangeSignal.WaitAsync(); + VerifyFlagValues(client, _flagData2); } }); } From 20d8ec81339df43761562c88ba6d24e75e73eb00 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 29 Aug 2019 18:55:56 -0700 Subject: [PATCH 247/499] put event processor off/online based on Offline property + network status --- src/LaunchDarkly.XamarinSdk/Factory.cs | 5 -- .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 12 ++-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientTests.cs | 70 +++++++++++++++++++ .../MockComponents.cs | 6 ++ 6 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index c77846f4..c8114f03 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -64,11 +64,6 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration { return configuration._eventProcessor; } - if (configuration.Offline) - { - return new NullEventProcessor(); - } - HttpClient httpClient = Util.MakeHttpClient(configuration.HttpRequestConfiguration, MobileClientEnvironment.Instance); return new DefaultEventProcessor(configuration.EventProcessorConfiguration, null, httpClient, Constants.EVENTS_PATH); } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 9e0a07d4..168d54d0 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index a336dbbf..e557d807 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -130,7 +130,6 @@ public event EventHandler FlagChanged _user = DecorateUser(user); flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); - eventProcessor = Factory.CreateEventProcessor(configuration); _connectionManager = new ConnectionManager(); _connectionManager.SetForceOffline(configuration.Offline); @@ -143,15 +142,19 @@ public event EventHandler FlagChanged true ); - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); - _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); _connectivityStateManager.ConnectionChanged += networkAvailable => { Log.DebugFormat("Setting online to {0} due to a connectivity change event", networkAvailable); _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result + eventProcessor.SetOffline(!networkAvailable || _connectionManager.ForceOffline); }; - _connectionManager.SetNetworkEnabled(_connectivityStateManager.IsConnected); + var isConnected = _connectivityStateManager.IsConnected; + _connectionManager.SetNetworkEnabled(isConnected); + + eventProcessor = Factory.CreateEventProcessor(configuration); + eventProcessor.SetOffline(configuration.Offline || !isConnected); + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; @@ -296,6 +299,7 @@ public bool SetOffline(bool value, TimeSpan maxWaitTime) /// public async Task SetOfflineAsync(bool value) { + eventProcessor.SetOffline(value || !_connectionManager.NetworkEnabled); await _connectionManager.SetForceOffline(value); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index e98a23f2..80a225af 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index d7803018..f83fcbd6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -353,5 +353,75 @@ public void FlagsAreSavedToPersistentStorageByDefault() Assert.Equal(new JValue(100), flags["flag"].value); } } + + [Fact] + public void EventProcessorIsOnlineByDefault() + { + var eventProcessor = new MockEventProcessor(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .EventProcessor(eventProcessor) + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.False(eventProcessor.Offline); + } + } + + [Fact] + public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() + { + var connectivityStateManager = new MockConnectivityStateManager(true); + var eventProcessor = new MockEventProcessor(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .ConnectivityStateManager(connectivityStateManager) + .EventProcessor(eventProcessor) + .Offline(true) + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.True(eventProcessor.Offline); + + client.SetOffline(false, TimeSpan.FromSeconds(1)); + Assert.False(eventProcessor.Offline); + + client.SetOffline(true, TimeSpan.FromSeconds(1)); + Assert.True(eventProcessor.Offline); + + // If the network is unavailable... + connectivityStateManager.Connect(false); + + // ...then even if Offline is set to false, events stay off + client.SetOffline(false, TimeSpan.FromSeconds(1)); + Assert.True(eventProcessor.Offline); + } + } + + [Fact] + public void EventProcessorIsOfflineWhenNetworkIsUnavailable() + { + var connectivityStateManager = new MockConnectivityStateManager(false); + var eventProcessor = new MockEventProcessor(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .ConnectivityStateManager(connectivityStateManager) + .EventProcessor(eventProcessor) + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.True(eventProcessor.Offline); + + connectivityStateManager.Connect(true); + Assert.False(eventProcessor.Offline); + + connectivityStateManager.Connect(false); + Assert.True(eventProcessor.Offline); + + // If client is configured offline... + client.SetOffline(true, TimeSpan.FromSeconds(1)); + + // ...then even if the network comes back on, events stay off + connectivityStateManager.Connect(true); + Assert.True(eventProcessor.Offline); + } + } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index d8984938..4e730528 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -65,6 +65,12 @@ public string UniqueDeviceId() internal class MockEventProcessor : IEventProcessor { public List Events = new List(); + public bool Offline = false; + + public void SetOffline(bool offline) + { + Offline = offline; + } public void SendEvent(Event e) { From 23f2c850525ed31397f097b54c0f491e63b0d3f7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 29 Aug 2019 19:02:41 -0700 Subject: [PATCH 248/499] don't post more events if we're offline --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index e557d807..849a37b3 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -154,8 +154,11 @@ public event EventHandler FlagChanged eventProcessor = Factory.CreateEventProcessor(configuration); eventProcessor.SetOffline(configuration.Offline || !isConnected); - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + // Send an initial identify event - we will do this even if we're in offline mode, because if you later + // put the client online again, we do want the user to be identified. + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; } @@ -377,14 +380,14 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (!Initialized) { Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); - eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + SendEventIfOnline(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, EvaluationErrorKind.CLIENT_NOT_READY)); return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); } else { Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); - eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + SendEventIfOnline(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, EvaluationErrorKind.FLAG_NOT_FOUND)); return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); } @@ -421,10 +424,18 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); - eventProcessor.SendEvent(featureEvent); + SendEventIfOnline(featureEvent); return result; } + private void SendEventIfOnline(Event e) + { + if (!_connectionManager.ForceOffline) + { + eventProcessor.SendEvent(e); + } + } + /// public IDictionary AllFlags() { @@ -435,7 +446,7 @@ public IDictionary AllFlags() /// public void Track(string eventName, ImmutableJsonValue data) { - eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); + SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); } /// @@ -447,7 +458,7 @@ public void Track(string eventName) /// public void Flush() { - eventProcessor.Flush(); + eventProcessor.Flush(); // eventProcessor will ignore this if it is offline } /// @@ -476,7 +487,7 @@ public async Task IdentifyAsync(User user) _user = newUser; }); - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(newUser)); + SendEventIfOnline(_eventFactoryDefault.NewIdentifyEvent(newUser)); return await _connectionManager.SetUpdateProcessorFactory( Factory.CreateUpdateProcessorFactory(_config, user, flagCacheManager, _inBackground), From dc2d86fd18cc423a3edfdd581974e3de9f48beea Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 29 Aug 2019 19:04:57 -0700 Subject: [PATCH 249/499] don't send identify event if we're offline --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 849a37b3..c5c1f14f 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -155,9 +155,11 @@ public event EventHandler FlagChanged eventProcessor = Factory.CreateEventProcessor(configuration); eventProcessor.SetOffline(configuration.Offline || !isConnected); - // Send an initial identify event - we will do this even if we're in offline mode, because if you later - // put the client online again, we do want the user to be identified. - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + // Send an initial identify event, but only if we weren't explicitly set to be offline + if (!configuration.Offline) + { + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + } _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; From dfdc9aab5c0b607ccd83aa9010467c07f4837344 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 10:42:36 -0700 Subject: [PATCH 250/499] generate XML comment files --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 168d54d0..80b7dcf5 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -16,6 +16,13 @@ False + + bin\Debug\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml + + + bin\Release\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml + + From ad45b10c0d10e1db7d6bc637a24ebbde7b944e39 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 11:05:37 -0700 Subject: [PATCH 251/499] try skipping XML step in Android CI job because msbuild will fail --- .circleci/config.yml | 3 +++ src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d4cd36d1..1a23e709 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,9 @@ jobs: docker: - image: ldcircleci/ld-xamarin-android-linux:api27 + environment: + LD_SKIP_XML_DOCS: 1 # there seems to be a bug in Xamarin Android builds on Linux that makes the XML build step fail + steps: - checkout diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 80b7dcf5..4ca2cb18 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -16,10 +16,10 @@ False - + bin\Debug\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml - + bin\Release\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml From dffedd40fe993bd4f33b9ec2503075ae11439ec5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 12:25:09 -0700 Subject: [PATCH 252/499] rm unused type --- src/LaunchDarkly.XamarinSdk/ClientRunMode.cs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/ClientRunMode.cs diff --git a/src/LaunchDarkly.XamarinSdk/ClientRunMode.cs b/src/LaunchDarkly.XamarinSdk/ClientRunMode.cs deleted file mode 100644 index 320af5ee..00000000 --- a/src/LaunchDarkly.XamarinSdk/ClientRunMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LaunchDarkly.Xamarin -{ - public enum ClientRunMode - { - Foreground, - Background - } -} From 44dbb6a356985417b06540f908781e0c05f2daf6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 12:25:35 -0700 Subject: [PATCH 253/499] XML comment fixes --- src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs | 2 +- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 3 +++ src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 10 ++++++++++ src/LaunchDarkly.XamarinSdk/PlatformType.cs | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 1dec8f2d..d7d87522 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -7,7 +7,7 @@ namespace LaunchDarkly.Xamarin { /// - /// An event object that is sent to handlers for the event. + /// An event object that is sent to handlers for the event. /// public sealed class FlagChangedEventArgs { diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index fb4dc3d2..41077a5e 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -6,6 +6,9 @@ namespace LaunchDarkly.Xamarin { + /// + /// Interface for the standard SDK client methods and properties. The only implementation of this is . + /// public interface ILdClient : IDisposable { /// diff --git a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs index cbe0c73b..ce5f4daa 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Xamarin internal interface IMobileUpdateProcessor : IDisposable { /// - /// Initializes the processor. This is called once from the constructor. + /// Initializes the processor. This is called once from the constructor. /// /// a Task which is completed once the processor has finished starting up Task Start(); diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index c5c1f14f..789bbad1 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -528,6 +528,16 @@ User DecorateUser(User user) return buildUser is null ? user : buildUser.Build(); } + /// + /// Permanently shuts down the SDK client. + /// + /// + /// This method closes all network collections, shuts down all background tasks, and releases any other + /// resources being held by the SDK. + /// + /// If there are any pending analytics events, and if the SDK is online, it attempts to deliver the events + /// to LaunchDarkly before closing. + /// public void Dispose() { Dispose(true); diff --git a/src/LaunchDarkly.XamarinSdk/PlatformType.cs b/src/LaunchDarkly.XamarinSdk/PlatformType.cs index 3c5ab789..9c34ff70 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformType.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformType.cs @@ -2,7 +2,7 @@ namespace LaunchDarkly.Xamarin { /// - /// Values returned by . + /// Values returned by . /// public enum PlatformType { From e4347b78fbc98ad26673fe8fe6c18597c5f25127 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 12:29:49 -0700 Subject: [PATCH 254/499] version 1.0.0-beta23 --- CHANGELOG.md | 21 +++++++++++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 4 ++-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d89cf6e..d2c7fd77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.0.0-beta23] - 2019-08-30 +### Added: +- XML documentation comments are now included in the package, so they should be visible in Visual Studio for all LaunchDarkly types and methods. + +### Changed: +- The `Online` property of `LdClient` was not useful because it could reflect either a deliberate change to whether the client is allowed to go online (that is, it would be false if you had set `Offline` to true in the configuration), _or_ a change in network availability. It has been removed and replaced with `Offline`, which is only used for explicitly forcing the client to be offline. This is a read-only property; to set it, use `SetOffline` or `SetOfflineAsync`. +- The synchronous `Identify` method now requires a timeout parameter, and returns false if it times out. +- In `Configuration` and `IConfigurationBuilder`, `HttpClientTimeout` is now `ConnectionTimeout`. +- There is now more debug-level logging for stream connection state changes. + +### Fixed: +- Network availability changes are now detected in both Android and iOS. The SDK should not attempt to connect to LaunchDarkly if the OS has told it that the network is unavailable. +- Background polling was never enabled, even if `Configuration.EnableBackgroundUpdating` was true. +- When changing from offline to online by setting `client.Online = true`, or calling `await client.SetOnlineAsync(true)` (the equivalent now would be `client.Offline = false`, etc.), the SDK was returning too soon before it had acquired flags from LaunchDarkly. The known issues in 1.0.0-beta22 have been fixed. +- If the SDK was online and then was explicitly set to be offline, or if network connectivity was lost, the SDK was still attempting to send analytics events. It will no longer do so. +- If the SDK was originally set to be offline and then was put online, the SDK was _not_ sending analytics events. Now it will. + +### Removed: +- `ConfigurationBuilder.UseReport`. Due to [an issue](https://github.com/xamarin/xamarin-android/issues/3544) with the Android implementation of HTTP, the HTTP REPORT method is not currently usable in the Xamarin SDK. +- `IConnectionManager` interface. The SDK now always uses a platform-appropriate implementation of this logic. + ## [1.0.0-beta22] - 2019-08-12 ### Changed: - By default, on Android and iOS the SDK now uses Xamarin's platform-specific implementations of `HttpMessageHandler` that are based on native APIs, rather than the basic `System.Net.Http.HttpClientHandler`. This improves performance and stability on mobile platforms. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 4ca2cb18..656350c0 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -4,7 +4,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta22 + 1.0.0-beta23 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk @@ -31,7 +31,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 80a225af..0676b1a1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From eb4830b7ce47271ea1c83b5b66f1cde20f754de5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 30 Aug 2019 12:40:07 -0700 Subject: [PATCH 255/499] changelog additions --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c7fd77..7e54ed8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](http://semver.org). ### Changed: - The `Online` property of `LdClient` was not useful because it could reflect either a deliberate change to whether the client is allowed to go online (that is, it would be false if you had set `Offline` to true in the configuration), _or_ a change in network availability. It has been removed and replaced with `Offline`, which is only used for explicitly forcing the client to be offline. This is a read-only property; to set it, use `SetOffline` or `SetOfflineAsync`. - The synchronous `Identify` method now requires a timeout parameter, and returns false if it times out. +- `LdClient.Initialized` is now a property, not a method. +- `LdClient.Version` is now static, since it describes the entire package rather than a client instance. - In `Configuration` and `IConfigurationBuilder`, `HttpClientTimeout` is now `ConnectionTimeout`. - There is now more debug-level logging for stream connection state changes. From c3567a0f0abcade79113bbb0dfde43a39153cf44 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 3 Sep 2019 13:59:09 -0700 Subject: [PATCH 256/499] don't use Plugin.DeviceInfo for iOS --- .../PlatformSpecific/ClientIdentifier.ios.cs | 4 ++-- .../PlatformSpecific/ClientIdentifier.shared.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs index 7d3d398b..5d24a355 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -1,4 +1,4 @@ -using Plugin.DeviceInfo; +using UIKit; namespace LaunchDarkly.Xamarin.PlatformSpecific { @@ -7,7 +7,7 @@ internal static partial class ClientIdentifier // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. public static string PlatformGetOrCreateClientId() { - return CrossDeviceInfo.Current.Id; + return UIDevice.CurrentDevice.IdentifierForVendor.AsString(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs index 18aacadf..aeef2a81 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -4,11 +4,19 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { + private static volatile string _id; + private const string PreferencesAnonUserIdKey = "anonUserId"; public static string GetOrCreateClientId() { - return PlatformGetOrCreateClientId(); + var id = _id; + if (id is null) + { + id = PlatformGetOrCreateClientId(); + _id = id; + } + return id; } // Used only for testing, to keep previous calls to GetOrCreateRandomizedClientId from affecting test state. From 2356ba5252802c78751b1445514af7d3da8173b1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 3 Sep 2019 14:37:20 -0700 Subject: [PATCH 257/499] don't use Plugin.DeviceInfo for Android --- .../LaunchDarkly.XamarinSdk.csproj | 2 - .../ClientIdentifier.android.cs | 41 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 656350c0..00c24d10 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -53,7 +53,6 @@ - @@ -79,7 +78,6 @@ - diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs index 7d3d398b..56ed6ece 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -1,13 +1,48 @@ -using Plugin.DeviceInfo; +using System; +using Common.Logging; +using Android.App; +using Android.Provider; +using Android.OS; +using Android.Runtime; +using Java.Interop; namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { - // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. + private static readonly ILog Log = LogManager.GetLogger(typeof(ClientIdentifier)); + private static JniPeerMembers buildMembers = new XAPeerMembers("android/os/Build", typeof(Build)); + public static string PlatformGetOrCreateClientId() { - return CrossDeviceInfo.Current.Id; + // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs + string serialField; + try + { + var value = buildMembers.StaticFields.GetObjectValue("SERIAL.Ljava/lang/String;"); + serialField = JNIEnv.GetString(value.Handle, JniHandleOwnership.TransferLocalRef); + } + catch + { + serialField = ""; + } + if (string.IsNullOrWhiteSpace(serialField) || serialField == Build.Unknown || serialField == "0") + { + try + { + var context = Android.App.Application.Context; + return Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId); + } + catch (Exception ex) + { + Log.WarnFormat("Unable to get client ID: {0}", ex); + return null; + } + } + else + { + return serialField; + } } } } From a578c43d0026723b1dd357f430cd9396eb1a8f60 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 3 Sep 2019 14:53:13 -0700 Subject: [PATCH 258/499] don't call the platform-specific method directly --- src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs | 2 +- .../PlatformSpecific/ClientIdentifier.android.cs | 2 +- .../PlatformSpecific/ClientIdentifier.ios.cs | 4 ++-- .../PlatformSpecific/ClientIdentifier.netstandard.cs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs index c46e50a4..4ca093ab 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs @@ -8,7 +8,7 @@ internal sealed class DefaultDeviceInfo : IDeviceInfo { public string UniqueDeviceId() { - return ClientIdentifier.PlatformGetOrCreateClientId(); + return ClientIdentifier.GetOrCreateClientId(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs index 56ed6ece..b856dddd 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -13,7 +13,7 @@ internal static partial class ClientIdentifier private static readonly ILog Log = LogManager.GetLogger(typeof(ClientIdentifier)); private static JniPeerMembers buildMembers = new XAPeerMembers("android/os/Build", typeof(Build)); - public static string PlatformGetOrCreateClientId() + private static string PlatformGetOrCreateClientId() { // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs string serialField; diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs index 5d24a355..27e779c4 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -4,8 +4,8 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { - // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. - public static string PlatformGetOrCreateClientId() + // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. + private static string PlatformGetOrCreateClientId() { return UIDevice.CurrentDevice.IdentifierForVendor.AsString(); } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs index feae56e0..1b3e8cc9 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs @@ -3,10 +3,10 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { - // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. - // Instead, we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a - // user key for this (OS) user account, and if not, generate a randomized ID and cache it. - public static string PlatformGetOrCreateClientId() + // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. + // Instead, we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a + // user key for this (OS) user account, and if not, generate a randomized ID and cache it. + private static string PlatformGetOrCreateClientId() { return GetOrCreateRandomizedClientId(); } From 9b8252ce7e5e0aec0c4160368b51d6170bf94778 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 3 Sep 2019 14:53:28 -0700 Subject: [PATCH 259/499] add tests for device ID --- .../AndroidSpecificTests.cs | 14 ++++++++++++++ .../IOsSpecificTests.cs | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index 1d6fa17f..7c3c1e07 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -25,5 +25,19 @@ public void UserHasOSAndDeviceAttributesForPlatform() Assert.Contains("device", user.Custom.Keys); } } + + [Fact] + public void CanGetUniqueUserKey() + { + var anonUser = User.Builder((string)null).Anonymous(true).Build(); + var config = TestUtil.ConfigWithFlagsJson(anonUser, "mobileKey", "{}") + .DeviceInfo(null).Build(); + using (var client = TestUtil.CreateClient(config, anonUser)) + { + var user = client.User; + Assert.NotNull(user.Key); + Assert.NotEqual("", user.Key); + } + } } } diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index 5f2d776f..80c86777 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -24,6 +24,20 @@ public void UserHasOSAndDeviceAttributesForPlatform() Assert.StartsWith("iOS ", user.Custom["os"].AsString); Assert.Contains("device", user.Custom.Keys); } + } + + [Fact] + public void CanGetUniqueUserKey() + { + var anonUser = User.Builder((string)null).Anonymous(true).Build(); + var config = TestUtil.ConfigWithFlagsJson(anonUser, "mobileKey", "{}") + .DeviceInfo(null).Build(); + using (var client = TestUtil.CreateClient(config, anonUser)) + { + var user = client.User; + Assert.NotNull(user.Key); + Assert.NotEqual("", user.Key); + } } } } From e24920ac9e54344f1463b5e3fe73583efb18fae6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 3 Sep 2019 18:58:52 -0700 Subject: [PATCH 260/499] adjust target frameworks for restwrapper CI builds --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 00c24d10..99c8758a 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -3,7 +3,8 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + netstandard1.6;netstandard2.0;MonoAndroid71;MonoAndroid80;MonoAndroid81; 1.0.0-beta23 Library LaunchDarkly.XamarinSdk From 7a6d8a89e684048e7b4fcfe3c1062924513441c5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 4 Sep 2019 09:23:44 -0700 Subject: [PATCH 261/499] adjust target frameworks for restwrapper CI builds --- .../LaunchDarkly.XamarinSdk.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 99c8758a..1511dc12 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -2,10 +2,10 @@ - netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - netstandard1.6;netstandard2.0;MonoAndroid71;MonoAndroid80;MonoAndroid81; - 1.0.0-beta23 + netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + $(LD_TARGET_FRAMEWORKS) + 0.0.3 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From e4a35b1bada0833c8dfa878ff9d4dd8e65eb03ef Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 4 Sep 2019 17:19:24 -0700 Subject: [PATCH 262/499] add Sandcastle doc build script + general doc comment cleanup --- .gitignore | 2 + docs/launchdarkly.css | 7 + docs/project.shfbproj | 91 +++++++++ scripts/build-docs.ps1 | 79 ++++++++ scripts/publish-docs.sh | 66 +++++++ src/LaunchDarkly.XamarinSdk/Configuration.cs | 49 +++-- .../ConfigurationBuilder.cs | 55 +++--- .../FlagChangedEvent.cs | 26 +-- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 183 +++++++++++------- .../LaunchDarkly.XamarinSdk.csproj | 1 + src/LaunchDarkly.XamarinSdk/LdClient.cs | 155 +++++++++------ src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs | 34 ++++ 12 files changed, 554 insertions(+), 194 deletions(-) create mode 100644 docs/launchdarkly.css create mode 100644 docs/project.shfbproj create mode 100644 scripts/build-docs.ps1 create mode 100755 scripts/publish-docs.sh create mode 100644 src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs diff --git a/.gitignore b/.gitignore index a13ec91a..43051c3b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ Thumbs.db # private key file *.snk + +docs/build/ diff --git a/docs/launchdarkly.css b/docs/launchdarkly.css new file mode 100644 index 00000000..4fd0fffd --- /dev/null +++ b/docs/launchdarkly.css @@ -0,0 +1,7 @@ + +/* LaunchDarkly additions to default Sandcastle styles */ + +/* hide search box because it is PHP-based and can't work in Github Pages */ +form#SearchForm { + display: none; +} diff --git a/docs/project.shfbproj b/docs/project.shfbproj new file mode 100644 index 00000000..ea52968c --- /dev/null +++ b/docs/project.shfbproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + 2.0 + {0555bc4c-d824-4f83-a272-6bcac93347a1} + 2017.9.26.0 + + Documentation + Documentation + Documentation + + .NET Framework 4.5 + .\build\html + Documentation + en-US + + + + + + + + LaunchDarkly Client-Side SDK for Xamarin $(LD_RELEASE_VERSION) + 1.0.0.0 + True + False + MemberName + 2 + False + Blank + Website + C#, Visual Basic + VS2013 + True + True + False + False + OnlyWarningsAndErrors + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + OnBuildSuccess + + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Common.Logging.dll + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Common.Logging.Core.dll + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\LaunchDarkly.Cache.dll + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\LaunchDarkly.EventSource.dll + + + ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Newtonsoft.Json.dll + + + \ No newline at end of file diff --git a/scripts/build-docs.ps1 b/scripts/build-docs.ps1 new file mode 100644 index 00000000..a6714b7a --- /dev/null +++ b/scripts/build-docs.ps1 @@ -0,0 +1,79 @@ + +# +# This script builds HTML documentation for the SDK using Sandcastle Help File Builder. It assumes that +# the Sandcastle software is already installed on the host. The Sandcastle GUI is not required, only the +# core tools and the SandcastleBuilderUtils package that provides the MSBuild targets. +# +# The script takes no parameters; it infers the project version by looking at the .csproj file. It starts +# by building the project in Debug configuration. +# +# Since some public APIs are provided by the LaunchDarkly.CommonSdk package, the Sandcastle project is +# configured to merge that package's documentation into this one, which requires some special file +# copying as seen below. +# + +# Terminate the script if any PowerShell command fails +$ErrorActionPreference = "Stop" + +# Terminate the script if any external command fails +function ExecuteOrFail { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, + [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd) + ) + & $cmd + if ($lastexitcode -ne 0) { + throw ($errorMessage) + } +} + +ExecuteOrFail { msbuild /restore /p:Configuration=Debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj } + +# Building the SDK causes the assemblies for all its package dependencies to be copied into bin\Debug\netstandard2.0. +# The .shfbproj is configured to expect them to be there. However, we also need the XML documentation file +# for LaunchDarkly.CommonSdk, which isn't automatically copied. We can get it out of the NuGet package +# cache, but first we need to determine what version of it we're using. +$match = Select-String ` + -Path src\LaunchDarkly.XamarinSdk\LaunchDarkly.XamarinSdk.csproj ` + -Pattern "([^<]*)" +if ($match.Matches.Length -ne 1) { + throw "Could not find SDK version string in project file" +} +$sdkVersion = $match.Matches[0].Groups[1].Value + +[System.Environment]::SetEnvironmentVariable("LD_RELEASE_VERSION", $sdkVersion, "Process") + +try +{ + Push-Location + Set-Location docs + ExecuteOrFail { msbuild project.shfbproj /p:Verbose=True } +} +finally +{ + Pop-Location +} + +# Add our own stylesheet overrides. You're supposed to be able to put customized stylesheets in +# ./styles (relative to the project file) and have them be automatically copied in, but that +# doesn't seem to work, so we'll just modify the CSS file after building. +Get-Content docs\launchdarkly.css | Add-Content docs\build\html\styles\branding-Website.css diff --git a/scripts/publish-docs.sh b/scripts/publish-docs.sh new file mode 100755 index 00000000..ebdd2fd0 --- /dev/null +++ b/scripts/publish-docs.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +set -ue + +# Publishes HTML content built by build-docs.ps1 to Github Pages. If the gh-pages branch +# doesn't already exist, we will create it. + +# This logic is copied from the publish-github-pages.sh script in Releaser. Once we are able +# to build docs in CI, this step can just be done by Releaser. + +if [ ! -d ./docs/build/html ]; then + echo "Docs have not been built" + exit 1 +fi + +if [ -z "${LD_RELEASE_VERSION:-}" ]; then + LD_RELEASE_VERSION=$(sed -n -e "s%.*\([^<]*\).*%\1%p" src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj) + if [ -z "${LD_RELEASE_VERSION}" ]; then + echo "Could not find SDK version string in project file" + exit 1 + fi +fi + +CONTENT_PATH=$(pwd)/docs/build/html +COMMIT_MESSAGE="Updating documentation to version ${LD_RELEASE_VERSION}" + +GH_PAGES_BRANCH=gh-pages + +LD_RELEASE_PROJECT_DIR=$(pwd) +LD_RELEASE_TEMP_DIR=$(mktemp -d) +trap "rm -rf ${LD_RELEASE_TEMP_DIR}" EXIT + +# Check for a prerelease version string like "2.0.0-beta.1" +if [[ "${LD_RELEASE_VERSION}" =~ '-' ]]; then + echo "Not publishing documentation because this is not a production release" + exit 0 +fi + +echo "Publishing to Github Pages" + +cd "${LD_RELEASE_PROJECT_DIR}" +GIT_URL="$(git remote get-url origin)" +GH_PAGES_CHECKOUT_DIR="${LD_RELEASE_TEMP_DIR}/gh-pages-checkout" + +rm -rf "${GH_PAGES_CHECKOUT_DIR}" +if git clone -b "${GH_PAGES_BRANCH}" --single-branch "${GIT_URL}" "${GH_PAGES_CHECKOUT_DIR}"; then + cd "${GH_PAGES_CHECKOUT_DIR}" + git rm -qr ./* || true +else + echo "Can't find ${GH_PAGES_BRANCH} branch; creating one from default branch" + git clone "${GIT_URL}" "${GH_PAGES_CHECKOUT_DIR}" + cd "${GH_PAGES_CHECKOUT_DIR}" + # branch off of the very first commit, so the history of the new branch will be simple + first_commit=$(git log --reverse --format=%H | head -n 1) + git checkout "${first_commit}" + git checkout -b "${GH_PAGES_BRANCH}" + git rm -qr ./* || true + git commit -m "clearing Github Pages branch" || true +fi + +touch .nojekyll # this turns off unneeded preprocessing by GH Pages which can break our docs +git add .nojekyll +cp -r "${CONTENT_PATH}"/* . +git add ./* +git commit -m "${COMMIT_MESSAGE}" || true # possibly there are no changes +git push origin "${GH_PAGES_BRANCH}" || { echo "push to gh-pages failed" >&2; exit 1; } diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index a3cfa2c3..2e175692 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -8,10 +8,10 @@ namespace LaunchDarkly.Xamarin { /// - /// This class exposes advanced configuration options for . + /// Configuration options for . /// /// - /// Instances of Configuration are immutable once created. They can be created with the factory method + /// Instances of are immutable once created. They can be created with the factory method /// , or using a builder pattern with /// or . /// @@ -56,11 +56,9 @@ public sealed class Configuration /// the LaunchDarkly server). /// /// - /// If this is true, all of the user attributes will be private, not just the attributes specified with - /// or with the - /// method in . - /// - /// By default, this is false. + /// By default, this is . If , all of the user attributes + /// will be private, not just the attributes specified with + /// or with the method. /// public bool AllAttributesPrivate => _allAttributesPrivate; @@ -98,12 +96,12 @@ public sealed class Configuration /// The additional information will then be available through the client's "detail" /// methods such as . Since this /// increases the size of network requests, such information is not sent unless you set this option - /// to true. + /// to . /// public bool EvaluationReasons => _evaluationReasons; /// - /// The capacity of the events buffer. + /// The capacity of the event buffer. /// /// /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded @@ -134,8 +132,8 @@ public sealed class Configuration /// Sets whether to include full user details in every analytics event. /// /// - /// The default is false: events will only include the user key, except for one "index" event that - /// provides the full details for the user. + /// The default is : events will only include the user key, except for one + /// "index" event that provides the full details for the user. /// public bool InlineUsersInEvents => _inlineUsersInEvents; @@ -156,7 +154,7 @@ public sealed class Configuration public string MobileKey => _mobileKey; /// - /// Whether or not this client is offline. If true, no calls to Launchdarkly will be made. + /// Whether or not this client is offline. If , no calls to Launchdarkly will be made. /// public bool Offline => _offline; @@ -165,7 +163,7 @@ public sealed class Configuration /// immediately available the next time the SDK is started for the same user. /// /// - /// The default is true. + /// The default is . /// public bool PersistFlagValues => _persistFlagValues; @@ -180,7 +178,7 @@ public sealed class Configuration /// /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name /// removed, even if you did not use the - /// method in . + /// method when building the user. /// public IImmutableSet PrivateAttributeNames => _privateAttributeNames; @@ -252,32 +250,31 @@ public sealed class Configuration internal static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); /// - /// Creates a configuration with all parameters set to the default. Use extension methods - /// to set additional parameters. + /// Creates a configuration with all parameters set to the default. /// /// the SDK key for your LaunchDarkly environment - /// a Configuration instance + /// a instance public static Configuration Default(string mobileKey) { return Builder(mobileKey).Build(); } /// - /// Creates a ConfigurationBuilder for constructing a configuration object using a fluent syntax. + /// Creates an for constructing a configuration object using a fluent syntax. /// /// - /// This is the only method for building a Configuration if you are setting properties - /// besides the MobileKey. The ConfigurationBuilder has methods for setting any number of - /// properties, after which you call to get the resulting + /// This is the only method for building a if you are setting properties + /// besides the MobileKey. The has methods for setting any number of + /// properties, after which you call to get the resulting /// Configuration instance. /// /// - ///
+        /// 
         ///     var config = Configuration.Builder("my-sdk-key")
-        ///         .EventQueueFrequency(TimeSpan.FromSeconds(90))
+        ///         .EventFlushInterval(TimeSpan.FromSeconds(90))
         ///         .StartWaitTime(TimeSpan.FromSeconds(5))
         ///         .Build();
-        /// 
+ ///
///
/// the mobile SDK key for your LaunchDarkly environment /// a builder object @@ -291,7 +288,7 @@ public static IConfigurationBuilder Builder(string mobileKey) } /// - /// Exposed for test code that needs to access the internal methods of ConfigurationBuilder that + /// Exposed for test code that needs to access the internal methods of that /// are not in . /// /// the mobile SDK key @@ -302,7 +299,7 @@ internal static ConfigurationBuilder BuilderInternal(string mobileKey) } /// - /// Creates a ConfigurationBuilder starting with the properties of an existing Configuration. + /// Creates an starting with the properties of an existing . /// /// the configuration to copy /// a builder object diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 008e5230..1b50fed4 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -10,10 +10,13 @@ namespace LaunchDarkly.Xamarin /// A mutable object that uses the Builder pattern to specify properties for a object. ///
/// + /// /// Obtain an instance of this class by calling . - /// + /// + /// /// All of the builder methods for setting a configuration property return a reference to the same builder, so they can be /// chained together. + /// /// /// /// @@ -24,7 +27,7 @@ public interface IConfigurationBuilder { /// /// Creates a based on the properties that have been set on the builder. - /// Modifying the builder after this point does not affect the returned Configuration. + /// Modifying the builder after this point does not affect the returned . /// /// the configured Configuration object Configuration Build(); @@ -34,11 +37,9 @@ public interface IConfigurationBuilder /// the LaunchDarkly server). ///
/// - /// If this is true, all of the user attributes will be private, not just the attributes specified with - /// or with the - /// method with . - /// - /// By default, this is false. + /// By default, this is . If , all of the user attributes + /// will be private, not just the attributes specified with + /// or with the method. /// /// true if all attributes should be private /// the same builder @@ -79,36 +80,36 @@ public interface IConfigurationBuilder /// By default, on Android and iOS the SDK can still receive feature flag updates when an application /// is in the background, but it will use polling rather than maintaining a streaming connection (and /// will use rather than ). - /// If you set EnableBackgroundUpdating to false, it will not check for feature flag updates - /// until the application returns to the foreground. + /// If you set this property to false, it will not check for feature flag updates until the + /// application returns to the foreground. /// - /// true if background updating should be allowed + /// if background updating should be allowed /// the same builder IConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating); /// - /// Set to true if LaunchDarkly should provide additional information about how flag values were + /// Set to if LaunchDarkly should provide additional information about how flag values were /// calculated. /// /// /// The additional information will then be available through the client's "detail" /// methods such as . Since this /// increases the size of network requests, such information is not sent unless you set this option - /// to true. + /// to . /// - /// True if evaluation reasons are desired. + /// if evaluation reasons are desired /// the same builder IConfigurationBuilder EvaluationReasons(bool evaluationReasons); /// - /// Sets the capacity of the events buffer. + /// Sets the capacity of the event buffer. /// /// /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events /// are less likely to be discarded, at the cost of consuming more memory. /// - /// the capacity of the events buffer + /// the capacity of the event buffer /// the same builder IConfigurationBuilder EventCapacity(int eventCapacity); @@ -137,10 +138,10 @@ public interface IConfigurationBuilder /// This is exposed mainly for testing purposes; you should not normally need to change it. /// By default, on mobile platforms it will use the appropriate native HTTP handler for the /// current platform, if any (e.g. Xamarin.Android.Net.AndroidClientHandler). If this is - /// null, the SDK will call the default constructor without + /// , the SDK will call the default constructor without /// specifying a handler, which may or may not result in using a native HTTP handler. /// - /// the HttpMessageHandler to use + /// the to use /// the same builder IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler); @@ -148,8 +149,8 @@ public interface IConfigurationBuilder /// Sets whether to include full user details in every analytics event. ///
/// - /// The default is false: events will only include the user key, except for one "index" event that - /// provides the full details for the user. + /// The default is : events will only include the user key, except for one + /// "index" event that provides the full details for the user. /// /// true or false /// the same builder @@ -159,7 +160,7 @@ public interface IConfigurationBuilder /// Sets whether or not the streaming API should be used to receive flag updates. ///
/// - /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. + /// This is by default. Streaming should only be disabled on the advice of LaunchDarkly support. /// /// true if the streaming API should be used /// the same builder @@ -176,9 +177,9 @@ public interface IConfigurationBuilder IConfigurationBuilder MobileKey(string mobileKey); /// - /// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made. + /// Sets whether or not this client is offline. If , no calls to Launchdarkly will be made. /// - /// true if the client should remain offline + /// if the client should remain offline /// the same builder IConfigurationBuilder Offline(bool offline); @@ -187,10 +188,9 @@ public interface IConfigurationBuilder /// immediately available the next time the SDK is started for the same user. ///
/// - /// The default is true. + /// The default is . /// - /// true to save flag values - /// the same Configuration instance + /// to save flag values /// the same builder IConfigurationBuilder PersistFlagValues(bool persistFlagValues); @@ -209,11 +209,14 @@ public interface IConfigurationBuilder /// Marks an attribute name as private for all users. ///
/// + /// /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name /// removed, even if you did not use the /// method in . - /// + /// + /// /// You may call this method repeatedly to mark multiple attributes as private. + /// /// /// the attribute name /// the same builder diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index d7d87522..84acdd63 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -20,25 +20,29 @@ public sealed class FlagChangedEventArgs /// The updated value of the flag for the current user. ///
/// + /// /// Since flag values can be of any JSON type, this property is an . You - /// can use convenience properties of ImmutableJsonValue such as AsBool to convert it to a - /// primitive type, or AsJToken() for complex types. - /// - /// Flag evaluations always produce non-null values, but this property could still be null if the flag was + /// can use convenience properties of such as + /// to convert it to a primitive type, or to access it as a complex Newtonsoft.Json type. + /// + /// + /// Flag evaluations always produce non-null values, but this property could still be if the flag was /// completely deleted or if it could not be evaluated due to an error of some kind. - /// - /// Note that in those cases, the Variation methods may return a different result from this property, + /// + /// + /// Note that in those cases, the Variation methods may return a different result from this property, /// because of their "default value" behavior. For instance, if the flag "feature1" has been deleted, the /// following expression will return the string "xyz", because that is the default value that you specified /// in the method call: - /// + /// /// /// client.StringVariation("feature1", "xyz"); /// - /// - /// But when a FlagChangedEvent is sent for the deletion of the flag, it has no way to know that you - /// would have specified "xyz" as a default value when evaluating the flag, so NewValue will simply - /// contain a null. + /// + /// But when an event is sent for the deletion of the flag, it has no way to know that you would have + /// specified "xyz" as a default value when evaluating the flag, so will simply + /// contain a . + /// /// public ImmutableJsonValue NewValue { get; private set; } diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index 41077a5e..ec8be6e6 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -15,17 +15,19 @@ public interface ILdClient : IDisposable /// Returns a boolean value indicating LaunchDarkly connection and flag state within the client. ///
/// - /// When you first start the client, once Init or InitAsync has returned, - /// Initialized should be true if and only if either 1. it connected to LaunchDarkly - /// and successfully retrieved flags, or 2. it started in offline mode so - /// there's no need to connect to LaunchDarkly. So if the client timed out trying to - /// connect to LD, then Initialized is false (even if we do have cached flags). - /// If the client connected and got a 401 error, Initialized is false. - /// This serves the purpose of letting the app know that - /// there was a problem of some kind. Initialized will be temporarily false during the - /// time in between calling Identify and receiving the new user's flags. It will also be false - /// if you switch users with Identify and the client is unable - /// to get the new user's flags from LaunchDarkly. + /// + /// When you first start the client, once or + /// has returned, should be + /// if and only if either 1. it connected to LaunchDarkly and successfully retrieved + /// flags, or 2. it started in offline mode so there's no need to connect to LaunchDarkly. If the client + /// timed out trying to connect to LD, then is (even if we + /// do have cached flags). If the client connected and got a 401 error, is + /// . This serves the purpose of letting the app know that there was a problem of some kind. + /// + /// + /// If you call or , + /// will become until the SDK receives the new user's flags. + /// /// bool Initialized { get; } @@ -33,13 +35,17 @@ public interface ILdClient : IDisposable /// Indicates whether the SDK is configured to be always offline. ///
/// - /// This is initially true if you set it to true in the configuration with . - /// However, you can change it at any time to allow the client to go online, or force it to go offline, - /// using or . - /// - /// When Offline is false, the SDK connects to LaunchDarkly if possible, but this does not guarantee - /// that the connection is successful. There is currently no mechanism to detect whether the SDK is currently - /// connected to LaunchDarkly. + /// + /// This is initially if you set it to in the configuration with + /// . However, you can change it at any time to allow the client + /// to go online, or force it to go offline, using or + /// . + /// + /// + /// When is , the SDK connects to LaunchDarkly if possible, but + /// this does not guarantee that the connection is successful. There is currently no mechanism to detect whether + /// the SDK is currently connected to LaunchDarkly. + /// /// bool Offline { get; } @@ -47,20 +53,26 @@ public interface ILdClient : IDisposable /// Sets whether the SDK should be always offline. ///
/// + /// /// This is equivalent to , but as a synchronous method. - /// - /// If you set the property to true, any existing connection will be dropped, and the method immediately - /// returns false. - /// - /// If you set it to false when it was previously true, but no connection can be made because the network - /// is not available, the method immediately returns false, but the SDK will attempt to connect later if - /// the network becomes available. - /// - /// If you set it to false when it was previously true, and the network is available, the SDK will attempt - /// to connect to LaunchDarkly. If the connection succeeds within the interval maxWaitTime, the - /// method returns true. If the connection permanently fails (e.g. if the mobile key is invalid), the - /// method returns false. If the connection attempt is still in progress after maxWaitTime elapses, - /// the method returns false, but the connection might succeed later. + /// + /// + /// If you set the property to , any existing connection will be dropped, and the + /// method immediately returns . + /// + /// + /// If you set it to when it was previously , but no connection can + /// be made because the network is not available, the method immediately returns , but the + /// SDK will attempt to connect later if the network becomes available. + /// + /// + /// If you set it to when it was previously , and the network is + /// available, the SDK will attempt to connect to LaunchDarkly. If the connection succeeds within the interval + /// maxWaitTime, the method returns . If the connection permanently fails (e.g. if + /// the mobile key is invalid), the method returns . If the connection attempt is still in + /// progress after maxWaitTime elapses, the method returns , but the connection + /// might succeed later. + /// /// /// true if the client should be always offline /// the maximum length of time to wait for a connection @@ -71,18 +83,24 @@ public interface ILdClient : IDisposable /// Sets whether the SDK should be always offline. ///
/// + /// /// This is equivalent to , but as an asynchronous method. - /// - /// If you set the property to true, any existing connection will be dropped, and the task immediately - /// yields false. - /// - /// If you set it to false when it was previously true, but no connection can be made because the network - /// is not available, the task immediately yields false, but the SDK will attempt to connect later if - /// the network becomes available. - /// - /// If you set it to false when it was previously true, and the network is available, the SDK will attempt - /// to connect to LaunchDarkly. If and when the connection succeeds, the task yields true. If and when the - /// connection permanently fails (e.g. if the mobile key is invalid), the task yields false. + /// + /// + /// If you set the property to , any existing connection will be dropped, and the + /// task immediately yields . + /// + /// + /// If you set it to when it was previously , but no connection can + /// be made because the network is not available, the task immediately yields , but the + /// SDK will attempt to connect later if the network becomes available. + /// + /// + /// If you set it to when it was previously , and the network is + /// available, the SDK will attempt to connect to LaunchDarkly. If and when the connection succeeds, the task + /// yields . If and when the connection permanently fails (e.g. if the mobile key is + /// invalid), the task yields . + /// /// /// true if the client should be always offline /// a task that yields true if a new connection was successfully made @@ -102,8 +120,8 @@ public interface ILdClient : IDisposable /// describes the way the value was determined. ///
/// - /// The Reason property in the result will also be included in analytics events, if you are - /// capturing detailed event data for this flag. + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -124,8 +142,8 @@ public interface ILdClient : IDisposable /// describes the way the value was determined. ///
/// - /// The Reason property in the result will also be included in analytics events, if you are - /// capturing detailed event data for this flag. + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -146,8 +164,8 @@ public interface ILdClient : IDisposable /// describes the way the value was determined. ///
/// - /// The Reason property in the result will also be included in analytics events, if you are - /// capturing detailed event data for this flag. + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -168,8 +186,8 @@ public interface ILdClient : IDisposable /// describes the way the value was determined. ///
/// - /// The Reason property in the result will also be included in analytics events, if you are - /// capturing detailed event data for this flag. + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -190,8 +208,8 @@ public interface ILdClient : IDisposable /// describes the way the value was determined. ///
/// - /// The Reason property in the result will also be included in analytics events, if you are - /// capturing detailed event data for this flag. + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -212,13 +230,17 @@ public interface ILdClient : IDisposable void Track(string eventName); /// - /// Returns a map from feature flag keys to feature flag values for the current user. + /// Returns a map from feature flag keys to feature flag values for the current user. /// /// + /// /// If the result of a flag's value would have returned the default variation, the value in the map will contain - /// null instead of a JToken. If the client is offline or has not been initialized, a null map will be returned. - /// + /// . If the client is offline or has not been initialized, an empty + /// map will be returned. + /// + /// /// This method will not send analytics events back to LaunchDarkly. + /// /// /// a map from feature flag keys to values for the current user IDictionary AllFlags(); @@ -227,18 +249,22 @@ public interface ILdClient : IDisposable /// This event is triggered when the client has received an updated value for a feature flag. ///
/// + /// /// This could mean that the flag configuration was changed in LaunchDarkly, or that you have changed the current /// user and the flag values are different for this user than for the previous user. The event is only triggered /// if the newly received flag value is actually different from the previous one. - /// + /// + /// /// The properties will indicate the key of the feature flag, the new value, /// and the previous value. - /// + /// + /// /// On platforms that have a main UI thread (such as iOS and Android), handlers for this event are guaranteed to /// be called on that thread; on other platforms, the SDK uses a thread pool. Either way, the handler is called /// called asynchronously after whichever SDK action triggered the flag change has already completed. This is to /// avoid deadlocks, in case the action was also on the main thread, or on a thread that was holding a lock on /// some application resource that the handler also uses. + /// /// /// /// @@ -256,14 +282,19 @@ public interface ILdClient : IDisposable /// an analytics event to tell LaunchDarkly about the user. ///
/// + /// /// This is equivalent to , but as a synchronous method. - /// - /// If the SDK is online, Identify waits to receive feature flag values for the new user from - /// LaunchDarkly. If it receives the new flag values before maxWaitTime has elapsed, it returns true. - /// If the timeout elapses, it returns false (although the SDK might still receive the flag values later). - /// If we do not need to request flags from LaunchDarkly because we are in offline mode, it returns true. - /// + /// + /// + /// If the SDK is online, waits to receive feature flag values for the new user from + /// LaunchDarkly. If it receives the new flag values before maxWaitTime has elapsed, it returns + /// . If the timeout elapses, it returns (although the SDK might + /// still receive the flag values later). If we do not need to request flags from LaunchDarkly because we are + /// in offline mode, it returns . + /// + /// /// If you do not want to wait, you can either set maxWaitTime to zero or call . + /// /// /// the new user /// the maximum time to wait for the new flag values @@ -275,19 +306,37 @@ public interface ILdClient : IDisposable /// an analytics event to tell LaunchDarkly about the user. ///
/// + /// /// This is equivalent to , but as an asynchronous method. - /// + /// + /// /// If the SDK is online, the returned task is completed once the SDK has received feature flag values for the - /// new user from LaunchDarkly, or received an unrecoverable error; it yields true for success or false for an - /// error. If the SDK is offline, the returned task is completed immediately and yields true. + /// new user from LaunchDarkly, or received an unrecoverable error; it yields for success + /// or for an error. If the SDK is offline, the returned task is completed immediately + /// and yields . + /// /// /// the new user /// a task that yields true if new flag values were obtained Task IdentifyAsync(User user); /// - /// Flushes all pending events. + /// Tells the client that all pending analytics events should be delivered as soon as possible. /// + /// + /// + /// When the LaunchDarkly client generates analytics events (from flag evaluations, or from + /// or ), they are queued on a worker thread. + /// The event thread normally sends all queued events to LaunchDarkly at regular intervals, controlled by the + /// option. Calling triggers a send + /// without waiting for the next interval. + /// + /// + /// Flushing is asynchronous, so this method will return before it is complete. However, if you + /// shut down the client with , events are guaranteed to be + /// sent before that method returns. + /// + /// void Flush(); } } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 1511dc12..ec88e1af 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -15,6 +15,7 @@ latest True False + true diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 789bbad1..c4da1dfe 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -47,17 +47,17 @@ public sealed class LdClient : ILdClient ///
/// /// Use the static factory methods or - /// to set this LdClient instance. + /// to set this instance. /// public static LdClient Instance => _instance; /// - /// Returns the current version number of the LaunchDarkly client. + /// The current version string of the SDK. /// public static Version Version => MobileClientEnvironment.Instance.Version; /// - /// The Configuration instance used to setup the LdClient. + /// The instance used to set up the LdClient. /// public Configuration Config => _config; @@ -71,33 +71,37 @@ public sealed class LdClient : ILdClient ///
public User User => LockUtils.WithReadLock(_stateLock, () => _user); - /// + /// public bool Offline => _connectionManager.ForceOffline; - /// + /// public bool Initialized => _connectionManager.Initialized; /// /// Indicates which platform the SDK is built for. /// /// + /// /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, /// but rather which variant of the SDK is currently in use. - /// + /// + /// /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard /// variant. - /// + /// + /// /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific /// behavior such as detecting when an application has gone into the background, detecting network connectivity, /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find /// that these platform-specific behaviors are not working correctly, you may want to check this property to /// make sure you are not for some reason running the .NET Standard SDK on a phone. + /// /// public static PlatformType PlatformType => UserMetadata.PlatformType; - /// + /// public event EventHandler FlagChanged { add @@ -181,24 +185,31 @@ async Task StartAsync() } /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. + /// Creates a new singleton instance and attempts to initialize feature flags. /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. + /// + /// + /// In offline mode, this constructor will return immediately. Otherwise, it will wait and block on + /// the current thread until initialization and the first response from the LaunchDarkly service is + /// returned, up to the specified timeout. If the timeout elapses, the returned instance will have + /// an property of . + /// + /// + /// If you would rather this happen asynchronously, use . To + /// specify additional configuration options rather than just the mobile key, use + /// or . + /// + /// + /// You must use one of these static factory methods to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// + /// the singleton instance + /// the mobile key given to you by LaunchDarkly + /// the user needed for client operations (must not be ); + /// if the user's is and + /// is , it will be assigned a key that uniquely identifies this device + /// the maximum length of time to wait for the client to initialize public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) { var config = Configuration.Default(mobileKey); @@ -207,19 +218,23 @@ public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) } /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. This constructor should be used if you do not want to wait - /// for the client to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. + /// Creates a new singleton instance and attempts to initialize feature flags asynchronously. /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// + /// + /// The returned task will yield the instance once the first response from + /// the LaunchDarkly service is returned (or immediately if it is in offline mode). + /// + /// + /// You must use one of these static factory methods to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// + /// the singleton instance + /// the mobile key given to you by LaunchDarkly + /// the user needed for client operations (must not be ); + /// if the user's is and + /// is , it will be assigned a key that uniquely identifies this device public static async Task InitAsync(string mobileKey, User user) { var config = Configuration.Default(mobileKey); @@ -230,15 +245,24 @@ public static async Task InitAsync(string mobileKey, User user) /// /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. /// + /// + /// + /// In offline mode, this constructor will return immediately. Otherwise, it will wait and block on + /// the current thread until initialization and the first response from the LaunchDarkly service is + /// returned, up to the specified timeout. If the timeout elapses, the returned instance will have + /// an property of . + /// + /// + /// If you would rather this happen asynchronously, use . + /// If you do not need to specify configuration options other than the mobile key, you can use + /// or . + /// + /// + /// You must use one of these static factory methods to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// /// The singleton LdClient instance. /// The client configuration object /// The user needed for client operations. Must not be null. @@ -295,74 +319,74 @@ static LdClient CreateInstance(Configuration configuration, User user) } } - /// + /// public bool SetOffline(bool value, TimeSpan maxWaitTime) { return AsyncUtils.WaitSafely(() => SetOfflineAsync(value), maxWaitTime); } - /// + /// public async Task SetOfflineAsync(bool value) { eventProcessor.SetOffline(value || !_connectionManager.NetworkEnabled); await _connectionManager.SetForceOffline(value); } - /// + /// public bool BoolVariation(string key, bool defaultValue = false) { return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); } - /// + /// public string StringVariation(string key, string defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); } - /// + /// public float FloatVariation(string key, float defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); } - /// + /// public int IntVariation(string key, int defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); } - /// + /// public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; } - /// + /// public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) { return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); @@ -438,32 +462,32 @@ private void SendEventIfOnline(Event e) } } - /// + /// public IDictionary AllFlags() { return flagCacheManager.FlagsForUser(User) .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); } - /// + /// public void Track(string eventName, ImmutableJsonValue data) { SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); } - /// + /// public void Track(string eventName) { Track(eventName, ImmutableJsonValue.Null); } - /// + /// public void Flush() { eventProcessor.Flush(); // eventProcessor will ignore this if it is offline } - /// + /// public bool Identify(User user, TimeSpan maxWaitTime) { if (user == null) @@ -474,7 +498,7 @@ public bool Identify(User user, TimeSpan maxWaitTime) return AsyncUtils.WaitSafely(() => IdentifyAsync(user), maxWaitTime); } - /// + /// public async Task IdentifyAsync(User user) { if (user == null) @@ -532,11 +556,14 @@ User DecorateUser(User user) /// Permanently shuts down the SDK client. ///
/// + /// /// This method closes all network collections, shuts down all background tasks, and releases any other /// resources being held by the SDK. - /// + /// + /// /// If there are any pending analytics events, and if the SDK is online, it attempts to deliver the events /// to LaunchDarkly before closing. + /// /// public void Dispose() { diff --git a/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs b/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs new file mode 100644 index 00000000..e8403291 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs @@ -0,0 +1,34 @@ +// This file is not a real class but is used by Sandcastle Help File Builder to provide documentation +// for the namespace. The other way to document a namespace is to add this XML to the .shfbproj file: +// +// +// +// ...summary here... +// +// +// However, currently Sandcastle does not correctly resolve links if you use that method. + +namespace LaunchDarkly.Xamarin +{ + /// + /// This is the main namespace for the LaunchDarkly Xamarin SDK. You will most often use + /// (the SDK client) and (configuration options for the client). The SDK also uses types + /// from , such as . + /// + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + class NamespaceDoc + { + } +} + +namespace LaunchDarkly.Client +{ + /// + /// This namespace contains types that are shared between the Xamarin and .NET SDKs, such as . + /// + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + class NamespaceDoc + { + } +} From 0a6cd8aabd40cabc6326a1e40132894808702ca8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 5 Sep 2019 12:55:06 -0700 Subject: [PATCH 263/499] fix "Launchdarkly" --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 2 +- .../ConfigurationBuilder.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 2e175692..55e08f27 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -154,7 +154,7 @@ public sealed class Configuration public string MobileKey => _mobileKey; /// - /// Whether or not this client is offline. If , no calls to Launchdarkly will be made. + /// Whether or not this client is offline. If , no calls to LaunchDarkly will be made. /// public bool Offline => _offline; diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 1b50fed4..1d40d946 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -45,13 +45,13 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); - /// - /// Sets the interval between feature flag updates when the application is running in the background. - /// - /// - /// This is only relevant on mobile platforms. The default is ; - /// the minimum is . - /// + /// + /// Sets the interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. The default is ; + /// the minimum is . + /// /// the background polling interval /// the same builder IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); @@ -177,7 +177,7 @@ public interface IConfigurationBuilder IConfigurationBuilder MobileKey(string mobileKey); /// - /// Sets whether or not this client is offline. If , no calls to Launchdarkly will be made. + /// Sets whether or not this client is offline. If , no calls to LaunchDarkly will be made. /// /// if the client should remain offline /// the same builder From 0c5f3712e705a15df09962af781ba3f714128382 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 5 Sep 2019 12:55:48 -0700 Subject: [PATCH 264/499] symbol style for Json namespace --- src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 84acdd63..f93a4471 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -23,7 +23,7 @@ public sealed class FlagChangedEventArgs /// /// Since flag values can be of any JSON type, this property is an . You /// can use convenience properties of such as - /// to convert it to a primitive type, or to access it as a complex Newtonsoft.Json type. + /// to convert it to a primitive type, or to access it as a complex type. /// /// /// Flag evaluations always produce non-null values, but this property could still be if the flag was @@ -74,7 +74,7 @@ internal interface IFlagChangedEventManager internal sealed class FlagChangedEventManager : IFlagChangedEventManager { - private static readonly ILog Log = LogManager.GetLogger(typeof(IFlagChangedEventManager)); + private static readonly ILog Log = LogManager.GetLogger(typeof(IFlagChangedEventManager)); public event EventHandler FlagChanged; From beb73b9def0e0fd2a50089e29f99162920818a4b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 6 Sep 2019 15:44:47 -0700 Subject: [PATCH 265/499] remove JToken from public APIs and nearly everywhere else --- src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 5 +- .../FlagCacheManager.cs | 17 ++-- .../FlagChangedEvent.cs | 25 +++--- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 1 - .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 13 ++- src/LaunchDarkly.XamarinSdk/ValueType.cs | 82 ------------------- .../FeatureFlagRequestorTests.cs | 2 - .../FlagCacheManagerTests.cs | 21 +++-- .../FlagChangedEventTests.cs | 11 +-- .../LDClientEndToEndTests.cs | 19 ++--- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LdClientEvaluationTests.cs | 40 ++++----- .../LdClientEventTests.cs | 43 +++++----- .../LdClientTests.cs | 7 +- .../MobileStreamingProcessorTests.cs | 14 ++-- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 4 +- 17 files changed, 109 insertions(+), 199 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/ValueType.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index ef0a1647..de663578 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -1,5 +1,4 @@ using System; -using Newtonsoft.Json.Linq; using LaunchDarkly.Client; using LaunchDarkly.Common; @@ -7,7 +6,7 @@ namespace LaunchDarkly.Xamarin { internal sealed class FeatureFlag : IEquatable { - public JToken value; + public ImmutableJsonValue value; public int version; public int? flagVersion; public bool trackEvents; @@ -18,7 +17,7 @@ internal sealed class FeatureFlag : IEquatable public bool Equals(FeatureFlag otherFlag) { - return JToken.DeepEquals(value, otherFlag.value) + return value.Equals(otherFlag.value) && version == otherFlag.version && flagVersion == otherFlag.flagVersion && trackEvents == otherFlag.trackEvents diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index af614883..ae0b73d8 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin { @@ -45,7 +44,7 @@ public IDictionary FlagsForUser(User user) public void CacheFlagsFromService(IDictionary flags, User user) { - List> changes = null; + List> changes = null; readWriteLock.EnterWriteLock(); try { @@ -57,11 +56,11 @@ public void CacheFlagsFromService(IDictionary flags, User u { if (previousFlags.TryGetValue(flag.Key, out var originalFlag)) { - if (!JToken.DeepEquals(originalFlag.value, flag.Value.value)) + if (!originalFlag.value.Equals(flag.Value.value)) { if (changes == null) { - changes = new List>(); + changes = new List>(); } changes.Add(Tuple.Create(flag.Key, flag.Value.value, originalFlag.value)); } @@ -93,13 +92,15 @@ public FeatureFlag FlagForUser(string flagKey, User user) public void RemoveFlagForUser(string flagKey, User user) { - JToken oldValue = null; + ImmutableJsonValue oldValue; + bool existed = false; readWriteLock.EnterWriteLock(); try { var flagsForUser = inMemoryCache.RetrieveFlags(user); if (flagsForUser.TryGetValue(flagKey, out var flag)) { + existed = true; oldValue = flag.value; flagsForUser.Remove(flagKey); deviceCache.CacheFlagsForUser(flagsForUser, user); @@ -110,7 +111,7 @@ public void RemoveFlagForUser(string flagKey, User user) { readWriteLock.ExitWriteLock(); } - if (oldValue != null) + if (existed) { flagChangedEventManager.FlagWasDeleted(flagKey, oldValue); } @@ -119,14 +120,14 @@ public void RemoveFlagForUser(string flagKey, User user) public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) { bool changed = false; - JToken oldValue = null; + ImmutableJsonValue oldValue; readWriteLock.EnterWriteLock(); try { var flagsForUser = inMemoryCache.RetrieveFlags(user); if (flagsForUser.TryGetValue(flagKey, out var oldFlag)) { - if (!JToken.DeepEquals(oldFlag.value, featureFlag.value)) + if (!oldFlag.value.Equals(featureFlag.value)) { oldValue = oldFlag.value; changed = true; diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index f93a4471..0009f2f7 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -2,7 +2,6 @@ using System.Linq; using Common.Logging; using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin { @@ -22,12 +21,12 @@ public sealed class FlagChangedEventArgs /// /// /// Since flag values can be of any JSON type, this property is an . You - /// can use convenience properties of such as - /// to convert it to a primitive type, or to access it as a complex type. + /// can use properties and methods of such as + /// to convert it to other types. /// /// - /// Flag evaluations always produce non-null values, but this property could still be if the flag was - /// completely deleted or if it could not be evaluated due to an error of some kind. + /// Flag evaluations always produce non-null values, but this property could still be + /// if the flag was completely deleted or if it could not be evaluated due to an error of some kind. /// /// /// Note that in those cases, the Variation methods may return a different result from this property, @@ -56,11 +55,11 @@ public sealed class FlagChangedEventArgs ///
public bool FlagWasDeleted { get; private set; } - internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool flagWasDeleted) + internal FlagChangedEventArgs(string key, ImmutableJsonValue newValue, ImmutableJsonValue oldValue, bool flagWasDeleted) { Key = key; - NewValue = ImmutableJsonValue.FromSafeValue(newValue); - OldValue = ImmutableJsonValue.FromSafeValue(oldValue); + NewValue = newValue; + OldValue = oldValue; FlagWasDeleted = flagWasDeleted; } } @@ -68,8 +67,8 @@ internal FlagChangedEventArgs(string key, JToken newValue, JToken oldValue, bool internal interface IFlagChangedEventManager { event EventHandler FlagChanged; - void FlagWasDeleted(string flagKey, JToken oldValue); - void FlagWasUpdated(string flagKey, JToken newValue, JToken oldValue); + void FlagWasDeleted(string flagKey, ImmutableJsonValue oldValue); + void FlagWasUpdated(string flagKey, ImmutableJsonValue newValue, ImmutableJsonValue oldValue); } internal sealed class FlagChangedEventManager : IFlagChangedEventManager @@ -83,12 +82,12 @@ public bool IsHandlerRegistered(EventHandler handler) return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); } - public void FlagWasDeleted(string flagKey, JToken oldValue) + public void FlagWasDeleted(string flagKey, ImmutableJsonValue oldValue) { - FireEvent(new FlagChangedEventArgs(flagKey, null, oldValue, true)); + FireEvent(new FlagChangedEventArgs(flagKey, ImmutableJsonValue.Null, oldValue, true)); } - public void FlagWasUpdated(string flagKey, JToken newValue, JToken oldValue) + public void FlagWasUpdated(string flagKey, ImmutableJsonValue newValue, ImmutableJsonValue oldValue) { FireEvent(new FlagChangedEventArgs(flagKey, newValue, oldValue, false)); } diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index ec8be6e6..2d769a32 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using System.Threading.Tasks; using LaunchDarkly.Client; diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ec88e1af..a1476f72 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index c4da1dfe..cde06b2e 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -7,7 +7,6 @@ using LaunchDarkly.Client; using LaunchDarkly.Common; using LaunchDarkly.Xamarin.PlatformSpecific; -using Newtonsoft.Json.Linq; namespace LaunchDarkly.Xamarin { @@ -395,7 +394,7 @@ public EvaluationDetail JsonVariationDetail(string key, Immu EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) { FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); - JToken defaultJson = desiredType.ValueToJson(defaultValue); + ImmutableJsonValue defaultJson = desiredType.ValueToJson(defaultValue); EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); @@ -428,8 +427,8 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => featureFlagEvent = new FeatureFlagEvent(featureKey, flag); EvaluationDetail result; - JToken valueJson; - if (flag.value == null || flag.value.Type == JTokenType.Null) + ImmutableJsonValue valueJson; + if (flag.value.IsNull) { valueJson = defaultJson; result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); @@ -449,7 +448,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } } var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, - new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); + new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); SendEventIfOnline(featureEvent); return result; } @@ -466,13 +465,13 @@ private void SendEventIfOnline(Event e) public IDictionary AllFlags() { return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); + .ToDictionary(p => p.Key, p => p.Value.value); } /// public void Track(string eventName, ImmutableJsonValue data) { - SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data.AsJToken())); + SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data)); } /// diff --git a/src/LaunchDarkly.XamarinSdk/ValueType.cs b/src/LaunchDarkly.XamarinSdk/ValueType.cs deleted file mode 100644 index 4222f1ff..00000000 --- a/src/LaunchDarkly.XamarinSdk/ValueType.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - internal sealed class ValueType - { - public Func ValueFromJson { get; internal set; } - public Func ValueToJson { get; internal set; } - } - - internal class ValueTypes - { - private static ArgumentException BadTypeException() - { - return new ArgumentException("unexpected data type"); - } - - public static ValueType Bool = new ValueType - { - ValueFromJson = json => - { - if (json.Type != JTokenType.Boolean) - { - throw BadTypeException(); - } - return json.Value(); - }, - ValueToJson = value => new JValue(value) - }; - - public static ValueType Int = new ValueType - { - ValueFromJson = json => - { - if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) - { - throw BadTypeException(); - } - return json.Value(); - }, - ValueToJson = value => new JValue(value) - }; - - public static ValueType Float = new ValueType - { - ValueFromJson = json => - { - if (json.Type != JTokenType.Integer && json.Type != JTokenType.Float) - { - throw BadTypeException(); - } - return json.Value(); - }, - ValueToJson = value => new JValue(value) - }; - - public static ValueType String = new ValueType - { - ValueFromJson = json => - { - if (json == null || json.Type == JTokenType.Null) - { - return null; // strings are always nullable - } - if (json.Type != JTokenType.String) - { - throw BadTypeException(); - } - return json.Value(); - }, - ValueToJson = value => value == null ? null : new JValue(value) - }; - - public static ValueType Json = new ValueType - { - ValueFromJson = json => ImmutableJsonValue.FromSafeValue(json), - ValueToJson = value => value.AsJToken() - }; - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 462c1c97..b6d320f1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,8 +1,6 @@ using System; using System.Threading.Tasks; using LaunchDarkly.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index f568ec49..dea801af 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -1,5 +1,4 @@ using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -31,9 +30,9 @@ public void CacheFlagsShouldStoreFlagsInDeviceCache() { var flagCacheManager = ManagerWithCachedFlags(); var cachedDeviceFlags = deviceCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.ToObject()); + Assert.Equal(15, cachedDeviceFlags["int-flag"].value.AsInt); + Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.AsString); + Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.AsFloat); } [Fact] @@ -41,9 +40,9 @@ public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() { var flagCacheManager = ManagerWithCachedFlags(); var cachedDeviceFlags = inMemoryCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.ToObject()); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.ToString()); - Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.ToObject()); + Assert.Equal(15, cachedDeviceFlags["int-flag"].value.AsInt); + Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.AsString); + Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.AsFloat); } [Fact] @@ -59,11 +58,11 @@ public void CanUpdateFlagForUser() { var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(5); + updatedFeatureFlag.value = ImmutableJsonValue.Of(5); updatedFeatureFlag.version = 12; flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); - Assert.Equal(5, updatedFlagFromCache.value.ToObject()); + Assert.Equal(5, updatedFlagFromCache.value.AsInt); Assert.Equal(12, updatedFeatureFlag.version); } @@ -75,7 +74,7 @@ public void UpdateFlagSendsFlagChangeEvent() var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(7); + updatedFeatureFlag.value = ImmutableJsonValue.Of(7); flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); @@ -93,7 +92,7 @@ public void RemoveFlagSendsFlagChangeEvent() var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = JToken.FromObject(7); + updatedFeatureFlag.value = ImmutableJsonValue.Of(7); flagCacheManager.RemoveFlagForUser("int-flag", user); var e = listener.Await(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs index c56ff54d..d1b2484b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Threading; +using LaunchDarkly.Client; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -23,8 +24,8 @@ public void CanRegisterListeners() manager.FlagChanged += listener1.Handler; manager.FlagChanged += listener2.Handler; - manager.FlagWasUpdated(INT_FLAG, 7, 6); - manager.FlagWasUpdated(DOUBLE_FLAG, 10.5, 9.5); + manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(7), ImmutableJsonValue.Of(6)); + manager.FlagWasUpdated(DOUBLE_FLAG, ImmutableJsonValue.Of(10.5f), ImmutableJsonValue.Of(9.5f)); var event1a = listener1.Await(); var event1b = listener1.Await(); @@ -61,7 +62,7 @@ public void CanUnregisterListeners() manager.FlagChanged -= listener1.Handler; - manager.FlagWasUpdated(INT_FLAG, 7, 6); + manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(7), ImmutableJsonValue.Of(6)); var e = listener2.Await(); Assert.Equal(INT_FLAG, e.Key); @@ -81,7 +82,7 @@ public void ListenerGetsUpdatedWhenManagerFlagDeleted() var listener = new FlagChangedEventSink(); manager.FlagChanged += listener.Handler; - manager.FlagWasDeleted(INT_FLAG, 1); + manager.FlagWasDeleted(INT_FLAG, ImmutableJsonValue.Of(1)); var e = listener.Await(); Assert.Equal(INT_FLAG, e.Key); @@ -113,7 +114,7 @@ public void ListenerCallIsDeferred() lock (locker) { - manager.FlagWasUpdated(INT_FLAG, 2, 1); + manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(2), ImmutableJsonValue.Of(1)); Assert.False(Volatile.Read(ref called)); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index f7775556..75c29016 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -7,8 +7,6 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; @@ -516,22 +514,23 @@ private void VerifyNoFlagValues(ILdClient client, IDictionary fl } } - private JToken FlagJson(string key, string value) + private ImmutableJsonValue FlagJson(string key, string value) { - var o = new JObject(); - o.Add("key", key); - o.Add("value", value); - return o; + return ImmutableJsonValue.FromDictionary(new Dictionary + { + { "key", ImmutableJsonValue.Of(key) }, + { "value", ImmutableJsonValue.Of(value) } + }); } private string PollingData(IDictionary flags) { - var o = new JObject(); + var d = new Dictionary(); foreach (var e in flags) { - o.Add(e.Key, FlagJson(e.Key, e.Value)); + d.Add(e.Key, FlagJson(e.Key, e.Value)); } - return JsonConvert.SerializeObject(o); + return ImmutableJsonValue.FromDictionary(d).ToJsonString(); } private string StreamingData(IDictionary flags) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 0676b1a1..4704d638 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 94b3e301..ca5fb070 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -1,5 +1,5 @@ -using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using LaunchDarkly.Client; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -19,7 +19,7 @@ private static LdClient ClientWithFlagsJson(string flagsJson) [Fact] public void BoolVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(true)); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.True(client.BoolVariation("flag-key", false)); @@ -39,7 +39,7 @@ public void BoolVariationReturnsDefaultForUnknownFlag() public void BoolVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(true), 1, reason); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(true), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(true, 1, reason); @@ -50,7 +50,7 @@ public void BoolVariationDetailReturnsValue() [Fact] public void IntVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3)); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(3, client.IntVariation("flag-key", 0)); @@ -60,7 +60,7 @@ public void IntVariationReturnsValue() [Fact] public void IntVariationCoercesFloatValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3.0f)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3.0f)); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(3, client.IntVariation("flag-key", 0)); @@ -80,7 +80,7 @@ public void IntVariationReturnsDefaultForUnknownFlag() public void IntVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(3), 1, reason); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(3, 1, reason); @@ -91,7 +91,7 @@ public void IntVariationDetailReturnsValue() [Fact] public void FloatVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2.5f)); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); @@ -101,7 +101,7 @@ public void FloatVariationReturnsValue() [Fact] public void FloatVariationCoercesIntValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2)); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); @@ -121,7 +121,7 @@ public void FloatVariationReturnsDefaultForUnknownFlag() public void FloatVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue(2.5f), 1, reason); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2.5f), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(2.5f, 1, reason); @@ -132,7 +132,7 @@ public void FloatVariationDetailReturnsValue() [Fact] public void StringVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal("string value", client.StringVariation("flag-key", "")); @@ -152,7 +152,7 @@ public void StringVariationReturnsDefaultForUnknownFlag() public void StringVariationDetailReturnsValue() { var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value"), 1, reason); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value"), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail("string value", 1, reason); @@ -163,11 +163,11 @@ public void StringVariationDetailReturnsValue() [Fact] public void JsonVariationReturnsValue() { - var jsonValue = new JObject { { "thing", new JValue("stuff") } }; + var jsonValue = ImmutableJsonValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.Of(3)).AsJToken()); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.Of(3))); } } @@ -184,14 +184,14 @@ public void JsonVariationReturnsDefaultForUnknownFlag() [Fact] public void JsonVariationDetailReturnsValue() { - var jsonValue = new JObject { { "thing", new JValue("stuff") } }; + var jsonValue = ImmutableJsonValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { - var expected = new EvaluationDetail(jsonValue, 1, reason); + var expected = new EvaluationDetail(jsonValue, 1, reason); var result = client.JsonVariationDetail("flag-key", ImmutableJsonValue.Of(3)); - Assert.True(JToken.DeepEquals(expected.Value, result.Value.AsJToken())); + Assert.Equal(expected.Value, result.Value); Assert.Equal(expected.VariationIndex, result.VariationIndex); Assert.Equal(expected.Reason, result.Reason); } @@ -213,7 +213,7 @@ public void AllFlagsReturnsAllFlagValues() [Fact] public void DefaultValueReturnedIfValueTypeIsDifferent() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(3, client.IntVariation("flag-key", 3)); @@ -223,7 +223,7 @@ public void DefaultValueReturnedIfValueTypeIsDifferent() [Fact] public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", new JValue("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); @@ -234,7 +234,7 @@ public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() [Fact] public void DefaultValueReturnedIfFlagValueIsNull() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", null); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Null); using (var client = ClientWithFlagsJson(flagsJson)) { Assert.Equal(3, client.IntVariation("flag-key", 3)); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 0679b318..b285aee2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -1,6 +1,5 @@ using System; using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -35,15 +34,15 @@ public void TrackSendsCustomEvent() { using (LdClient client = MakeClient(user, "{}")) { - JToken data = new JValue("hi"); - client.Track("eventkey", ImmutableJsonValue.FromJToken(data)); + ImmutableJsonValue data = ImmutableJsonValue.Of("hi"); + client.Track("eventkey", data); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { CustomEvent ce = Assert.IsType(e); Assert.Equal("eventkey", ce.Key); Assert.Equal(user.Key, ce.User.Key); - Assert.Equal(data, ce.JsonData); + Assert.Equal(data, ce.Data); }); } } @@ -63,10 +62,10 @@ public void VariationSendsFeatureEventForValidFlag() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); + Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Equal(2000, fe.DebugEventsUntilDate); Assert.Null(fe.Reason); @@ -89,10 +88,10 @@ public void FeatureEventUsesFlagVersionIfProvided() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); + Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); Assert.Equal(1500, fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); }); } } @@ -111,10 +110,10 @@ public void VariationSendsFeatureEventForDefaultValue() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); + Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); } @@ -132,10 +131,10 @@ public void VariationSendsFeatureEventForUnknownFlag() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); + Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); } @@ -159,10 +158,10 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); + Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); } @@ -184,10 +183,10 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); + Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); @@ -213,10 +212,10 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("a", fe.Value); + Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); Assert.Equal(1000, fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Equal(2000, fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); @@ -238,10 +237,10 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); + Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); @@ -268,10 +267,10 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); - Assert.Equal("b", fe.Value); + Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); Assert.Null(fe.Version); - Assert.Equal("b", fe.Default); + Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index f83fcbd6..ebb76624 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using LaunchDarkly.Client; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -74,8 +73,8 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func>(storedJson); - Assert.Equal(new JValue(100), flags["flag"].value); + Assert.Equal(100, flags["flag"].value.AsInt); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 9f887898..41aab296 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -107,7 +107,7 @@ public void PUTstoresFeatureFlags() PUTMessageSentToProcessor(); flagsInCache = mockFlagCacheMgr.FlagsForUser(user); Assert.NotEmpty(flagsInCache); - int intFlagValue = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + int intFlagValue = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagValue); } @@ -117,7 +117,7 @@ public void PATCHupdatesFeatureFlag() // before PATCH, fill in flags MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); //PATCH to update 1 flag @@ -125,7 +125,7 @@ public void PATCHupdatesFeatureFlag() mockEventSource.RaiseMessageRcvd(eventArgs); //verify flag has changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(99, flagFromPatch); } @@ -135,7 +135,7 @@ public void PATCHdoesnotUpdateFlagIfVersionIsLower() // before PATCH, fill in flags MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); //PATCH to update 1 flag @@ -143,7 +143,7 @@ public void PATCHdoesnotUpdateFlagIfVersionIsLower() mockEventSource.RaiseMessageRcvd(eventArgs); //verify flag has not changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, flagFromPatch); } @@ -153,7 +153,7 @@ public void DELETEremovesFeatureFlag() // before DELETE, fill in flags, test it's there MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); // DELETE int-flag @@ -170,7 +170,7 @@ public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() // before DELETE, fill in flags, test it's there MobileStreamingProcessorStarted(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.ToObject(); + var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); // DELETE int-flag diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index e362d879..7f53dbbb 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -114,13 +114,13 @@ public static void ClearClient() }); } - internal static Dictionary MakeSingleFlagData(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) + internal static Dictionary MakeSingleFlagData(string flagKey, ImmutableJsonValue value, int? variation = null, EvaluationReason reason = null) { var flag = new FeatureFlag { value = value, variation = variation, reason = reason }; return new Dictionary { { flagKey, flag } }; } - internal static string JsonFlagsWithSingleFlag(string flagKey, JToken value, int? variation = null, EvaluationReason reason = null) + internal static string JsonFlagsWithSingleFlag(string flagKey, ImmutableJsonValue value, int? variation = null, EvaluationReason reason = null) { return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); } From a2c13e72486cb5a757e65f46bd9a506c639aca7f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 10:28:48 -0700 Subject: [PATCH 266/499] rename ImmutableJsonValue to LdValue, don't use ValueType --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 7 + src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 2 +- .../FlagCacheManager.cs | 8 +- .../FlagChangedEvent.cs | 22 +- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 14 +- .../LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 51 ++-- .../FlagCacheManagerTests.cs | 14 +- .../FlagChangedEventTests.cs | 10 +- .../LDClientEndToEndTests.cs | 12 +- .../LdClientEvaluationTests.cs | 136 ++++----- .../LdClientEventTests.cs | 18 +- .../LdClientTests.cs | 268 +++++++++--------- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 4 +- 14 files changed, 287 insertions(+), 281 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 55e08f27..d702b69c 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -380,6 +380,13 @@ private class StreamManagerAdapter : IStreamManagerConfiguration public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; public TimeSpan ReadTimeout => Config.ReadTimeout; public TimeSpan ReconnectTime => Config.ReconnectTime; + + public Exception TranslateHttpException(Exception e) + { + // TODO: this will be used as part of the fix for ch47489 - it will ensure that platform-specific + // exceptions like java.net.SocketTimeout will be logged as a standard .NET exception type + return e; + } } } } diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index de663578..e2bb8e80 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -6,7 +6,7 @@ namespace LaunchDarkly.Xamarin { internal sealed class FeatureFlag : IEquatable { - public ImmutableJsonValue value; + public LdValue value; public int version; public int? flagVersion; public bool trackEvents; diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index ae0b73d8..18a5eefe 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -44,7 +44,7 @@ public IDictionary FlagsForUser(User user) public void CacheFlagsFromService(IDictionary flags, User user) { - List> changes = null; + List> changes = null; readWriteLock.EnterWriteLock(); try { @@ -60,7 +60,7 @@ public void CacheFlagsFromService(IDictionary flags, User u { if (changes == null) { - changes = new List>(); + changes = new List>(); } changes.Add(Tuple.Create(flag.Key, flag.Value.value, originalFlag.value)); } @@ -92,7 +92,7 @@ public FeatureFlag FlagForUser(string flagKey, User user) public void RemoveFlagForUser(string flagKey, User user) { - ImmutableJsonValue oldValue; + LdValue oldValue = LdValue.Null; bool existed = false; readWriteLock.EnterWriteLock(); try @@ -120,7 +120,7 @@ public void RemoveFlagForUser(string flagKey, User user) public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) { bool changed = false; - ImmutableJsonValue oldValue; + LdValue oldValue = LdValue.Null; readWriteLock.EnterWriteLock(); try { diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index 0009f2f7..dcb78815 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -20,12 +20,12 @@ public sealed class FlagChangedEventArgs ///
/// /// - /// Since flag values can be of any JSON type, this property is an . You - /// can use properties and methods of such as + /// Since flag values can be of any JSON type, this property is an . You + /// can use properties and methods of such as /// to convert it to other types. /// /// - /// Flag evaluations always produce non-null values, but this property could still be + /// Flag evaluations always produce non-null values, but this property could still be /// if the flag was completely deleted or if it could not be evaluated due to an error of some kind. /// /// @@ -43,19 +43,19 @@ public sealed class FlagChangedEventArgs /// contain a . /// /// - public ImmutableJsonValue NewValue { get; private set; } + public LdValue NewValue { get; private set; } /// /// The last known value of the flag for the current user prior to the update. /// - public ImmutableJsonValue OldValue { get; private set; } + public LdValue OldValue { get; private set; } /// /// True if the flag was completely removed from the environment. /// public bool FlagWasDeleted { get; private set; } - internal FlagChangedEventArgs(string key, ImmutableJsonValue newValue, ImmutableJsonValue oldValue, bool flagWasDeleted) + internal FlagChangedEventArgs(string key, LdValue newValue, LdValue oldValue, bool flagWasDeleted) { Key = key; NewValue = newValue; @@ -67,8 +67,8 @@ internal FlagChangedEventArgs(string key, ImmutableJsonValue newValue, Immutable internal interface IFlagChangedEventManager { event EventHandler FlagChanged; - void FlagWasDeleted(string flagKey, ImmutableJsonValue oldValue); - void FlagWasUpdated(string flagKey, ImmutableJsonValue newValue, ImmutableJsonValue oldValue); + void FlagWasDeleted(string flagKey, LdValue oldValue); + void FlagWasUpdated(string flagKey, LdValue newValue, LdValue oldValue); } internal sealed class FlagChangedEventManager : IFlagChangedEventManager @@ -82,12 +82,12 @@ public bool IsHandlerRegistered(EventHandler handler) return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); } - public void FlagWasDeleted(string flagKey, ImmutableJsonValue oldValue) + public void FlagWasDeleted(string flagKey, LdValue oldValue) { - FireEvent(new FlagChangedEventArgs(flagKey, ImmutableJsonValue.Null, oldValue, true)); + FireEvent(new FlagChangedEventArgs(flagKey, LdValue.Null, oldValue, true)); } - public void FlagWasUpdated(string flagKey, ImmutableJsonValue newValue, ImmutableJsonValue oldValue) + public void FlagWasUpdated(string flagKey, LdValue newValue, LdValue oldValue) { FireEvent(new FlagChangedEventArgs(flagKey, newValue, oldValue, false)); } diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index 2d769a32..3bb54b99 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -200,7 +200,7 @@ public interface ILdClient : IDisposable /// the default value of the flag /// the variation for the selected user, or defaultValue if the flag is /// disabled in the LaunchDarkly control panel - ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue); + LdValue JsonVariation(string key, LdValue defaultValue); /// /// Returns the JSON value of a feature flag for a given flag key, in an object that also @@ -213,14 +213,14 @@ public interface ILdClient : IDisposable /// the unique feature key for the feature flag /// the default value of the flag /// an EvaluationDetail object - EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue); + EvaluationDetail JsonVariationDetail(string key, LdValue defaultValue); /// /// Tracks that the current user performed an event for the given event name, with additional JSON data. /// /// the name of the event /// a JSON value containing additional data associated with the event - void Track(string eventName, ImmutableJsonValue data); + void Track(string eventName, LdValue data); /// /// Tracks that the current user performed an event for the given event name. @@ -229,20 +229,20 @@ public interface ILdClient : IDisposable void Track(string eventName); /// - /// Returns a map from feature flag keys to feature flag values for the current user. + /// Returns a map from feature flag keys to feature flag values for the current user. /// /// /// /// If the result of a flag's value would have returned the default variation, the value in the map will contain - /// . If the client is offline or has not been initialized, an empty + /// . If the client is offline or has not been initialized, an empty /// map will be returned. /// /// /// This method will not send analytics events back to LaunchDarkly. /// /// - /// a map from feature flag keys to values for the current user - IDictionary AllFlags(); + /// a map from feature flag keys to values for the current user + IDictionary AllFlags(); /// /// This event is triggered when the client has received an updated value for a feature flag. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index a1476f72..367756c5 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index cde06b2e..cfac7ded 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -334,67 +334,67 @@ public async Task SetOfflineAsync(bool value) /// public bool BoolVariation(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, _eventFactoryDefault).Value; } /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { - return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, _eventFactoryWithReasons); } /// public string StringVariation(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, _eventFactoryDefault).Value; } /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, _eventFactoryWithReasons); } /// public float FloatVariation(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, _eventFactoryDefault).Value; } /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, _eventFactoryWithReasons); } /// public int IntVariation(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, _eventFactoryDefault).Value; } /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { - return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, _eventFactoryWithReasons); } /// - public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) + public LdValue JsonVariation(string key, LdValue defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; + return VariationInternal(key, defaultValue, LdValue.Convert.Json, false, _eventFactoryDefault).Value; } /// - public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) + public EvaluationDetail JsonVariationDetail(string key, LdValue defaultValue) { - return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); + return VariationInternal(key, defaultValue, LdValue.Convert.Json, false, _eventFactoryWithReasons); } - EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) + EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, LdValue.Converter converter, bool checkType, EventFactory eventFactory) { FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); - ImmutableJsonValue defaultJson = desiredType.ValueToJson(defaultValue); + T defaultValue = converter.ToType(defaultJson); EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); @@ -427,7 +427,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => featureFlagEvent = new FeatureFlagEvent(featureKey, flag); EvaluationDetail result; - ImmutableJsonValue valueJson; + LdValue valueJson; if (flag.value.IsNull) { valueJson = defaultJson; @@ -435,20 +435,19 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } else { - try - { - valueJson = flag.value; - var value = desiredType.ValueFromJson(flag.value); - result = new EvaluationDetail(value, flag.variation, flag.reason); - } - catch (Exception) + if (checkType && flag.value.Type != defaultJson.Type) { valueJson = defaultJson; result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); } + else + { + valueJson = flag.value; + result = new EvaluationDetail(converter.ToType(flag.value), flag.variation, flag.reason); + } } var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, - new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); + new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); SendEventIfOnline(featureEvent); return result; } @@ -462,14 +461,14 @@ private void SendEventIfOnline(Event e) } /// - public IDictionary AllFlags() + public IDictionary AllFlags() { return flagCacheManager.FlagsForUser(User) .ToDictionary(p => p.Key, p => p.Value.value); } /// - public void Track(string eventName, ImmutableJsonValue data) + public void Track(string eventName, LdValue data) { SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data)); } @@ -477,7 +476,7 @@ public void Track(string eventName, ImmutableJsonValue data) /// public void Track(string eventName) { - Track(eventName, ImmutableJsonValue.Null); + Track(eventName, LdValue.Null); } /// diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index dea801af..ed408491 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -58,7 +58,7 @@ public void CanUpdateFlagForUser() { var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = ImmutableJsonValue.Of(5); + updatedFeatureFlag.value = LdValue.Of(5); updatedFeatureFlag.version = 12; flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); @@ -74,7 +74,7 @@ public void UpdateFlagSendsFlagChangeEvent() var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = ImmutableJsonValue.Of(7); + updatedFeatureFlag.value = LdValue.Of(7); flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); @@ -86,15 +86,15 @@ public void UpdateFlagSendsFlagChangeEvent() [Fact] public void RemoveFlagSendsFlagChangeEvent() - { + { var listener = new FlagChangedEventSink(); listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = ImmutableJsonValue.Of(7); - flagCacheManager.RemoveFlagForUser("int-flag", user); - + updatedFeatureFlag.value = LdValue.Of(7); + flagCacheManager.RemoveFlagForUser("int-flag", user); + var e = listener.Await(); Assert.Equal("int-flag", e.Key); Assert.True(e.FlagWasDeleted); @@ -102,7 +102,7 @@ public void RemoveFlagSendsFlagChangeEvent() [Fact] public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() - { + { var listener = new FlagChangedEventSink(); listenerManager.FlagChanged += listener.Handler; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs index d1b2484b..d44595e6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -24,8 +24,8 @@ public void CanRegisterListeners() manager.FlagChanged += listener1.Handler; manager.FlagChanged += listener2.Handler; - manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(7), ImmutableJsonValue.Of(6)); - manager.FlagWasUpdated(DOUBLE_FLAG, ImmutableJsonValue.Of(10.5f), ImmutableJsonValue.Of(9.5f)); + manager.FlagWasUpdated(INT_FLAG, LdValue.Of(7), LdValue.Of(6)); + manager.FlagWasUpdated(DOUBLE_FLAG, LdValue.Of(10.5f), LdValue.Of(9.5f)); var event1a = listener1.Await(); var event1b = listener1.Await(); @@ -62,7 +62,7 @@ public void CanUnregisterListeners() manager.FlagChanged -= listener1.Handler; - manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(7), ImmutableJsonValue.Of(6)); + manager.FlagWasUpdated(INT_FLAG, LdValue.Of(7), LdValue.Of(6)); var e = listener2.Await(); Assert.Equal(INT_FLAG, e.Key); @@ -82,7 +82,7 @@ public void ListenerGetsUpdatedWhenManagerFlagDeleted() var listener = new FlagChangedEventSink(); manager.FlagChanged += listener.Handler; - manager.FlagWasDeleted(INT_FLAG, ImmutableJsonValue.Of(1)); + manager.FlagWasDeleted(INT_FLAG, LdValue.Of(1)); var e = listener.Await(); Assert.Equal(INT_FLAG, e.Key); @@ -114,7 +114,7 @@ public void ListenerCallIsDeferred() lock (locker) { - manager.FlagWasUpdated(INT_FLAG, ImmutableJsonValue.Of(2), ImmutableJsonValue.Of(1)); + manager.FlagWasUpdated(INT_FLAG, LdValue.Of(2), LdValue.Of(1)); Assert.False(Volatile.Read(ref called)); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 75c29016..35609e9d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -514,23 +514,23 @@ private void VerifyNoFlagValues(ILdClient client, IDictionary fl } } - private ImmutableJsonValue FlagJson(string key, string value) + private LdValue FlagJson(string key, string value) { - return ImmutableJsonValue.FromDictionary(new Dictionary + return LdValue.FromDictionary(new Dictionary { - { "key", ImmutableJsonValue.Of(key) }, - { "value", ImmutableJsonValue.Of(value) } + { "key", LdValue.Of(key) }, + { "value", LdValue.Of(value) } }); } private string PollingData(IDictionary flags) { - var d = new Dictionary(); + var d = new Dictionary(); foreach (var e in flags) { d.Add(e.Key, FlagJson(e.Key, e.Value)); } - return ImmutableJsonValue.FromDictionary(d).ToJsonString(); + return LdValue.FromDictionary(d).ToJsonString(); } private string StreamingData(IDictionary flags) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index ca5fb070..0c3602cf 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -8,7 +8,7 @@ public class LdClientEvaluationTests : BaseTest { static readonly string appKey = "some app key"; static readonly string nonexistentFlagKey = "some flag key"; - static readonly User user = User.WithKey("userkey"); + static readonly User user = User.WithKey("userkey"); private static LdClient ClientWithFlagsJson(string flagsJson) { @@ -19,92 +19,92 @@ private static LdClient ClientWithFlagsJson(string flagsJson) [Fact] public void BoolVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(true)); - using (var client = ClientWithFlagsJson(flagsJson)) - { - Assert.True(client.BoolVariation("flag-key", false)); - } + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(true)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.True(client.BoolVariation("flag-key", false)); + } } [Fact] public void BoolVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) - { - Assert.False(client.BoolVariation(nonexistentFlagKey)); + using (var client = ClientWithFlagsJson("{}")) + { + Assert.False(client.BoolVariation(nonexistentFlagKey)); } } [Fact] - public void BoolVariationDetailReturnsValue() + public void BoolVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(true), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) - { + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(true), 1, reason); + using (var client = ClientWithFlagsJson(flagsJson)) + { var expected = new EvaluationDetail(true, 1, reason); - Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); + Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); } } [Fact] public void IntVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3)); - using (var client = ClientWithFlagsJson(flagsJson)) - { - Assert.Equal(3, client.IntVariation("flag-key", 0)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 0)); } } [Fact] public void IntVariationCoercesFloatValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3.0f)); - using (var client = ClientWithFlagsJson(flagsJson)) - { - Assert.Equal(3, client.IntVariation("flag-key", 0)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3.0f)); + using (var client = ClientWithFlagsJson(flagsJson)) + { + Assert.Equal(3, client.IntVariation("flag-key", 0)); } } [Fact] public void IntVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = ClientWithFlagsJson("{}")) { - Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); + Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); } } [Fact] - public void IntVariationDetailReturnsValue() + public void IntVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(3), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3), 1, reason); + using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(3, 1, reason); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); } } [Fact] public void FloatVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2.5f)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2.5f)); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); + Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); } } [Fact] public void FloatVariationCoercesIntValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2)); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2)); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); + Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); } } @@ -113,29 +113,29 @@ public void FloatVariationReturnsDefaultForUnknownFlag() { using (var client = ClientWithFlagsJson("{}")) { - Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); + Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); } } [Fact] - public void FloatVariationDetailReturnsValue() + public void FloatVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of(2.5f), 1, reason); + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2.5f), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(2.5f, 1, reason); - Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); + Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); } } [Fact] public void StringVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal("string value", client.StringVariation("flag-key", "")); + Assert.Equal("string value", client.StringVariation("flag-key", "")); } } @@ -144,30 +144,30 @@ public void StringVariationReturnsDefaultForUnknownFlag() { using (var client = ClientWithFlagsJson("{}")) { - Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); + Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); } } [Fact] - public void StringVariationDetailReturnsValue() + public void StringVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value"), 1, reason); + var reason = EvaluationReason.Off.Instance; + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value"), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail("string value", 1, reason); - Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); + Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); } } [Fact] public void JsonVariationReturnsValue() { - var jsonValue = ImmutableJsonValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); + var jsonValue = LdValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(jsonValue, client.JsonVariation("flag-key", ImmutableJsonValue.Of(3))); + Assert.Equal(jsonValue, client.JsonVariation("flag-key", LdValue.Of(3))); } } @@ -176,24 +176,24 @@ public void JsonVariationReturnsDefaultForUnknownFlag() { using (var client = ClientWithFlagsJson("{}")) { - var defaultVal = ImmutableJsonValue.Of(3); - Assert.Equal(defaultVal, client.JsonVariation(nonexistentFlagKey, defaultVal)); + var defaultVal = LdValue.Of(3); + Assert.Equal(defaultVal, client.JsonVariation(nonexistentFlagKey, defaultVal)); } } [Fact] - public void JsonVariationDetailReturnsValue() + public void JsonVariationDetailReturnsValue() { - var jsonValue = ImmutableJsonValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); - var reason = EvaluationReason.Off.Instance; + var jsonValue = LdValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); + var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { - var expected = new EvaluationDetail(jsonValue, 1, reason); - var result = client.JsonVariationDetail("flag-key", ImmutableJsonValue.Of(3)); - Assert.Equal(expected.Value, result.Value); - Assert.Equal(expected.VariationIndex, result.VariationIndex); - Assert.Equal(expected.Reason, result.Reason); + var expected = new EvaluationDetail(jsonValue, 1, reason); + var result = client.JsonVariationDetail("flag-key", LdValue.Of(3)); + Assert.Equal(expected.Value, result.Value); + Assert.Equal(expected.VariationIndex, result.VariationIndex); + Assert.Equal(expected.Reason, result.Reason); } } @@ -203,41 +203,41 @@ public void AllFlagsReturnsAllFlagValues() var flagsJson = @"{""flag1"":{""value"":""a""},""flag2"":{""value"":""b""}}"; using (var client = ClientWithFlagsJson(flagsJson)) { - var result = client.AllFlags(); - Assert.Equal(2, result.Count); - Assert.Equal("a", result["flag1"].AsString); - Assert.Equal("b", result["flag2"].AsString); + var result = client.AllFlags(); + Assert.Equal(2, result.Count); + Assert.Equal("a", result["flag1"].AsString); + Assert.Equal("b", result["flag2"].AsString); } } [Fact] public void DefaultValueReturnedIfValueTypeIsDifferent() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(3, client.IntVariation("flag-key", 3)); + Assert.Equal(3, client.IntVariation("flag-key", 3)); } } [Fact] public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Of("string value")); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); + Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); } } [Fact] public void DefaultValueReturnedIfFlagValueIsNull() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", ImmutableJsonValue.Null); + string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Null); using (var client = ClientWithFlagsJson(flagsJson)) { - Assert.Equal(3, client.IntVariation("flag-key", 3)); + Assert.Equal(3, client.IntVariation("flag-key", 3)); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index b285aee2..751d05b8 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -7,7 +7,7 @@ namespace LaunchDarkly.Xamarin.Tests public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); - private MockEventProcessor eventProcessor = new MockEventProcessor(); + private MockEventProcessor eventProcessor = new MockEventProcessor(); public LdClient MakeClient(User user, string flagsJson) { @@ -34,7 +34,7 @@ public void TrackSendsCustomEvent() { using (LdClient client = MakeClient(user, "{}")) { - ImmutableJsonValue data = ImmutableJsonValue.Of("hi"); + LdValue data = LdValue.Of("hi"); client.Track("eventkey", data); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), @@ -138,11 +138,11 @@ public void VariationSendsFeatureEventForUnknownFlag() Assert.Null(fe.Reason); }); } - } - + } + [Fact] public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() - { + { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor); @@ -154,7 +154,7 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() Assert.Equal("b", result); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), - e => + e => { FeatureRequestEvent fe = Assert.IsType(e); Assert.Equal("flag", fe.Key); @@ -246,11 +246,11 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() Assert.Equal(expectedReason, fe.Reason); }); } - } - + } + [Fact] public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() - { + { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index ebb76624..45018a42 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -57,76 +57,76 @@ public void IdentifyUpdatesTheUser() Assert.True(success); Assert.Equal(client.User.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes } - } - - [Fact] - public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => client.IdentifyAsync(user)); - - [Fact] - public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(4)))); - - private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) - { - var userA = User.WithKey("a"); - var userB = User.WithKey("b"); - - var flagKey = "flag"; - var userAFlags = TestUtil.MakeSingleFlagData(flagKey, ImmutableJsonValue.Of("a-value")); - var userBFlags = TestUtil.MakeSingleFlagData(flagKey, ImmutableJsonValue.Of("b-value")); - - var startedIdentifyUserB = new SemaphoreSlim(0, 1); - var canFinishIdentifyUserB = new SemaphoreSlim(0, 1); - var finishedIdentifyUserB = new SemaphoreSlim(0, 1); - - Func updateProcessorFactory = (c, flags, user) => - new MockUpdateProcessorFromLambda(user, async () => - { - switch (user.Key) - { - case "a": - flags.CacheFlagsFromService(userAFlags, user); - break; - - case "b": - startedIdentifyUserB.Release(); - await canFinishIdentifyUserB.WaitAsync(); - flags.CacheFlagsFromService(userBFlags, user); - break; - } - }); - - var config = TestUtil.ConfigWithFlagsJson(userA, appKey, "{}") - .UpdateProcessorFactory(updateProcessorFactory) - .Build(); - - ClearCachedFlags(userA); - ClearCachedFlags(userB); - - using (var client = await LdClient.InitAsync(config, userA)) - { - Assert.True(client.Initialized); - Assert.Equal("a-value", client.StringVariation(flagKey, null)); - - var identifyUserBTask = Task.Run(async () => - { - await identifyTask(client, userB); - finishedIdentifyUserB.Release(); - }); - - await startedIdentifyUserB.WaitAsync(); - - Assert.False(client.Initialized); - Assert.Null(client.StringVariation(flagKey, null)); - - canFinishIdentifyUserB.Release(); - await finishedIdentifyUserB.WaitAsync(); - - Assert.True(client.Initialized); - Assert.Equal("b-value", client.StringVariation(flagKey, null)); - } - } + } + + [Fact] + public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => client.IdentifyAsync(user)); + + [Fact] + public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(4)))); + + private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) + { + var userA = User.WithKey("a"); + var userB = User.WithKey("b"); + + var flagKey = "flag"; + var userAFlags = TestUtil.MakeSingleFlagData(flagKey, LdValue.Of("a-value")); + var userBFlags = TestUtil.MakeSingleFlagData(flagKey, LdValue.Of("b-value")); + + var startedIdentifyUserB = new SemaphoreSlim(0, 1); + var canFinishIdentifyUserB = new SemaphoreSlim(0, 1); + var finishedIdentifyUserB = new SemaphoreSlim(0, 1); + + Func updateProcessorFactory = (c, flags, user) => + new MockUpdateProcessorFromLambda(user, async () => + { + switch (user.Key) + { + case "a": + flags.CacheFlagsFromService(userAFlags, user); + break; + + case "b": + startedIdentifyUserB.Release(); + await canFinishIdentifyUserB.WaitAsync(); + flags.CacheFlagsFromService(userBFlags, user); + break; + } + }); + + var config = TestUtil.ConfigWithFlagsJson(userA, appKey, "{}") + .UpdateProcessorFactory(updateProcessorFactory) + .Build(); + + ClearCachedFlags(userA); + ClearCachedFlags(userB); + + using (var client = await LdClient.InitAsync(config, userA)) + { + Assert.True(client.Initialized); + Assert.Equal("a-value", client.StringVariation(flagKey, null)); + + var identifyUserBTask = Task.Run(async () => + { + await identifyTask(client, userB); + finishedIdentifyUserB.Release(); + }); + + await startedIdentifyUserB.WaitAsync(); + + Assert.False(client.Initialized); + Assert.Null(client.StringVariation(flagKey, null)); + + canFinishIdentifyUserB.Release(); + await finishedIdentifyUserB.WaitAsync(); + + Assert.True(client.Initialized); + Assert.Equal("b-value", client.StringVariation(flagKey, null)); + } + } [Fact] public void IdentifyWithNullUserThrowsException() @@ -156,23 +156,23 @@ public void SharedClientIsTheOnlyClientAvailable() using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); - } + } TestUtil.ClearClient(); }); } [Fact] - public void CanCreateNewClientAfterDisposingOfSharedInstance() - { - TestUtil.WithClientLock(() => - { - TestUtil.ClearClient(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); - using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } - Assert.Null(LdClient.Instance); - // Dispose() is called automatically at end of "using" block - using (var client1 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } - }); + public void CanCreateNewClientAfterDisposingOfSharedInstance() + { + TestUtil.WithClientLock(() => + { + TestUtil.ClearClient(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + Assert.Null(LdClient.Instance); + // Dispose() is called automatically at end of "using" block + using (var client1 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + }); } [Fact] @@ -354,73 +354,73 @@ public void FlagsAreSavedToPersistentStorageByDefault() } [Fact] - public void EventProcessorIsOnlineByDefault() - { - var eventProcessor = new MockEventProcessor(); + public void EventProcessorIsOnlineByDefault() + { + var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .EventProcessor(eventProcessor) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - Assert.False(eventProcessor.Offline); - } + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.False(eventProcessor.Offline); + } } [Fact] - public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() - { - var connectivityStateManager = new MockConnectivityStateManager(true); - var eventProcessor = new MockEventProcessor(); + public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() + { + var connectivityStateManager = new MockConnectivityStateManager(true); + var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .ConnectivityStateManager(connectivityStateManager) .EventProcessor(eventProcessor) .Offline(true) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - Assert.True(eventProcessor.Offline); - - client.SetOffline(false, TimeSpan.FromSeconds(1)); - Assert.False(eventProcessor.Offline); - - client.SetOffline(true, TimeSpan.FromSeconds(1)); - Assert.True(eventProcessor.Offline); - - // If the network is unavailable... - connectivityStateManager.Connect(false); - - // ...then even if Offline is set to false, events stay off - client.SetOffline(false, TimeSpan.FromSeconds(1)); - Assert.True(eventProcessor.Offline); - } + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.True(eventProcessor.Offline); + + client.SetOffline(false, TimeSpan.FromSeconds(1)); + Assert.False(eventProcessor.Offline); + + client.SetOffline(true, TimeSpan.FromSeconds(1)); + Assert.True(eventProcessor.Offline); + + // If the network is unavailable... + connectivityStateManager.Connect(false); + + // ...then even if Offline is set to false, events stay off + client.SetOffline(false, TimeSpan.FromSeconds(1)); + Assert.True(eventProcessor.Offline); + } } [Fact] - public void EventProcessorIsOfflineWhenNetworkIsUnavailable() - { - var connectivityStateManager = new MockConnectivityStateManager(false); - var eventProcessor = new MockEventProcessor(); + public void EventProcessorIsOfflineWhenNetworkIsUnavailable() + { + var connectivityStateManager = new MockConnectivityStateManager(false); + var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .ConnectivityStateManager(connectivityStateManager) .EventProcessor(eventProcessor) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - Assert.True(eventProcessor.Offline); - - connectivityStateManager.Connect(true); - Assert.False(eventProcessor.Offline); - - connectivityStateManager.Connect(false); - Assert.True(eventProcessor.Offline); - - // If client is configured offline... - client.SetOffline(true, TimeSpan.FromSeconds(1)); - - // ...then even if the network comes back on, events stay off - connectivityStateManager.Connect(true); - Assert.True(eventProcessor.Offline); - } + .Build(); + using (var client = TestUtil.CreateClient(config, simpleUser)) + { + Assert.True(eventProcessor.Offline); + + connectivityStateManager.Connect(true); + Assert.False(eventProcessor.Offline); + + connectivityStateManager.Connect(false); + Assert.True(eventProcessor.Offline); + + // If client is configured offline... + client.SetOffline(true, TimeSpan.FromSeconds(1)); + + // ...then even if the network comes back on, events stay off + connectivityStateManager.Connect(true); + Assert.True(eventProcessor.Offline); + } } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 7f53dbbb..6d2e4d95 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -114,13 +114,13 @@ public static void ClearClient() }); } - internal static Dictionary MakeSingleFlagData(string flagKey, ImmutableJsonValue value, int? variation = null, EvaluationReason reason = null) + internal static Dictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) { var flag = new FeatureFlag { value = value, variation = variation, reason = reason }; return new Dictionary { { flagKey, flag } }; } - internal static string JsonFlagsWithSingleFlag(string flagKey, ImmutableJsonValue value, int? variation = null, EvaluationReason reason = null) + internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) { return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); } From 50534dfc0d83d59dbe558d7e2b98861894b5dea4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 10:31:31 -0700 Subject: [PATCH 267/499] fix dependency --- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 4704d638..3784f158 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From b78c5fcb4e686ffddeb3affc668b2636e3fa0ee8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 10:37:29 -0700 Subject: [PATCH 268/499] fix tests --- tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs | 4 ++-- .../LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 35609e9d..42e70ecf 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -516,7 +516,7 @@ private void VerifyNoFlagValues(ILdClient client, IDictionary fl private LdValue FlagJson(string key, string value) { - return LdValue.FromDictionary(new Dictionary + return LdValue.ObjectFrom(new Dictionary { { "key", LdValue.Of(key) }, { "value", LdValue.Of(value) } @@ -530,7 +530,7 @@ private string PollingData(IDictionary flags) { d.Add(e.Key, FlagJson(e.Key, e.Value)); } - return LdValue.FromDictionary(d).ToJsonString(); + return LdValue.ObjectFrom(d).ToJsonString(); } private string StreamingData(IDictionary flags) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 0c3602cf..7de5e099 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -163,7 +163,7 @@ public void StringVariationDetailReturnsValue() [Fact] public void JsonVariationReturnsValue() { - var jsonValue = LdValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); + var jsonValue = LdValue.Convert.String.ObjectFrom(new Dictionary { { "thing", "stuff" } }); string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); using (var client = ClientWithFlagsJson(flagsJson)) { @@ -184,7 +184,7 @@ public void JsonVariationReturnsDefaultForUnknownFlag() [Fact] public void JsonVariationDetailReturnsValue() { - var jsonValue = LdValue.FromDictionary(new Dictionary { { "thing", "stuff" } }); + var jsonValue = LdValue.Convert.String.ObjectFrom(new Dictionary { { "thing", "stuff" } }); var reason = EvaluationReason.Off.Instance; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) From 776f88a9ce1f823c2d10248f04a594a538423ca2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 11:37:24 -0700 Subject: [PATCH 269/499] fix type checking --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index cfac7ded..504b8a6e 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -435,7 +435,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } else { - if (checkType && flag.value.Type != defaultJson.Type) + if (checkType && !defaultJson.IsNull && flag.value.Type != defaultJson.Type) { valueJson = defaultJson; result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); From e949a760729ecabf0261ae73dd150bafeac6e79c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 13:00:12 -0700 Subject: [PATCH 270/499] disable annoying date parsing behavior --- src/LaunchDarkly.XamarinSdk/Extensions.cs | 5 +-- src/LaunchDarkly.XamarinSdk/JsonUtil.cs | 31 +++++++++++++++++++ .../MobilePollingProcessor.cs | 3 +- .../MobileStreamingProcessor.cs | 7 ++--- .../UserFlagDeviceCache.cs | 5 ++- .../UserFlagInMemoryCache.cs | 5 ++- .../LDClientEndToEndTests.cs | 25 +++++++++++++++ .../LdClientTests.cs | 3 +- .../MockComponents.cs | 3 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 5 ++- 10 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 src/LaunchDarkly.XamarinSdk/JsonUtil.cs diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs index 44562aba..8df77793 100644 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ b/src/LaunchDarkly.XamarinSdk/Extensions.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using LaunchDarkly.Client; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { @@ -15,8 +13,7 @@ public static string Base64Encode(this string plainText) public static string AsJson(this User user) { - var userAsString = JsonConvert.SerializeObject(user); - return userAsString; + return JsonUtil.EncodeJson(user); } } } diff --git a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs new file mode 100644 index 00000000..658026c0 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs @@ -0,0 +1,31 @@ +using System; +using Newtonsoft.Json; + +namespace LaunchDarkly.Xamarin +{ + internal class JsonUtil + { + private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.None + }; + + // Wrapper for JsonConvert.DeserializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. + internal static T DecodeJson(string json) + { + return JsonConvert.DeserializeObject(json, _jsonSettings); + } + + // Wrapper for JsonConvert.DeserializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. + internal static object DecodeJson(string json, Type type) + { + return JsonConvert.DeserializeObject(json, type, _jsonSettings); + } + + // Wrapper for JsonConvert.SerializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. + internal static string EncodeJson(object o) + { + return JsonConvert.SerializeObject(o, _jsonSettings); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index 0ed96ee3..dfb00d56 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -5,7 +5,6 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { @@ -84,7 +83,7 @@ private async Task UpdateTaskAsync() if (response.statusCode == 200) { var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonConvert.DeserializeObject>(flagsAsJsonString); + var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); _flagCacheManager.CacheFlagsFromService(flagsDictionary, _user); // We can't use bool in CompareExchange because it is not a reference type. diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 77a30a44..0a0d2296 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -4,7 +4,6 @@ using LaunchDarkly.Client; using LaunchDarkly.Common; using System.Net.Http; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Text; using Common.Logging; @@ -85,7 +84,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT { case Constants.PUT: { - _cacheManager.CacheFlagsFromService(JsonConvert.DeserializeObject>(messageData), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(messageData), _user); streamManager.Initialized = true; break; } @@ -93,7 +92,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT { try { - var parsed = JsonConvert.DeserializeObject(messageData); + var parsed = JsonUtil.DecodeJson(messageData); var flagkey = (string)parsed[Constants.KEY]; var featureFlag = parsed.ToObject(); PatchFeatureFlag(flagkey, featureFlag); @@ -108,7 +107,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT { try { - var dictionary = JsonConvert.DeserializeObject>(messageData); + var dictionary = JsonUtil.DecodeJson>(messageData); int version = dictionary[Constants.VERSION].ToObject(); string flagKey = dictionary[Constants.KEY].ToString(); DeleteFeatureFlag(flagKey, version); diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs index a5c83b62..fda453b3 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs @@ -3,7 +3,6 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { @@ -19,7 +18,7 @@ public UserFlagDeviceCache(IPersistentStorage persister) void IUserFlagCache.CacheFlagsForUser(IDictionary flags, User user) { - var jsonString = JsonConvert.SerializeObject(flags); + var jsonString = JsonUtil.EncodeJson(flags); try { persister.Save(Constants.FLAGS_KEY_PREFIX + user.Key, jsonString); @@ -39,7 +38,7 @@ IDictionary IUserFlagCache.RetrieveFlags(User user) var flagsAsJson = persister.GetValue(Constants.FLAGS_KEY_PREFIX + user.Key); if (flagsAsJson != null) { - return JsonConvert.DeserializeObject>(flagsAsJson); + return JsonUtil.DecodeJson>(flagsAsJson); } } catch (Exception ex) diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs index 2ebd2ce6..9c0ef187 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using LaunchDarkly.Client; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { @@ -13,7 +12,7 @@ internal sealed class UserFlagInMemoryCache : IUserFlagCache void IUserFlagCache.CacheFlagsForUser(IDictionary flags, User user) { - var jsonString = JsonConvert.SerializeObject(flags); + var jsonString = JsonUtil.EncodeJson(flags); JSONMap[user.Key] = jsonString; } @@ -22,7 +21,7 @@ IDictionary IUserFlagCache.RetrieveFlags(User user) string json; if (JSONMap.TryGetValue(user.Key, out json)) { - return JsonConvert.DeserializeObject>(json); + return JsonUtil.DecodeJson>(json); } return new Dictionary(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 42e70ecf..d3862501 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -435,6 +435,31 @@ await WithServerAsync(async server => }); } + [Theory] + [MemberData(nameof(PollingAndStreaming))] + public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) + { + // Newtonsoft.Json's default behavior is to transform ISO date/time strings into DateTime objects. We + // definitely don't want that. Verify that we're disabling that behavior when we parse flags. + const string dateLikeString1 = "1970-01-01T00:00:01.001Z"; + const string dateLikeString2 = "1970-01-01T00:00:01Z"; + WithServer(server => + { + var flagData = new Dictionary + { + { "flag1", dateLikeString1 }, + { "flag2", dateLikeString2 } + }; + SetupResponse(server, flagData, mode); + + var config = BaseConfig(server, mode); + using (var client = TestUtil.CreateClient(config, _user)) + { + VerifyFlagValues(client, flagData); + } + }); + } + private Configuration BaseConfig(FluentMockServer server, Func extraConfig = null) { var builderInternal = Configuration.BuilderInternal(_mobileKey) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 45018a42..c4671d83 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Client; -using Newtonsoft.Json; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -348,7 +347,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() using (var client = TestUtil.CreateClient(config, simpleUser)) { var storedJson = storage.GetValue(Constants.FLAGS_KEY_PREFIX + simpleUser.Key); - var flags = JsonConvert.DeserializeObject>(storedJson); + var flags = JsonUtil.DecodeJson>(storedJson); Assert.Equal(100, flags["flag"].value.AsInt); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 4e730528..0cb444de 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; -using Newtonsoft.Json; namespace LaunchDarkly.Xamarin.Tests { @@ -220,7 +219,7 @@ public Task Start() IsRunning = true; if (_cacheManager != null && _flagsJson != null) { - _cacheManager.CacheFlagsFromService(JsonConvert.DeserializeObject>(_flagsJson), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(_flagsJson), _user); } return Task.FromResult(true); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 6d2e4d95..af22aed2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Client; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -122,12 +121,12 @@ internal static Dictionary MakeSingleFlagData(string flagKe internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) { - return JsonConvert.SerializeObject(MakeSingleFlagData(flagKey, value, variation, reason)); + return JsonUtil.EncodeJson(MakeSingleFlagData(flagKey, value, variation, reason)); } internal static IDictionary DecodeFlagsJson(string flagsJson) { - return JsonConvert.DeserializeObject>(flagsJson); + return JsonUtil.DecodeJson>(flagsJson); } internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) From 4aee37fb4da8c04daff643f7851d1798166044be Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 13:53:01 -0700 Subject: [PATCH 271/499] make flag data immutable and stop re-parsing it all the time --- src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 36 +++++++---- .../FlagCacheManager.cs | 17 +++--- .../IFlagCacheManager.cs | 6 +- src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs | 10 +-- src/LaunchDarkly.XamarinSdk/LdClient.cs | 3 +- .../MobilePollingProcessor.cs | 4 +- .../MobileStreamingProcessor.cs | 3 +- .../UserFlagDeviceCache.cs | 18 +++--- .../UserFlagInMemoryCache.cs | 29 ++++----- .../FeatureFlagBuilder.cs | 61 +++++++++++++++++++ .../FeatureFlagTests.cs | 7 +-- .../FlagCacheManagerTests.cs | 10 +-- .../MockComponents.cs | 15 ++--- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 11 ++-- 14 files changed, 148 insertions(+), 82 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index e2bb8e80..9bb36f0d 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -1,19 +1,34 @@ using System; using LaunchDarkly.Client; using LaunchDarkly.Common; +using Newtonsoft.Json; namespace LaunchDarkly.Xamarin { internal sealed class FeatureFlag : IEquatable { - public LdValue value; - public int version; - public int? flagVersion; - public bool trackEvents; - public bool trackReason; - public int? variation; - public long? debugEventsUntilDate; - public EvaluationReason reason; + public readonly LdValue value; + public readonly int version; + public readonly int? flagVersion; + public readonly bool trackEvents; + public readonly bool trackReason; + public readonly int? variation; + public readonly long? debugEventsUntilDate; + public readonly EvaluationReason reason; + + [JsonConstructor] + public FeatureFlag(LdValue value, int version, int? flagVersion, bool trackEvents, bool trackReason, + int? variation, long? debugEventsUntilDate, EvaluationReason reason) + { + this.value = value; + this.version = version; + this.flagVersion = flagVersion; + this.trackEvents = trackEvents; + this.trackReason = trackReason; + this.variation = variation; + this.debugEventsUntilDate = debugEventsUntilDate; + this.reason = reason; + } public bool Equals(FeatureFlag otherFlag) { @@ -37,11 +52,6 @@ internal struct FeatureFlagEvent : IFlagEventProperties private readonly FeatureFlag _featureFlag; private readonly string _key; - public static FeatureFlagEvent Default(string key) - { - return new FeatureFlagEvent(key, new FeatureFlag()); - } - public FeatureFlagEvent(string key, FeatureFlag featureFlag) { _featureFlag = featureFlag; diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index 18a5eefe..0f045a49 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using LaunchDarkly.Client; @@ -29,7 +30,7 @@ public FlagCacheManager(IUserFlagCache inMemoryCache, } } - public IDictionary FlagsForUser(User user) + public IImmutableDictionary FlagsForUser(User user) { readWriteLock.EnterReadLock(); try @@ -42,7 +43,7 @@ public IDictionary FlagsForUser(User user) } } - public void CacheFlagsFromService(IDictionary flags, User user) + public void CacheFlagsFromService(IImmutableDictionary flags, User user) { List> changes = null; readWriteLock.EnterWriteLock(); @@ -102,9 +103,9 @@ public void RemoveFlagForUser(string flagKey, User user) { existed = true; oldValue = flag.value; - flagsForUser.Remove(flagKey); - deviceCache.CacheFlagsForUser(flagsForUser, user); - inMemoryCache.CacheFlagsForUser(flagsForUser, user); + var updatedFlags = flagsForUser.Remove(flagKey); // IImmutableDictionary.Remove() returns a new dictionary + deviceCache.CacheFlagsForUser(updatedFlags, user); + inMemoryCache.CacheFlagsForUser(updatedFlags, user); } } finally @@ -133,9 +134,9 @@ public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user changed = true; } } - flagsForUser[flagKey] = featureFlag; - deviceCache.CacheFlagsForUser(flagsForUser, user); - inMemoryCache.CacheFlagsForUser(flagsForUser, user); + var updatedFlags = flagsForUser.SetItem(flagKey, featureFlag); // IImmutableDictionary.SetItem() returns a new dictionary + deviceCache.CacheFlagsForUser(updatedFlags, user); + inMemoryCache.CacheFlagsForUser(updatedFlags, user); } finally { diff --git a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs index 46b2fc82..87cb4377 100644 --- a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; +using System.Collections.Immutable; using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { internal interface IFlagCacheManager { - void CacheFlagsFromService(IDictionary flags, User user); + void CacheFlagsFromService(IImmutableDictionary flags, User user); FeatureFlag FlagForUser(string flagKey, User user); void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user); void RemoveFlagForUser(string flagKey, User user); - IDictionary FlagsForUser(User user); + IImmutableDictionary FlagsForUser(User user); } } \ No newline at end of file diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs index 2cecb540..2676c121 100644 --- a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs +++ b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; +using System.Collections.Immutable; using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { internal interface IUserFlagCache { - void CacheFlagsForUser(IDictionary flags, User user); - IDictionary RetrieveFlags(User user); + void CacheFlagsForUser(IImmutableDictionary flags, User user); + IImmutableDictionary RetrieveFlags(User user); } internal sealed class NullUserFlagCache : IUserFlagCache { - public void CacheFlagsForUser(IDictionary flags, User user) { } - public IDictionary RetrieveFlags(User user) => null; + public void CacheFlagsForUser(IImmutableDictionary flags, User user) { } + public IImmutableDictionary RetrieveFlags(User user) => ImmutableDictionary.Create(); } } diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 504b8a6e..08cb1e9d 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -393,7 +393,6 @@ public EvaluationDetail JsonVariationDetail(string key, LdValue default EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, LdValue.Converter converter, bool checkType, EventFactory eventFactory) { - FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); T defaultValue = converter.ToType(defaultJson); EvaluationDetail errorResult(EvaluationErrorKind kind) => @@ -425,7 +424,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => } } - featureFlagEvent = new FeatureFlagEvent(featureKey, flag); + FeatureFlagEvent featureFlagEvent = new FeatureFlagEvent(featureKey, flag); EvaluationDetail result; LdValue valueJson; if (flag.value.IsNull) diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index dfb00d56..c269b7ff 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Common.Logging; @@ -83,7 +83,7 @@ private async Task UpdateTaskAsync() if (response.statusCode == 200) { var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); + var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); _flagCacheManager.CacheFlagsFromService(flagsDictionary, _user); // We can't use bool in CompareExchange because it is not a reference type. diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 0a0d2296..d016355b 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using LaunchDarkly.Client; using LaunchDarkly.Common; @@ -84,7 +85,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT { case Constants.PUT: { - _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(messageData), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(messageData), _user); streamManager.Initialized = true; break; } diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs index fda453b3..8ddfe823 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using System; +using System.Collections.Immutable; using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Common; @@ -16,7 +16,7 @@ public UserFlagDeviceCache(IPersistentStorage persister) this.persister = persister; } - void IUserFlagCache.CacheFlagsForUser(IDictionary flags, User user) + void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) { var jsonString = JsonUtil.EncodeJson(flags); try @@ -25,30 +25,28 @@ void IUserFlagCache.CacheFlagsForUser(IDictionary flags, Us } catch (System.Exception ex) { - Log.ErrorFormat("Couldn't set preferences on mobile device: '{0}'", - ex, + Log.ErrorFormat("Couldn't set preferences on mobile device: {0}", Util.ExceptionMessage(ex)); } } - IDictionary IUserFlagCache.RetrieveFlags(User user) + IImmutableDictionary IUserFlagCache.RetrieveFlags(User user) { try { var flagsAsJson = persister.GetValue(Constants.FLAGS_KEY_PREFIX + user.Key); if (flagsAsJson != null) { - return JsonUtil.DecodeJson>(flagsAsJson); + return JsonUtil.DecodeJson>(flagsAsJson); // surprisingly, this works } } catch (Exception ex) { - Log.ErrorFormat("Couldn't get preferences on mobile device: '{0}'", - ex, + Log.ErrorFormat("Couldn't get preferences on mobile device: {0}", Util.ExceptionMessage(ex)); } - return new Dictionary(); + return ImmutableDictionary.Create(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs index 9c0ef187..d6e7f373 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs @@ -1,30 +1,31 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Immutable; using LaunchDarkly.Client; namespace LaunchDarkly.Xamarin { internal sealed class UserFlagInMemoryCache : IUserFlagCache { - // A map of the key (user.Key) and their featureFlags - readonly ConcurrentDictionary JSONMap = - new ConcurrentDictionary(); + // For each known user key, store a map of their flags. This is a write-through cache - updates will always + // go to UserFlagDeviceCache as well. The inner dictionaries are immutable; updates are done by updating + // the whole thing (that is safe because updates are only ever done from the streaming/polling thread, and + // since updates should be relatively infrequent, it's not very expensive). - void IUserFlagCache.CacheFlagsForUser(IDictionary flags, User user) + private readonly ConcurrentDictionary> _allData = + new ConcurrentDictionary>(); + + void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) { - var jsonString = JsonUtil.EncodeJson(flags); - JSONMap[user.Key] = jsonString; + _allData[user.Key] = flags; } - IDictionary IUserFlagCache.RetrieveFlags(User user) + IImmutableDictionary IUserFlagCache.RetrieveFlags(User user) { - string json; - if (JSONMap.TryGetValue(user.Key, out json)) + if (_allData.TryGetValue(user.Key, out var flags)) { - return JsonUtil.DecodeJson>(json); + return flags; } - - return new Dictionary(); + return ImmutableDictionary.Create(); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs new file mode 100644 index 00000000..c446d7b4 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs @@ -0,0 +1,61 @@ +using System; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin.Tests +{ + internal class FeatureFlagBuilder + { + private LdValue _value = LdValue.Null; + private int _version; + private int? _variation; + private int? _flagVersion; +#pragma warning disable 0649 + // Currently trackEvents, trackReason, and debugEventsUntilDate are never set in the tests. That's because those properties + // are only used by DefaultEventProcessor (in LaunchDarkly.CommonSdk), which has its own tests against an abstraction of the + // same properties. + private bool _trackEvents; + private bool _trackReason; + private long? _debugEventsUntilDate; +#pragma warning disable 0649 + private EvaluationReason _reason; + + public FeatureFlagBuilder() + { + } + + public FeatureFlag Build() + { + return new FeatureFlag(_value, _version, _flagVersion, _trackEvents, _trackReason, _variation, _debugEventsUntilDate, _reason); + } + + public FeatureFlagBuilder Value(LdValue value) + { + _value = value; + return this; + } + + public FeatureFlagBuilder FlagVersion(int? flagVersion) + { + _flagVersion = flagVersion; + return this; + } + + public FeatureFlagBuilder Version(int version) + { + _version = version; + return this; + } + + public FeatureFlagBuilder Variation(int? variation) + { + _variation = variation; + return this; + } + + public FeatureFlagBuilder Reason(EvaluationReason reason) + { + _reason = reason; + return this; + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs index 92a5b569..4f6cd85c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs @@ -7,9 +7,7 @@ public class FeatureFlagEventTests : BaseTest [Fact] public void ReturnsFlagVersionAsVersion() { - var flag = new FeatureFlag(); - flag.flagVersion = 123; - flag.version = 456; + var flag = new FeatureFlagBuilder().FlagVersion(123).Version(456).Build(); var flagEvent = new FeatureFlagEvent("my-flag", flag); Assert.Equal(123, flagEvent.EventVersion); } @@ -17,8 +15,7 @@ public void ReturnsFlagVersionAsVersion() [Fact] public void FallsBackToVersionAsVersion() { - var flag = new FeatureFlag(); - flag.version = 456; + var flag = new FeatureFlagBuilder().Version(456).Build(); var flagEvent = new FeatureFlagEvent("my-flag", flag); Assert.Equal(456, flagEvent.EventVersion); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index ed408491..0c010166 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -57,9 +57,7 @@ public void CanRemoveFlagForUser() public void CanUpdateFlagForUser() { var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = LdValue.Of(5); - updatedFeatureFlag.version = 12; + var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(5)).Version(12).Build(); flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); Assert.Equal(5, updatedFlagFromCache.value.AsInt); @@ -73,8 +71,7 @@ public void UpdateFlagSendsFlagChangeEvent() listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = LdValue.Of(7); + var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(7)).Build(); flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); @@ -91,8 +88,7 @@ public void RemoveFlagSendsFlagChangeEvent() listenerManager.FlagChanged += listener.Handler; var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlag(); - updatedFeatureFlag.value = LdValue.Of(7); + var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(7)).Build(); flagCacheManager.RemoveFlagForUser("int-flag", user); var e = listener.Await(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 0cb444de..6348ef2a 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; @@ -111,7 +112,7 @@ public MockFlagCacheManager(IUserFlagCache flagCache) _flagCache = flagCache; } - public void CacheFlagsFromService(IDictionary flags, User user) + public void CacheFlagsFromService(IImmutableDictionary flags, User user) { _flagCache.CacheFlagsForUser(flags, user); } @@ -128,7 +129,7 @@ public FeatureFlag FlagForUser(string flagKey, User user) return null; } - public IDictionary FlagsForUser(User user) + public IImmutableDictionary FlagsForUser(User user) { return _flagCache.RetrieveFlags(user); } @@ -136,17 +137,17 @@ public IDictionary FlagsForUser(User user) public void RemoveFlagForUser(string flagKey, User user) { var flagsForUser = FlagsForUser(user); - flagsForUser.Remove(flagKey); + var updatedDict = flagsForUser.Remove(flagKey); - CacheFlagsFromService(flagsForUser, user); + CacheFlagsFromService(updatedDict, user); } public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) { var flagsForUser = FlagsForUser(user); - flagsForUser[flagKey] = featureFlag; + var updatedDict = flagsForUser.SetItem(flagKey, featureFlag); - CacheFlagsFromService(flagsForUser, user); + CacheFlagsFromService(updatedDict, user); } } @@ -219,7 +220,7 @@ public Task Start() IsRunning = true; if (_cacheManager != null && _flagsJson != null) { - _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(_flagsJson), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(_flagsJson), _user); } return Task.FromResult(true); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index af22aed2..525811fe 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Client; @@ -113,10 +114,10 @@ public static void ClearClient() }); } - internal static Dictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) + internal static IImmutableDictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) { - var flag = new FeatureFlag { value = value, variation = variation, reason = reason }; - return new Dictionary { { flagKey, flag } }; + var flag = new FeatureFlagBuilder().Value(value).Variation(variation).Reason(reason).Build(); + return ImmutableDictionary.Create().SetItem(flagKey, flag); } internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) @@ -124,9 +125,9 @@ internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, in return JsonUtil.EncodeJson(MakeSingleFlagData(flagKey, value, variation, reason)); } - internal static IDictionary DecodeFlagsJson(string flagsJson) + internal static IImmutableDictionary DecodeFlagsJson(string flagsJson) { - return JsonUtil.DecodeJson>(flagsJson); + return JsonUtil.DecodeJson>(flagsJson); } internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) From 3c193955c9ec185150e189d15fad284ea2a2596a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 13:58:30 -0700 Subject: [PATCH 272/499] fix source file reference --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 3 +++ .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 5d6e2429..c6b2a02c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -67,6 +67,9 @@ SharedTestCode\ConfigurationTest.cs + + SharedTestCode\FeatureFlagBuilder.cs + SharedTestCode\FeatureFlagRequestorTests.cs diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 3f8703c3..c5131d56 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -151,6 +151,9 @@ SharedTestCode\ConfigurationTest.cs + + SharedTestCode\FeatureFlagBuilder.cs + SharedTestCode\FeatureFlagRequestorTests.cs From 661588cd26eca8c087890e531e536b82b1b44771 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 14:58:22 -0700 Subject: [PATCH 273/499] configure Android HTTP client to do timeouts correctly. --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 4 ++- .../ConfigurationBuilder.cs | 21 ++++++++----- .../MobilePollingProcessor.cs | 2 +- .../PlatformSpecific/Http.android.cs | 20 ++++++++++-- .../PlatformSpecific/Http.ios.cs | 8 +++-- .../PlatformSpecific/Http.netstandard.cs | 10 ++++-- .../PlatformSpecific/Http.shared.cs | 31 ++++++++++++------- .../ConfigurationTest.cs | 2 +- 8 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index d702b69c..fd642cc8 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -319,7 +319,9 @@ internal Configuration(ConfigurationBuilder builder) _eventFlushInterval = builder._eventFlushInterval; _eventCapacity = builder._eventCapacity; _eventsUri = builder._eventsUri; - _httpMessageHandler = builder._httpMessageHandler; + _httpMessageHandler = object.ReferenceEquals(builder._httpMessageHandler, ConfigurationBuilder.DefaultHttpMessageHandlerInstance) ? + PlatformSpecific.Http.CreateHttpMessageHandler(builder._connectionTimeout, builder._readTimeout) : + builder._httpMessageHandler; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; _mobileKey = builder._mobileKey; diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 1d40d946..8cac14a2 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -33,7 +33,7 @@ public interface IConfigurationBuilder Configuration Build(); /// - /// Sets whether or not user attributes (other than the key) should be private (not sent to + /// Sets whether or not user attributeps (other than the key) should be private (not sent to /// the LaunchDarkly server). /// /// @@ -135,11 +135,13 @@ public interface IConfigurationBuilder /// Sets the object to be used for sending HTTP requests, if a specific implementation is desired. /// /// - /// This is exposed mainly for testing purposes; you should not normally need to change it. - /// By default, on mobile platforms it will use the appropriate native HTTP handler for the - /// current platform, if any (e.g. Xamarin.Android.Net.AndroidClientHandler). If this is - /// , the SDK will call the default constructor without - /// specifying a handler, which may or may not result in using a native HTTP handler. + /// This is exposed mainly for testing purposes; you should not normally need to change it. The default + /// value is an , but if you do not change this value, + /// on mobile platforms it will be replaced by the appropriate native HTTP handler for the current + /// current platform, if any (e.g. Xamarin.Android.Net.AndroidClientHandler). If you set it + /// explicitly to , the SDK will call the default + /// constructor without specifying a handler, which may or may not result in using a native HTTP handler + /// (depending on your application configuration). /// /// the to use /// the same builder @@ -276,6 +278,11 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder { private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationBuilder)); + // This exists so that we can distinguish between leaving the HttpMessageHandler property unchanged + // and explicitly setting it to null. If the property value is the exact same instance as this, we + // will replace it with a platform-specific implementation. + internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); + internal bool _allAttributesPrivate = false; internal TimeSpan _backgroundPollingInterval; internal Uri _baseUri = Configuration.DefaultUri; @@ -285,7 +292,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal int _eventCapacity = Configuration.DefaultEventCapacity; internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; internal Uri _eventsUri = Configuration.DefaultEventsUri; - internal HttpMessageHandler _httpMessageHandler = PlatformSpecific.Http.GetHttpMessageHandler(); // see Http.shared.cs + internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; internal bool _inlineUsersInEvents = false; internal bool _isStreamingEnabled = true; internal string _mobileKey; diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index c269b7ff..773927a1 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -106,7 +106,7 @@ private async Task UpdateTaskAsync() } catch (Exception ex) { - Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(ex)); + Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(PlatformSpecific.Http.TranslateHttpException(ex))); } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs index adc54f51..0977c33c 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs @@ -1,11 +1,25 @@ -using System.Net.Http; +using System; +using System.Net.Http; using Xamarin.Android.Net; namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler() => - new AndroidClientHandler(); + private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => + new AndroidClientHandler() + { + ConnectTimeout = connectTimeout, + ReadTimeout = readTimeout + }; + + private static Exception PlatformTranslateHttpException(Exception e) + { + if (e is Java.Net.SocketTimeoutException) + { + return new TimeoutException(); + } + return e; + } } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs index 0076e11d..98294fca 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs @@ -1,10 +1,14 @@ -using System.Net.Http; +using System; +using System.Net.Http; namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler() => + private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => new NSUrlSessionHandler(); + + // NSUrlSessionHandler doesn't appear to throw platform-specific exceptions that we care about + private static Exception PlatformTranslateHttpException(Exception e) => e; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs index 4465fd65..baa85df1 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs @@ -1,9 +1,15 @@ -using System.Net.Http; +using System; +using System.Net.Http; namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler() => null; + private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => + null; + // Setting the HttpClient's message handler to null means it will use the default .NET implementation, + // which already correctly implements timeouts based on the client configuration. + + private static Exception PlatformTranslateHttpException(Exception e) => e; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs index ad70e696..2865a911 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs @@ -1,25 +1,34 @@ -using System.Net.Http; +using System; +using System.Net.Http; namespace LaunchDarkly.Xamarin.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler _httpMessageHandler = PlatformCreateHttpMessageHandler(); - /// /// If our default configuration should use a specific /// implementation, returns that implementation. /// /// - /// The handler is not stateful, so it can be a shared instance. If we don't need to use a - /// specific implementation, this returns null. This is just the default for - /// , so the application can still override it. If it is - /// null and the application lets it remain null, then Xamarin will make its - /// own decision based on logic we don't have access to; in practice that seems to result - /// in picking the same handler that our platform-specific logic would specify, but that - /// may be dependent on project configuration, so we decided to explicitly set a default. + /// The timeouts are passed in because the Xamarin Android implementation does not actually + /// look at the configured timeouts from HttpClient. /// /// an HTTP handler implementation or null - public static HttpMessageHandler GetHttpMessageHandler() => _httpMessageHandler; + public static HttpMessageHandler CreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => + PlatformCreateHttpMessageHandler(connectTimeout, readTimeout); + + /// + /// Converts any platform-specific exceptions that might be thrown by the platform-specific + /// HTTP handler to their .NET equivalents. + /// + /// + /// We don't really care about specific network exception classes in our code, but in any case + /// where we might expose the exception to application code, we want to normalize it to use only + /// .NET classes. + /// + /// an exception + /// the same exception or a more .NET-appropriate one + public static Exception TranslateHttpException(Exception e) => + e is HttpRequestException ? e : PlatformTranslateHttpException(e); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index 53e0438a..5aeb1be5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -73,7 +73,7 @@ public void CanSetHttpMessageHandler() .HttpMessageHandler(handler) .Build(); - Assert.Equal(handler, config.HttpMessageHandler); + Assert.Same(handler, config.HttpMessageHandler); } } } \ No newline at end of file From d73bdd6a9b469f48ecf068593c2af6d2c365d457 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 15:07:48 -0700 Subject: [PATCH 274/499] typo --- src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 8cac14a2..a97f004b 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -33,7 +33,7 @@ public interface IConfigurationBuilder Configuration Build(); /// - /// Sets whether or not user attributeps (other than the key) should be private (not sent to + /// Sets whether or not user attributes (other than the key) should be private (not sent to /// the LaunchDarkly server). /// /// From 42ab06ab1ec5a856bac42fe8fe334c6795cbed7a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 15:42:09 -0700 Subject: [PATCH 275/499] version 1.0.0-beta24 --- CHANGELOG.md | 17 +++++++++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e54ed8c..d556a6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.0.0-beta24] - 2019-09-13 +### Added: +- `ImmutableJsonValue` now has methods for converting to or from a list or dictionary, rather than using the `Newtonsoft.Json` types `JArray` and `JObject`. +- HTML documentation for all public types, methods, and properties is now available [online](https://launchdarkly.github.io/xamarin-client-sdk). + +### Changed: +- The SDK no longer has a dependency on [`Xam.Plugin.DeviceInfo`](https://github.com/jamesmontemagno/DeviceInfoPlugin). +- When accessing a floating-point flag value with `IntVariation`, or converting a floating-point `ImmutableJsonValue` to an `int`, it will now truncate (round toward zero) rather than rounding to the nearest integer. This is consistent with normal C# behavior and with most other LaunchDarkly SDKs. + +### Removed: +- All public methods and properties now use `ImmutableJsonValue` instead of `JToken`. + +### Fixed: +- Fixed a bug that caused a string flag value that is in an ISO date/time format, like "1970-01-01T00:00:01.001Z", to be treated as an incompatible type by `StringVariation` (because `Newtonsoft.Json` would parse it as a `DateTime` by default). +- On Android, an HTTP connection timeout could leave a connection attempt still happening in the background-- even though the timeout would still happen normally as far as the SDK was concerned-- until the default timeout for the Android HTTP handler elapsed, which is 24 hours. Now it will properly stop the connection attempt after a timeout. +- Fixed an implementation problem that caused excessive overhead for flag evaluations due to unnecessary JSON parsing. + ## [1.0.0-beta23] - 2019-08-30 ### Added: - XML documentation comments are now included in the package, so they should be visible in Visual Studio for all LaunchDarkly types and methods. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 367756c5..c422697a 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -5,7 +5,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; $(LD_TARGET_FRAMEWORKS) - 0.0.3 + 1.0.0-beta24 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From bc35414aecb73b2d391979b3b1b8a19aaa185b0f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 16:40:07 -0700 Subject: [PATCH 276/499] changelog update --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d556a6e2..e9acea34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org). ## [1.0.0-beta24] - 2019-09-13 ### Added: -- `ImmutableJsonValue` now has methods for converting to or from a list or dictionary, rather than using the `Newtonsoft.Json` types `JArray` and `JObject`. +- `LdValue` now has methods for converting to or from a list or dictionary, rather than using the `Newtonsoft.Json` types `JArray` and `JObject`. - HTML documentation for all public types, methods, and properties is now available [online](https://launchdarkly.github.io/xamarin-client-sdk). ### Changed: +- The new name of `ImmutableJsonValue` is `LdValue`. - The SDK no longer has a dependency on [`Xam.Plugin.DeviceInfo`](https://github.com/jamesmontemagno/DeviceInfoPlugin). -- When accessing a floating-point flag value with `IntVariation`, or converting a floating-point `ImmutableJsonValue` to an `int`, it will now truncate (round toward zero) rather than rounding to the nearest integer. This is consistent with normal C# behavior and with most other LaunchDarkly SDKs. +- When accessing a floating-point flag value with `IntVariation`, or converting a floating-point `LdValue` to an `int`, it will now truncate (round toward zero) rather than rounding to the nearest integer. This is consistent with normal C# behavior and with most other LaunchDarkly SDKs. ### Removed: -- All public methods and properties now use `ImmutableJsonValue` instead of `JToken`. +- All public methods and properties now use `LdValue` instead of `JToken`. ### Fixed: - Fixed a bug that caused a string flag value that is in an ISO date/time format, like "1970-01-01T00:00:01.001Z", to be treated as an incompatible type by `StringVariation` (because `Newtonsoft.Json` would parse it as a `DateTime` by default). From 5ff131f3fd58467a8bcc77927ae9e559f6481dcc Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 13 Sep 2019 18:08:00 -0700 Subject: [PATCH 277/499] version 1.0.0 --- CHANGELOG.md | 140 +----------------- .../LaunchDarkly.XamarinSdk.csproj | 4 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 3 files changed, 6 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9acea34..3e04cf59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,142 +3,8 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). -## [1.0.0-beta24] - 2019-09-13 -### Added: -- `LdValue` now has methods for converting to or from a list or dictionary, rather than using the `Newtonsoft.Json` types `JArray` and `JObject`. -- HTML documentation for all public types, methods, and properties is now available [online](https://launchdarkly.github.io/xamarin-client-sdk). - -### Changed: -- The new name of `ImmutableJsonValue` is `LdValue`. -- The SDK no longer has a dependency on [`Xam.Plugin.DeviceInfo`](https://github.com/jamesmontemagno/DeviceInfoPlugin). -- When accessing a floating-point flag value with `IntVariation`, or converting a floating-point `LdValue` to an `int`, it will now truncate (round toward zero) rather than rounding to the nearest integer. This is consistent with normal C# behavior and with most other LaunchDarkly SDKs. - -### Removed: -- All public methods and properties now use `LdValue` instead of `JToken`. +## [1.0.0] - 2019-09-13 -### Fixed: -- Fixed a bug that caused a string flag value that is in an ISO date/time format, like "1970-01-01T00:00:01.001Z", to be treated as an incompatible type by `StringVariation` (because `Newtonsoft.Json` would parse it as a `DateTime` by default). -- On Android, an HTTP connection timeout could leave a connection attempt still happening in the background-- even though the timeout would still happen normally as far as the SDK was concerned-- until the default timeout for the Android HTTP handler elapsed, which is 24 hours. Now it will properly stop the connection attempt after a timeout. -- Fixed an implementation problem that caused excessive overhead for flag evaluations due to unnecessary JSON parsing. +First GA release. -## [1.0.0-beta23] - 2019-08-30 -### Added: -- XML documentation comments are now included in the package, so they should be visible in Visual Studio for all LaunchDarkly types and methods. - -### Changed: -- The `Online` property of `LdClient` was not useful because it could reflect either a deliberate change to whether the client is allowed to go online (that is, it would be false if you had set `Offline` to true in the configuration), _or_ a change in network availability. It has been removed and replaced with `Offline`, which is only used for explicitly forcing the client to be offline. This is a read-only property; to set it, use `SetOffline` or `SetOfflineAsync`. -- The synchronous `Identify` method now requires a timeout parameter, and returns false if it times out. -- `LdClient.Initialized` is now a property, not a method. -- `LdClient.Version` is now static, since it describes the entire package rather than a client instance. -- In `Configuration` and `IConfigurationBuilder`, `HttpClientTimeout` is now `ConnectionTimeout`. -- There is now more debug-level logging for stream connection state changes. - -### Fixed: -- Network availability changes are now detected in both Android and iOS. The SDK should not attempt to connect to LaunchDarkly if the OS has told it that the network is unavailable. -- Background polling was never enabled, even if `Configuration.EnableBackgroundUpdating` was true. -- When changing from offline to online by setting `client.Online = true`, or calling `await client.SetOnlineAsync(true)` (the equivalent now would be `client.Offline = false`, etc.), the SDK was returning too soon before it had acquired flags from LaunchDarkly. The known issues in 1.0.0-beta22 have been fixed. -- If the SDK was online and then was explicitly set to be offline, or if network connectivity was lost, the SDK was still attempting to send analytics events. It will no longer do so. -- If the SDK was originally set to be offline and then was put online, the SDK was _not_ sending analytics events. Now it will. - -### Removed: -- `ConfigurationBuilder.UseReport`. Due to [an issue](https://github.com/xamarin/xamarin-android/issues/3544) with the Android implementation of HTTP, the HTTP REPORT method is not currently usable in the Xamarin SDK. -- `IConnectionManager` interface. The SDK now always uses a platform-appropriate implementation of this logic. - -## [1.0.0-beta22] - 2019-08-12 -### Changed: -- By default, on Android and iOS the SDK now uses Xamarin's platform-specific implementations of `HttpMessageHandler` that are based on native APIs, rather than the basic `System.Net.Http.HttpClientHandler`. This improves performance and stability on mobile platforms. -- The behavior of the `Initialized()` method has changed to be more consistent with the other SDKs. Rather than only being true if there is currently an active connection to LaunchDarkly, it is now also true if you configured it in offline mode so that it will not attempt to connect to LaunchDarkly; in other words, it is only false if it has tried to connect and not yet succeeded. -- Also, instead of always returning default values whenever `Initialized()` is false, the SDK now returns a default value only if it does not already have a cached value for the flag. It will always use cached values if it has obtained any (for the current user). - -### Known issues: -- Changing the `Online` property does not wait for the connection state to be updated; that is, if you were offline and then set `Online = true`, flag values may not be immediately available. It is equivalent to calling `SetOnlineAsync(true)` but _not_ waiting for the result. -- Starting with the configuration `Offline(true)` and then going online does not work. -- On mobile platforms, the SDK may not detect when network availability has changed. This means that if the network is unavailable, it may waste some CPU time on trying and failing to reconnect to LaunchDarkly. - -## [1.0.0-beta21] - 2019-08-06 -### Added: -- `Configuration.Builder` provides a fluent builder pattern for constructing `Configuration` objects. This is now the only method for building a configuration if you want to set properties other than the SDK key. -- `ImmutableJsonValue.Null` (equivalent to `ImmutableJsonValue.Of(null)`). -- `LdClient.PlatformType`. -- Verbose debug logging for stream connections. - -### Changed: -- `Configuration` objects are now immutable. -- In `Configuration`, `EventQueueCapacity` and `EventQueueFrency` have been renamed to `EventCapacity` and `EventFlushInterval`, for consistency with other LaunchDarkly SDKs. -- `ImmutableJsonValue.FromJToken()` was renamed to `ImmutableJsonValue.Of()`. -- In `FlagChangedEventArgs`, `NewValue` and `OldValue` now have the type `ImmutableJsonValue` instead of `JToken`. -- `ILdMobileClient` is now named `ILdClient`. - -### Fixed: -- Fixed a bug where setting a user's custom attribute to a null value could cause an exception during JSON serialization of event data. - -### Removed: -- `ConfigurationExtensions` (use `Configuration.Builder`). -- `Configuration.SamplingInterval`. -- `UserExtensions` (use `User.Builder`). -- `User` constructors (use `User.WithKey` or `User.Builder`). -- `User` property setters. -- `IBaseConfiguration` and `ICommonLdClient` interfaces. - -## [1.0.0-beta20] - 2019-08-06 -Incomplete release, replaced by beta21. - -## [1.0.0-beta19] - 2019-07-31 -### Added: -- `User.Builder` provides a fluent builder pattern for constructing `User` objects. This is now the only method for building a user if you want to set properties other than `Key`. -- The `ImmutableJsonValue` type provides a wrapper for the Newtonsoft.Json types that prevents accidentally modifying JSON object properties or array values that are shared by other objects. -- `LdClient.PlatformType` allows you to verify that you have loaded the correct target platform version of the SDK. - -### Changed: -- `User` objects are now immutable. -- In `User`, `IpAddress` has been renamed to `IPAddress` (standard .NET capitalization for two-letter acronyms). -- Custom attribute values in `User.Custom` are now returned as `ImmutableJsonValue` rather than `JToken`. -- JSON flag variations returned by `JsonVariation`, `JsonVariationDetail`, and `AllFlags`, are now `ImmutableJsonValue` rather than `JToken`. -- Setting additional data in a custom event with `Track` now uses `ImmutableJsonValue` rather than `JToken`. -- The mechanism for specifying a flag change listener has been changed to use the standard .NET event pattern. Instead of `client.RegisterFeatureFlagListener("flag-key", handler)` (where `handler` is an instance of some class that implements a particular interface), it is now `client.FlagChanged += handler` (where `handler` is an event handler method or function that will receive the flag key as part of `FlagChangedEventArgs`). -- Flag change listeners are now invoked asynchronously. This is to avoid the possibility of a deadlock if, for instance, application code triggers an action (such as Identify) that causes flags to be updated, which causes a flag change listener to be called, which then tries to access some resource that is being held by that same application code (e.g. trying to do an action on the main thread). On Android and iOS, these listeners are now guaranteed to be executed on the main thread, but only after any other current action on the main thread has completed. On .NET Standard, they are executed asynchronously with `Task.Run()`. - -### Fixed: -- Previously, if you created a client with `LdClient.Init` and then called `Dispose()` on the client, it would fail because the SDK would think a singleton instance already exists. Now, disposing of the singleton returns the SDK to a state where you can create a client again. -- Fixed a `NullReferenceException` that could sometimes be thrown when transitioning from background to foreground in Android. - -### Removed: -- `User` constructors (use `User.WithKey` or `User.Builder`). -- `User.IpAddress` (use `IPAddress`). -- `User` property setters, and the `UserExtension` methods for modifying properties (`AndName()`, etc.). - -## [1.0.0-beta18] - 2019-07-02 -### Added: -- New `Configuration` property `PersistFlagValues` (default: true) allows you to turn off the SDK's normal behavior of storing flag values locally so they can be used offline. -- Flag values are now stored locally in .NET Standard by default, on the filesystem, using the .NET `IsolatedStorageFile` mechanism. -- Added CI unit test suites that exercise most of the SDK functionality in .NET Standard, Android, and iOS. The tests do not currently cover background-mode detection and network connectivity detection on mobile platforms. - -### Changed: -- `Configuration.WithUpdateProcessor` has been replaced with `Configuration.WithUpdateProcessorFactory`. These methods are for testing purposes and will not normally be used. - -### Fixed: -- In .NET Standard, if you specify a user with `Key == null` and `Anonymous == true`, the SDK now generates a GUID for a user key and caches it in local storage for future reuse. This is consistent with the other client-side SDKs. Previously, it caused an exception. - -### Removed: -- Several low-level component interfaces such as `IDeviceInfo` which had been exposed for testing are now internal. - -# Note on future releases - -The LaunchDarkly SDK repositories are being renamed for consistency. This repository is now `xamarin-client-sdk` rather than `xamarin-client`. - -The package name will also change. In the 1.0.0-beta16 release, the published package was `LaunchDarkly.Xamarin`; in all future releases, it will be `LaunchDarkly.XamarinSdk`. - -## [1.0.0-beta17] - 2019-05-15 -### Changed: -- The NuGet package name for this SDK is now `LaunchDarkly.XamarinSdk`. There are no other changes. Substituting `LaunchDarkly.Xamarin` 1.0.0-beta16 with `LaunchDarkly.XamarinSdk` 1.0.0-beta17 should not affect functionality. - -## [1.0.0-beta16] - 2019-04-05 -### Added: -- In Android and iOS, when an app is in the background, the SDK should turn off the streaming connection and instead poll for flag updates at an interval determined by `Configuration.BackgroundPollingInterval` (default: 60 minutes). -- The SDK now supports [evaluation reasons](https://docs.launchdarkly.com/docs/evaluation-reasons). See `Configuration.WithEvaluationReasons` and `ILdMobileClient.BoolVariationDetail`. -- The SDK now sends custom attributes called `os` and `device` as part of the user data, indicating the user's platform and OS version. This is the same as what the native Android and iOS SDKs do, except that "iOS" or "Android" is also prepended to the `os` property. -### Changed: -- This is the first version that is built specifically for iOS and Android platforms. There is also still a .NET Standard 1.0 build in the same package. -- The SDK no longer uses Xamarin Essentials. -### Fixed: -- Under some circumstances, a `CancellationTokenSource` object could be leaked. \ No newline at end of file +For release notes on earlier beta versions, see the [beta changelog](https://github.com/launchdarkly/xamarin-client-sdk/blob/1.0.0-beta24/CHANGELOG.md). diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index c422697a..0130b600 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -5,7 +5,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; $(LD_TARGET_FRAMEWORKS) - 1.0.0-beta24 + 1.0.0 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk @@ -33,7 +33,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 3784f158..d27a33bc 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From 78ddabb2970344bd37bd82485a03e901eaa69f47 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 9 Oct 2019 10:51:09 -0700 Subject: [PATCH 278/499] use CommonSdk 4.1.0 --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 0130b600..5042619f 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + From feff6583527c23bb14f10c6a49826610b1505e57 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 9 Oct 2019 11:03:58 -0700 Subject: [PATCH 279/499] fix dependency --- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index d27a33bc..caea4f3a 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From 35914593a875654b9ed7830bc05d71447961c0c4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 9 Oct 2019 11:13:38 -0700 Subject: [PATCH 280/499] update test data because User.Anonymous was fixed to default to null instead of false --- .../FeatureFlagRequestorTests.cs | 4 ++-- .../MobileStreamingProcessorTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index b6d320f1..53933530 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -11,8 +11,8 @@ public class FeatureFlagRequestorTests : BaseTest private const string _mobileKey = "FAKE_KEY"; private static readonly User _user = User.WithKey("foo"); - private const string _userJson = "{\"key\":\"foo\",\"anonymous\":false}"; - private const string _encodedUser = "eyJrZXkiOiJmb28iLCJhbm9ueW1vdXMiOmZhbHNlLCJjdXN0b20iOnt9fQ=="; + private const string _userJson = "{\"key\":\"foo\"}"; + private const string _encodedUser = "eyJrZXkiOiJmb28iLCJBbm9ueW1vdXMiOmZhbHNlLCJjdXN0b20iOnt9fQ=="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 41aab296..f4f2a97d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -18,7 +18,7 @@ public class MobileStreamingProcessorTests : BaseTest "}"; private readonly User user = User.WithKey("me"); - private const string encodedUser = "eyJrZXkiOiJtZSIsImFub255bW91cyI6ZmFsc2UsImN1c3RvbSI6e319"; + private const string encodedUser = "eyJrZXkiOiJtZSIsIkFub255bW91cyI6ZmFsc2UsImN1c3RvbSI6e319"; private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; From 5a51370d7a26ee15028052971f2dff3beec49ea4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 9 Oct 2019 15:11:25 -0700 Subject: [PATCH 281/499] linefeeds --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 1338 +++++++++++------------ 1 file changed, 669 insertions(+), 669 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 6a662fe0..33e2afa9 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -1,669 +1,669 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; -using LaunchDarkly.Xamarin.PlatformSpecific; -using Newtonsoft.Json.Linq; - -namespace LaunchDarkly.Xamarin -{ - /// - /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate - /// a single LdClient for the lifetime of their application. - /// - public sealed class LdClient : ILdClient - { - private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); - - static volatile LdClient _instance; - static volatile User _user; - - bool initialized; - - static readonly object _createInstanceLock = new object(); - static readonly EventFactory _eventFactoryDefault = EventFactory.Default; - static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; - - readonly Configuration _config; - readonly SemaphoreSlim _connectionLock; - - readonly IDeviceInfo deviceInfo; - readonly IConnectionManager connectionManager; - readonly IEventProcessor eventProcessor; - readonly IFlagCacheManager flagCacheManager; - internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing - readonly IPersistentStorage persister; - - // These LdClient fields are not readonly because they change according to online status - internal volatile IMobileUpdateProcessor updateProcessor; - volatile bool _disableStreaming; - volatile bool _online; - - /// - /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot - /// create a new client instance unless you first call on this one. - /// - /// Use the designated static methods or - /// to set this LdClient instance. - /// - /// The LdClient instance. - public static LdClient Instance => _instance; - - /// - /// The Configuration instance used to setup the LdClient. - /// - /// The Configuration instance. - public Configuration Config => _config; - - /// - /// The User for the LdClient operations. - /// - /// The User. - public User User => _user; - - /// - public bool Online - { - get => _online; - set - { - var doNotAwaitResult = SetOnlineAsync(value); - } - } - - /// - /// Indicates which platform the SDK is built for. - /// - /// - /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, - /// but rather which variant of the SDK is currently in use. - /// - /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android - /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done - /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard - /// variant. - /// - /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific - /// behavior such as detecting when an application has gone into the background, detecting network connectivity, - /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find - /// that these platform-specific behaviors are not working correctly, you may want to check this property to - /// make sure you are not for some reason running the .NET Standard SDK on a phone. - /// - public static PlatformType PlatformType - { - get - { - return UserMetadata.PlatformType; - } - } - - // private constructor prevents initialization of this class - // without using WithConfigAnduser(config, user) - LdClient() { } - - LdClient(Configuration configuration, User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - - _connectionLock = new SemaphoreSlim(1, 1); - - if (configuration.Offline) - { - initialized = true; - } - - persister = Factory.CreatePersistentStorage(configuration); - deviceInfo = Factory.CreateDeviceInfo(configuration); - flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); - - _user = DecorateUser(user); - - flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); - connectionManager = Factory.CreateConnectionManager(configuration); - updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null, false); - eventProcessor = Factory.CreateEventProcessor(configuration); - - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); - - SetupConnectionManager(); - BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; - } - - /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. - public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) - { - var config = Configuration.Default(mobileKey); - - return Init(config, user, maxWaitTime); - } - - /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching feature flags. This constructor should be used if you do not want to wait - /// for the client to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more specific - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The mobile key given to you by LaunchDarkly. - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static async Task InitAsync(string mobileKey, User user) - { - var config = Configuration.Default(mobileKey); - - return await InitAsync(config, user); - } - - /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching Feature Flags. - /// - /// This constructor will wait and block on the current thread until initialization and the - /// first response from the LaunchDarkly service is returned, up to the specified timeout. - /// If you would rather this happen in an async fashion you can use . - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The client configuration object - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. - public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) - { - if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); - } - - var c = CreateInstance(config, user); - - if (c.Online) - { - if (!c.StartUpdateProcessor(maxWaitTime)) - { - Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", - maxWaitTime.TotalMilliseconds); - } - } - - return c; - } - - /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching Feature Flags. This constructor should be used if you do not want to wait - /// for the IUpdateProcessor instance to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. - /// - /// The singleton LdClient instance. - /// The client configuration object - /// The user needed for client operations. Must not be null. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static Task InitAsync(Configuration config, User user) - { - var c = CreateInstance(config, user); - - if (c.Online) - { - Task t = c.StartUpdateProcessorAsync(); - return t.ContinueWith((result) => c); - } - else - { - return Task.FromResult(c); - } - } - - static LdClient CreateInstance(Configuration configuration, User user) - { - lock (_createInstanceLock) - { - if (_instance != null) - { - throw new Exception("LdClient instance already exists."); - } - - var c = new LdClient(configuration, user); - _instance = c; - Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); - return c; - } - } - - void SetupConnectionManager() - { - if (connectionManager is MobileConnectionManager mobileConnectionManager) - { - mobileConnectionManager.ConnectionChanged += MobileConnectionManager_ConnectionChanged; - Log.InfoFormat("The mobile client connection changed online to {0}", - connectionManager.IsConnected); - } - _online = connectionManager.IsConnected; - } - - public async Task SetOnlineAsync(bool value) - { - await _connectionLock.WaitAsync(); - _online = value; - try - { - if (_online) - { - await RestartUpdateProcessorAsync(Config.PollingInterval); - } - else - { - ClearUpdateProcessor(); - } - } - finally - { - _connectionLock.Release(); - } - - return; - } - - void MobileConnectionManager_ConnectionChanged(bool isOnline) - { - Log.DebugFormat("Setting online to {0} due to a connectivity change event", isOnline); - Online = isOnline; - } - - /// - public bool BoolVariation(string key, bool defaultValue = false) - { - return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; - } - - /// - public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) - { - return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); - } - - /// - public string StringVariation(string key, string defaultValue) - { - return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; - } - - /// - public EvaluationDetail StringVariationDetail(string key, string defaultValue) - { - return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); - } - - /// - public float FloatVariation(string key, float defaultValue = 0) - { - return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; - } - - /// - public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) - { - return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); - } - - /// - public int IntVariation(string key, int defaultValue = 0) - { - return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; - } - - /// - public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) - { - return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); - } - - /// - public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) - { - return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; - } - - /// - public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) - { - return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); - } - - EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) - { - FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); - JToken defaultJson = desiredType.ValueToJson(defaultValue); - - EvaluationDetail errorResult(EvaluationErrorKind kind) => - new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); - - var flag = flagCacheManager.FlagForUser(featureKey, User); - if (flag == null) - { - if (!Initialized()) - { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); - eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.CLIENT_NOT_READY)); - return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); - } - else - { - Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); - eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.FLAG_NOT_FOUND)); - return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); - } - } - else - { - if (!Initialized()) - { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); - } - } - - featureFlagEvent = new FeatureFlagEvent(featureKey, flag); - EvaluationDetail result; - JToken valueJson; - if (flag.value == null || flag.value.Type == JTokenType.Null) - { - valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); - } - else - { - try - { - valueJson = flag.value; - var value = desiredType.ValueFromJson(flag.value); - result = new EvaluationDetail(value, flag.variation, flag.reason); - } - catch (Exception) - { - valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); - } - } - var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, - new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); - eventProcessor.SendEvent(featureEvent); - return result; - } - - /// - public IDictionary AllFlags() - { - return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); - } - - /// - public void Track(string eventName) - { - Track(eventName, ImmutableJsonValue.Null); - } - - /// - public void Track(string eventName, ImmutableJsonValue data) - { - eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.InnerValue)); - } - - /// - public void Track(string eventName, ImmutableJsonValue data, double metricValue) - { - eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.InnerValue, metricValue)); - } - - /// - public bool Initialized() - { - return initialized; - } - - /// - public bool IsOffline() - { - return !_online; - } - - /// - public void Flush() - { - eventProcessor.Flush(); - } - - /// - public void Identify(User user) - { - AsyncUtils.WaitSafely(() => IdentifyAsync(user)); - } - - /// - public async Task IdentifyAsync(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - User newUser = DecorateUser(user); - - await _connectionLock.WaitAsync(); - try - { - _user = newUser; - initialized = false; - await RestartUpdateProcessorAsync(Config.PollingInterval); - } - finally - { - _connectionLock.Release(); - } - - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(newUser)); - } - - bool StartUpdateProcessor(TimeSpan maxWaitTime) - { - if (Online) - { - var successfulConnection = AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); - initialized = successfulConnection; - return successfulConnection; - } - else - { - return true; - } - } - - Task StartUpdateProcessorAsync() - { - if (Online) - { - var successfulConnection = updateProcessor.Start(); - if (successfulConnection.IsCompleted) - { - initialized = true; - } - return successfulConnection; - } - else - { - return Task.FromResult(true); - } - } - - async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) - { - ClearAndSetUpdateProcessor(pollingInterval); - await StartUpdateProcessorAsync(); - } - - void ClearAndSetUpdateProcessor(TimeSpan pollingInterval) - { - ClearUpdateProcessor(); - updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval, _disableStreaming); - } - - void ClearUpdateProcessor() - { - if (updateProcessor != null) - { - updateProcessor.Dispose(); - updateProcessor = new NullUpdateProcessor(); - } - } - - User DecorateUser(User user) - { - IUserBuilder buildUser = null; - if (UserMetadata.DeviceName != null) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Custom("device", UserMetadata.DeviceName); - } - if (UserMetadata.OSName != null) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Custom("os", UserMetadata.OSName); - } - // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Key(deviceInfo.UniqueDeviceId()).Anonymous(true); - } - return buildUser is null ? user : buildUser.Build(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - void Dispose(bool disposing) - { - if (disposing) - { - Log.InfoFormat("Shutting down the LaunchDarkly client"); - - BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; - updateProcessor.Dispose(); - eventProcessor.Dispose(); - - // Reset the static Instance to null *if* it was referring to this instance - DetachInstance(); - } - } - - internal void DetachInstance() // exposed for testing - { - Interlocked.CompareExchange(ref _instance, null, this); - } - - /// - public Version Version - { - get - { - return MobileClientEnvironment.Instance.Version; - } - } - - /// - public event EventHandler FlagChanged - { - add - { - flagChangedEventManager.FlagChanged += value; - } - remove - { - flagChangedEventManager.FlagChanged -= value; - } - } - - internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) - { - AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); - } - - internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) - { - Log.DebugFormat("Background mode is changing to {0}", args.IsInBackground); - if (args.IsInBackground) - { - ClearUpdateProcessor(); - _disableStreaming = true; - if (Config.EnableBackgroundUpdating) - { - Log.Debug("Background updating is enabled, starting polling processor"); - await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); - } - else - { - Log.Debug("Background updating is disabled"); - } - } - else - { - _disableStreaming = false; - await RestartUpdateProcessorAsync(Config.PollingInterval); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common.Logging; +using LaunchDarkly.Client; +using LaunchDarkly.Common; +using LaunchDarkly.Xamarin.PlatformSpecific; +using Newtonsoft.Json.Linq; + +namespace LaunchDarkly.Xamarin +{ + /// + /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate + /// a single LdClient for the lifetime of their application. + /// + public sealed class LdClient : ILdClient + { + private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); + + static volatile LdClient _instance; + static volatile User _user; + + bool initialized; + + static readonly object _createInstanceLock = new object(); + static readonly EventFactory _eventFactoryDefault = EventFactory.Default; + static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; + + readonly Configuration _config; + readonly SemaphoreSlim _connectionLock; + + readonly IDeviceInfo deviceInfo; + readonly IConnectionManager connectionManager; + readonly IEventProcessor eventProcessor; + readonly IFlagCacheManager flagCacheManager; + internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing + readonly IPersistentStorage persister; + + // These LdClient fields are not readonly because they change according to online status + internal volatile IMobileUpdateProcessor updateProcessor; + volatile bool _disableStreaming; + volatile bool _online; + + /// + /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot + /// create a new client instance unless you first call on this one. + /// + /// Use the designated static methods or + /// to set this LdClient instance. + /// + /// The LdClient instance. + public static LdClient Instance => _instance; + + /// + /// The Configuration instance used to setup the LdClient. + /// + /// The Configuration instance. + public Configuration Config => _config; + + /// + /// The User for the LdClient operations. + /// + /// The User. + public User User => _user; + + /// + public bool Online + { + get => _online; + set + { + var doNotAwaitResult = SetOnlineAsync(value); + } + } + + /// + /// Indicates which platform the SDK is built for. + /// + /// + /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, + /// but rather which variant of the SDK is currently in use. + /// + /// The LaunchDarkly.XamarinSdk package contains assemblies for multiple target platforms. In an Android + /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done + /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard + /// variant. + /// + /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific + /// behavior such as detecting when an application has gone into the background, detecting network connectivity, + /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find + /// that these platform-specific behaviors are not working correctly, you may want to check this property to + /// make sure you are not for some reason running the .NET Standard SDK on a phone. + /// + public static PlatformType PlatformType + { + get + { + return UserMetadata.PlatformType; + } + } + + // private constructor prevents initialization of this class + // without using WithConfigAnduser(config, user) + LdClient() { } + + LdClient(Configuration configuration, User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + _connectionLock = new SemaphoreSlim(1, 1); + + if (configuration.Offline) + { + initialized = true; + } + + persister = Factory.CreatePersistentStorage(configuration); + deviceInfo = Factory.CreateDeviceInfo(configuration); + flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); + + _user = DecorateUser(user); + + flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); + connectionManager = Factory.CreateConnectionManager(configuration); + updateProcessor = Factory.CreateUpdateProcessor(configuration, User, flagCacheManager, null, false); + eventProcessor = Factory.CreateEventProcessor(configuration); + + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + + SetupConnectionManager(); + BackgroundDetection.BackgroundModeChanged += OnBackgroundModeChanged; + } + + /// + /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// fetching feature flags. + /// + /// This constructor will wait and block on the current thread until initialization and the + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . + /// + /// This is the creation point for LdClient, you must use this static method or the more specific + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The mobile key given to you by LaunchDarkly. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) + { + var config = Configuration.Default(mobileKey); + + return Init(config, user, maxWaitTime); + } + + /// + /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// fetching feature flags. This constructor should be used if you do not want to wait + /// for the client to finish initializing and receive the first response + /// from the LaunchDarkly service. + /// + /// This is the creation point for LdClient, you must use this static method or the more specific + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The mobile key given to you by LaunchDarkly. + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + public static async Task InitAsync(string mobileKey, User user) + { + var config = Configuration.Default(mobileKey); + + return await InitAsync(config, user); + } + + /// + /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// fetching Feature Flags. + /// + /// This constructor will wait and block on the current thread until initialization and the + /// first response from the LaunchDarkly service is returned, up to the specified timeout. + /// If you would rather this happen in an async fashion you can use . + /// + /// This is the creation point for LdClient, you must use this static method or the more basic + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The client configuration object + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// The maximum length of time to wait for the client to initialize. + /// If this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state. + public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) + { + if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); + } + + var c = CreateInstance(config, user); + + if (c.Online) + { + if (!c.StartUpdateProcessor(maxWaitTime)) + { + Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", + maxWaitTime.TotalMilliseconds); + } + } + + return c; + } + + /// + /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// fetching Feature Flags. This constructor should be used if you do not want to wait + /// for the IUpdateProcessor instance to finish initializing and receive the first response + /// from the LaunchDarkly service. + /// + /// This is the creation point for LdClient, you must use this static method or the more basic + /// to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// The singleton LdClient instance. + /// The client configuration object + /// The user needed for client operations. Must not be null. + /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + public static Task InitAsync(Configuration config, User user) + { + var c = CreateInstance(config, user); + + if (c.Online) + { + Task t = c.StartUpdateProcessorAsync(); + return t.ContinueWith((result) => c); + } + else + { + return Task.FromResult(c); + } + } + + static LdClient CreateInstance(Configuration configuration, User user) + { + lock (_createInstanceLock) + { + if (_instance != null) + { + throw new Exception("LdClient instance already exists."); + } + + var c = new LdClient(configuration, user); + _instance = c; + Log.InfoFormat("Initialized LaunchDarkly Client {0}", c.Version); + return c; + } + } + + void SetupConnectionManager() + { + if (connectionManager is MobileConnectionManager mobileConnectionManager) + { + mobileConnectionManager.ConnectionChanged += MobileConnectionManager_ConnectionChanged; + Log.InfoFormat("The mobile client connection changed online to {0}", + connectionManager.IsConnected); + } + _online = connectionManager.IsConnected; + } + + public async Task SetOnlineAsync(bool value) + { + await _connectionLock.WaitAsync(); + _online = value; + try + { + if (_online) + { + await RestartUpdateProcessorAsync(Config.PollingInterval); + } + else + { + ClearUpdateProcessor(); + } + } + finally + { + _connectionLock.Release(); + } + + return; + } + + void MobileConnectionManager_ConnectionChanged(bool isOnline) + { + Log.DebugFormat("Setting online to {0} due to a connectivity change event", isOnline); + Online = isOnline; + } + + /// + public bool BoolVariation(string key, bool defaultValue = false) + { + return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) + { + return VariationInternal(key, defaultValue, ValueTypes.Bool, _eventFactoryWithReasons); + } + + /// + public string StringVariation(string key, string defaultValue) + { + return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail StringVariationDetail(string key, string defaultValue) + { + return VariationInternal(key, defaultValue, ValueTypes.String, _eventFactoryWithReasons); + } + + /// + public float FloatVariation(string key, float defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueTypes.Float, _eventFactoryWithReasons); + } + + /// + public int IntVariation(string key, int defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) + { + return VariationInternal(key, defaultValue, ValueTypes.Int, _eventFactoryWithReasons); + } + + /// + public ImmutableJsonValue JsonVariation(string key, ImmutableJsonValue defaultValue) + { + return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail JsonVariationDetail(string key, ImmutableJsonValue defaultValue) + { + return VariationInternal(key, defaultValue, ValueTypes.Json, _eventFactoryWithReasons); + } + + EvaluationDetail VariationInternal(string featureKey, T defaultValue, ValueType desiredType, EventFactory eventFactory) + { + FeatureFlagEvent featureFlagEvent = FeatureFlagEvent.Default(featureKey); + JToken defaultJson = desiredType.ValueToJson(defaultValue); + + EvaluationDetail errorResult(EvaluationErrorKind kind) => + new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); + + var flag = flagCacheManager.FlagForUser(featureKey, User); + if (flag == null) + { + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); + eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + EvaluationErrorKind.CLIENT_NOT_READY)); + return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); + } + else + { + Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); + eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, + EvaluationErrorKind.FLAG_NOT_FOUND)); + return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); + } + } + else + { + if (!Initialized()) + { + Log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); + } + } + + featureFlagEvent = new FeatureFlagEvent(featureKey, flag); + EvaluationDetail result; + JToken valueJson; + if (flag.value == null || flag.value.Type == JTokenType.Null) + { + valueJson = defaultJson; + result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); + } + else + { + try + { + valueJson = flag.value; + var value = desiredType.ValueFromJson(flag.value); + result = new EvaluationDetail(value, flag.variation, flag.reason); + } + catch (Exception) + { + valueJson = defaultJson; + result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + } + } + var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, + new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); + eventProcessor.SendEvent(featureEvent); + return result; + } + + /// + public IDictionary AllFlags() + { + return flagCacheManager.FlagsForUser(User) + .ToDictionary(p => p.Key, p => ImmutableJsonValue.FromSafeValue(p.Value.value)); + } + + /// + public void Track(string eventName) + { + Track(eventName, ImmutableJsonValue.Null); + } + + /// + public void Track(string eventName, ImmutableJsonValue data) + { + eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.InnerValue)); + } + + /// + public void Track(string eventName, ImmutableJsonValue data, double metricValue) + { + eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data.InnerValue, metricValue)); + } + + /// + public bool Initialized() + { + return initialized; + } + + /// + public bool IsOffline() + { + return !_online; + } + + /// + public void Flush() + { + eventProcessor.Flush(); + } + + /// + public void Identify(User user) + { + AsyncUtils.WaitSafely(() => IdentifyAsync(user)); + } + + /// + public async Task IdentifyAsync(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + User newUser = DecorateUser(user); + + await _connectionLock.WaitAsync(); + try + { + _user = newUser; + initialized = false; + await RestartUpdateProcessorAsync(Config.PollingInterval); + } + finally + { + _connectionLock.Release(); + } + + eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(newUser)); + } + + bool StartUpdateProcessor(TimeSpan maxWaitTime) + { + if (Online) + { + var successfulConnection = AsyncUtils.WaitSafely(() => updateProcessor.Start(), maxWaitTime); + initialized = successfulConnection; + return successfulConnection; + } + else + { + return true; + } + } + + Task StartUpdateProcessorAsync() + { + if (Online) + { + var successfulConnection = updateProcessor.Start(); + if (successfulConnection.IsCompleted) + { + initialized = true; + } + return successfulConnection; + } + else + { + return Task.FromResult(true); + } + } + + async Task RestartUpdateProcessorAsync(TimeSpan pollingInterval) + { + ClearAndSetUpdateProcessor(pollingInterval); + await StartUpdateProcessorAsync(); + } + + void ClearAndSetUpdateProcessor(TimeSpan pollingInterval) + { + ClearUpdateProcessor(); + updateProcessor = Factory.CreateUpdateProcessor(Config, User, flagCacheManager, pollingInterval, _disableStreaming); + } + + void ClearUpdateProcessor() + { + if (updateProcessor != null) + { + updateProcessor.Dispose(); + updateProcessor = new NullUpdateProcessor(); + } + } + + User DecorateUser(User user) + { + IUserBuilder buildUser = null; + if (UserMetadata.DeviceName != null) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("device", UserMetadata.DeviceName); + } + if (UserMetadata.OSName != null) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("os", UserMetadata.OSName); + } + // If you pass in a user with a null or blank key, one will be assigned to them. + if (String.IsNullOrEmpty(user.Key)) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Key(deviceInfo.UniqueDeviceId()).Anonymous(true); + } + return buildUser is null ? user : buildUser.Build(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposing) + { + Log.InfoFormat("Shutting down the LaunchDarkly client"); + + BackgroundDetection.BackgroundModeChanged -= OnBackgroundModeChanged; + updateProcessor.Dispose(); + eventProcessor.Dispose(); + + // Reset the static Instance to null *if* it was referring to this instance + DetachInstance(); + } + } + + internal void DetachInstance() // exposed for testing + { + Interlocked.CompareExchange(ref _instance, null, this); + } + + /// + public Version Version + { + get + { + return MobileClientEnvironment.Instance.Version; + } + } + + /// + public event EventHandler FlagChanged + { + add + { + flagChangedEventManager.FlagChanged += value; + } + remove + { + flagChangedEventManager.FlagChanged -= value; + } + } + + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) + { + AsyncUtils.WaitSafely(() => OnBackgroundModeChangedAsync(sender, args)); + } + + internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) + { + Log.DebugFormat("Background mode is changing to {0}", args.IsInBackground); + if (args.IsInBackground) + { + ClearUpdateProcessor(); + _disableStreaming = true; + if (Config.EnableBackgroundUpdating) + { + Log.Debug("Background updating is enabled, starting polling processor"); + await RestartUpdateProcessorAsync(Config.BackgroundPollingInterval); + } + else + { + Log.Debug("Background updating is disabled"); + } + } + else + { + _disableStreaming = false; + await RestartUpdateProcessorAsync(Config.PollingInterval); + } + } + } +} From bc6097032b39ec3f0717c98838f92c0079a49eec Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 10 Oct 2019 15:05:38 -0700 Subject: [PATCH 282/499] use CommonSDK 4.2.0 (adds log helper) --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 5042619f..ee5209d1 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index caea4f3a..792ce2d3 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From eb170cf740aef21a9b6ee12c37576d7efc3e2d4a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 10 Oct 2019 15:27:46 -0700 Subject: [PATCH 283/499] version 1.1.0 --- CHANGELOG.md | 10 ++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e04cf59..441f1305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.1.0] - 2019-10-10 +### Added: +- Added support for upcoming LaunchDarkly experimentation features. See `ILdClient.Track(string, LdValue, double)`. +- `User.AnonymousOptional` and `IUserBuilder.AnonymousOptional` allow treating the `Anonymous` property as nullable (necessary for consistency with other SDKs). See note about this under Fixed. +- Added `LaunchDarkly.Logging.ConsoleAdapter` as a convenience for quickly enabling console logging; this is equivalent to `Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter`, but the latter is not available on some platforms. + +### Fixed: +- `IUserBuilder` was incorrectly setting the user's `Anonymous` property to `null` even if it had been explicitly set to `false`. Null and false behave the same in terms of LaunchDarkly's user indexing behavior, but currently it is possible to create a feature flag rule that treats them differently. So `IUserBuilder.Anonymous(false)` now correctly sets it to `false`, just as the deprecated method `UserExtensions.WithAnonymous(false)` would. +- `LdValue.Convert.Long` was mistakenly converting to an `int` rather than a `long`. (CommonSdk [#32](https://github.com/launchdarkly/dotnet-sdk-common/issues/32)) + ## [1.0.0] - 2019-09-13 First GA release. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ee5209d1..d6f08309 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -5,7 +5,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; $(LD_TARGET_FRAMEWORKS) - 1.0.0 + 1.1.0 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From 21ea91f1778c6c5c51acc4a1f49ee2616f456cba Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 14 Oct 2019 14:02:11 -0700 Subject: [PATCH 284/499] set default background polling interval --- .../ConfigurationBuilder.cs | 4 +- .../ConfigurationTest.cs | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index a97f004b..5ff0b273 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -284,7 +284,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); internal bool _allAttributesPrivate = false; - internal TimeSpan _backgroundPollingInterval; + internal TimeSpan _backgroundPollingInterval = Configuration.DefaultBackgroundPollingInterval; internal Uri _baseUri = Configuration.DefaultUri; internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; internal bool _enableBackgroundUpdating = true; @@ -303,7 +303,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; internal Uri _streamUri = Configuration.DefaultStreamUri; - internal bool _useReport; + internal bool _useReport = false; internal int _userKeysCapacity = Configuration.DefaultUserKeysCapacity; internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index 5aeb1be5..485231b6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -6,6 +6,43 @@ namespace LaunchDarkly.Xamarin.Tests { public class ConfigurationTest : BaseTest { + [Fact] + public void TestDefaultsFromDefaultFactoryMethod() + { + VerifyDefaults(Configuration.Default("my-key")); + } + + [Fact] + public void TestDefaultsFromBuilder() + { + VerifyDefaults(Configuration.Builder("my-key").Build()); + } + + private void VerifyDefaults(Configuration c) + { + Assert.False(c.AllAttributesPrivate); + Assert.Equal(Configuration.DefaultBackgroundPollingInterval, c.BackgroundPollingInterval); + Assert.Equal(Configuration.DefaultUri, c.BaseUri); + Assert.Equal(Configuration.DefaultConnectionTimeout, c.ConnectionTimeout); + Assert.True(c.EnableBackgroundUpdating); + Assert.False(c.EvaluationReasons); + Assert.Equal(Configuration.DefaultEventCapacity, c.EventCapacity); + Assert.Equal(Configuration.DefaultEventFlushInterval, c.EventFlushInterval); + Assert.Equal(Configuration.DefaultEventsUri, c.EventsUri); + Assert.False(c.InlineUsersInEvents); + Assert.True(c.IsStreamingEnabled); + Assert.False(c.Offline); + Assert.True(c.PersistFlagValues); + Assert.Equal(Configuration.DefaultPollingInterval, c.PollingInterval); + Assert.Null(c.PrivateAttributeNames); + Assert.Equal(Configuration.DefaultReadTimeout, c.ReadTimeout); + Assert.Equal(Configuration.DefaultReconnectTime, c.ReconnectTime); + Assert.Equal(Configuration.DefaultStreamUri, c.StreamUri); + Assert.False(c.UseReport); + Assert.Equal(Configuration.DefaultUserKeysCapacity, c.UserKeysCapacity); + Assert.Equal(Configuration.DefaultUserKeysFlushInterval, c.UserKeysFlushInterval); + } + [Fact] public void CanOverrideConfiguration() { From f92fd0e4b11affa317d4935cc0d2e89a7e68b450 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 11:16:18 -0700 Subject: [PATCH 285/499] fix csproj reference to MSBuild.Sdk.Extras --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index d6f08309..eb325b38 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -1,4 +1,4 @@ - + @@ -34,7 +34,6 @@ - From dab0339a74e7d6aa23a34538ef3a8cd4f615fc95 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 11:22:05 -0700 Subject: [PATCH 286/499] need to keep PackageReference? --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index eb325b38..2152a9fa 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -34,6 +34,7 @@ + From 13dca023a98a8b30d53688d0ac4a13cc71ee6a6f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 11:27:47 -0700 Subject: [PATCH 287/499] try to fix test build --- .../LaunchDarkly.XamarinSdk.Tests.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 792ce2d3..4ae20709 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0 @@ -12,6 +12,7 @@ + From 41f1d77fdf94289582751beb52c2d172c90e405f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 12:11:12 -0700 Subject: [PATCH 288/499] fix MSBuild.Sdk.Extras again (https://github.com/onovotny/MSBuildSdkExtras/blob/master/README.md) --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 2152a9fa..abed0a33 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -34,7 +34,6 @@ - @@ -81,6 +80,4 @@ - - From ad13837e8db0b009041d51838c5604534affa3b4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 12:17:22 -0700 Subject: [PATCH 289/499] try another project file format change --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 1 - .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index c6b2a02c..a041225d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 4ae20709..12a71cc5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index c5131d56..df50266e 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -1,5 +1,5 @@ - + Debug iPhoneSimulator From 1130fcfd6c40604f2eba3d58bc16a4d6ee4ce7f2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 12:29:18 -0700 Subject: [PATCH 290/499] more project config fixes --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 4 ---- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 4 ---- 2 files changed, 8 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index a041225d..748c3525 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -58,9 +58,6 @@ - - - SharedTestCode\BaseTest.cs @@ -112,7 +109,6 @@ SharedTestCode\WireMockExtensions.cs - diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index df50266e..306242c5 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -143,8 +143,6 @@ - - SharedTestCode\BaseTest.cs @@ -196,8 +194,6 @@ SharedTestCode\WireMockExtensions.cs - - From 2252db9042ac4712268272db3312e575baa2ab11 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 13:46:05 -0700 Subject: [PATCH 291/499] brew cask is built-in now --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a23e709..040d2344 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,7 +99,6 @@ jobs: - run: name: Install Xamarin tools command: | - brew install caskroom/cask/brew-cask brew cask install xamarin xamarin-ios mono-mdk # Note, "mono-mdk" provides the msbuild CLI tool From fd63c6015efcc6a5dff11efb348ea65c5448ff67 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 14:16:52 -0700 Subject: [PATCH 292/499] explicit TargetFramework --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 306242c5..10e0c9e2 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -6,6 +6,7 @@ {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32} {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {edc1b0fa-90cd-4038-8fad-98fe74adb368} + Xamarin.iOS10 Exe LaunchDarkly.XamarinSdk.iOS.Tests LaunchDarkly.XamarinSdk.iOS.Tests From 23710d8263b47adc804c23c6cedebc80702236fc Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 14:25:38 -0700 Subject: [PATCH 293/499] set OutputPath --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 10e0c9e2..a82e2672 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -8,6 +8,7 @@ {edc1b0fa-90cd-4038-8fad-98fe74adb368} Xamarin.iOS10 Exe + bin\$(Configuration)\$(Framework) LaunchDarkly.XamarinSdk.iOS.Tests LaunchDarkly.XamarinSdk.iOS.Tests Resources From 68f39a36ca1ff9296fe88fecf8b02ce38f172e2d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 14:34:51 -0700 Subject: [PATCH 294/499] rm redundant assembly info --- .../AssemblyInfo.cs | 4 -- .../Properties/AssemblyInfo.cs | 38 ++----------------- 2 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs deleted file mode 100644 index c4e33f39..00000000 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Xunit; - -// We must disable all parallel test running by XUnit in order for LogSink to work. -[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs index f68b19b0..c4e33f39 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,4 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using Xunit; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.iOS.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.iOS.Tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +// We must disable all parallel test running by XUnit in order for LogSink to work. +[assembly: CollectionBehavior(DisableTestParallelization = true)] From b2607f5e7d2219f094fef45da249b9d0d8e808bf Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 15:49:10 -0700 Subject: [PATCH 295/499] more project fixes --- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 3 +- .../Properties/AssemblyInfo.cs | 31 +------------------ .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 4 +-- 3 files changed, 4 insertions(+), 34 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 748c3525..f8b38ebc 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -17,7 +17,7 @@ Resources\Resource.designer.cs Resource Off - v8.1 + MonoAndroid81 Properties\AndroidManifest.xml Resources Assets @@ -168,5 +168,4 @@ LaunchDarkly.XamarinSdk - \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs index 20e589a2..c4e33f39 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Properties/AssemblyInfo.cs @@ -1,33 +1,4 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Xunit; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("LaunchDarkly.XamarinSdk.Android.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("LaunchDarkly.XamarinSdk.Android.Tests")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using Xunit; // We must disable all parallel test running by XUnit in order for LogSink to work. [assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index a82e2672..b2136dec 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -6,7 +6,7 @@ {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32} {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {edc1b0fa-90cd-4038-8fad-98fe74adb368} - Xamarin.iOS10 + Xamarin.iOS10 Exe bin\$(Configuration)\$(Framework) LaunchDarkly.XamarinSdk.iOS.Tests @@ -14,6 +14,7 @@ Resources true NSUrlSessionHandler + true true @@ -203,5 +204,4 @@ LaunchDarkly.XamarinSdk - \ No newline at end of file From 0fc5b6a6dfa511ea0cd439a0dbc43fd911ac4942 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 15:58:45 -0700 Subject: [PATCH 296/499] misc project fixes --- .../LaunchDarkly.XamarinSdk.csproj | 2 +- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 20 ++++++------------- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 6 +----- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index abed0a33..12f684d9 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -1,4 +1,4 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index f8b38ebc..daa6226d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -1,11 +1,10 @@  - + Debug AnyCPU 8.0.30703 2.0 - {2E7720E4-01A0-403B-863C-C6C596DF5926} {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} {122416d6-6b49-4ee2-a1e8-b825f31c79fe} Library @@ -142,20 +141,15 @@ - - 3.4.1 + - - 1.0.22 + - - 2.4.1 + - - 4.0.0.497661 + - - 2.5.25 + @@ -164,8 +158,6 @@ - {7717A2B2-9905-40A7-989F-790139D69543} - LaunchDarkly.XamarinSdk \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 12a71cc5..d726daba 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0 diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index b2136dec..5af64397 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -1,5 +1,5 @@ - + Debug iPhoneSimulator @@ -20,7 +20,6 @@ true full false - bin\iPhoneSimulator\Debug DEBUG prompt 4 @@ -32,7 +31,6 @@ none true - bin\iPhoneSimulator\Release prompt 4 None @@ -43,7 +41,6 @@ true full false - bin\iPhone\Debug DEBUG prompt 4 @@ -56,7 +53,6 @@ none true - bin\iPhone\Release prompt 4 Entitlements.plist From f04d4779be268cbdadb21a7c7b54343e3f11b7a6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 15 Oct 2019 16:05:11 -0700 Subject: [PATCH 297/499] add SDK version config --- global.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 00000000..17032b75 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "msbuild-sdks": { + "MSBuild.Sdk.Extras": "2.0.54" + } +} From 0e3a6d00a5ab0c227456c2cc4b9973c0d0e53bd6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 17 Oct 2019 15:37:05 -0700 Subject: [PATCH 298/499] changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 441f1305..9db818bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org). - Added `LaunchDarkly.Logging.ConsoleAdapter` as a convenience for quickly enabling console logging; this is equivalent to `Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter`, but the latter is not available on some platforms. ### Fixed: +- `Configuration.Builder` was not setting a default value for the `BackgroundPollingInterval` property. As a result, if you did not set the property explicitly, the SDK would throw an error when the application went into the background on mobile platforms. - `IUserBuilder` was incorrectly setting the user's `Anonymous` property to `null` even if it had been explicitly set to `false`. Null and false behave the same in terms of LaunchDarkly's user indexing behavior, but currently it is possible to create a feature flag rule that treats them differently. So `IUserBuilder.Anonymous(false)` now correctly sets it to `false`, just as the deprecated method `UserExtensions.WithAnonymous(false)` would. - `LdValue.Convert.Long` was mistakenly converting to an `int` rather than a `long`. (CommonSdk [#32](https://github.com/launchdarkly/dotnet-sdk-common/issues/32)) From 6402d5fc89c33e6a85b6e35e8270ded9fe20626f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 17 Oct 2019 15:38:01 -0700 Subject: [PATCH 299/499] changelog update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9db818bd..ca8d513b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). -## [1.1.0] - 2019-10-10 +## [1.1.0] - 2019-10-17 ### Added: - Added support for upcoming LaunchDarkly experimentation features. See `ILdClient.Track(string, LdValue, double)`. - `User.AnonymousOptional` and `IUserBuilder.AnonymousOptional` allow treating the `Anonymous` property as nullable (necessary for consistency with other SDKs). See note about this under Fixed. From c6a4a4564a230e1f9e58c8389449e8878f2c8845 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 23 Oct 2019 11:53:53 -0700 Subject: [PATCH 300/499] use CommonSdk 4.2.1 --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index d6f08309..6995707e 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 792ce2d3..1318e999 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From c4ed95132eb6976e9180b83f7061e2b9ebb01ef4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 23 Oct 2019 12:31:50 -0700 Subject: [PATCH 301/499] fix tests (user JSON no longer includes pointless "custom":{}) --- .../LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs | 2 +- .../MobileStreamingProcessorTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 53933530..4013db30 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -12,7 +12,7 @@ public class FeatureFlagRequestorTests : BaseTest private static readonly User _user = User.WithKey("foo"); private const string _userJson = "{\"key\":\"foo\"}"; - private const string _encodedUser = "eyJrZXkiOiJmb28iLCJBbm9ueW1vdXMiOmZhbHNlLCJjdXN0b20iOnt9fQ=="; + private const string _encodedUser = "eyJrZXkiOiJmb28ifQ=="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index f4f2a97d..58de0838 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -18,7 +18,7 @@ public class MobileStreamingProcessorTests : BaseTest "}"; private readonly User user = User.WithKey("me"); - private const string encodedUser = "eyJrZXkiOiJtZSIsIkFub255bW91cyI6ZmFsc2UsImN1c3RvbSI6e319"; + private const string encodedUser = "eyJrZXkiOiJtZSJ9"; private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; From c580418c7e2fe60c0902d6018c93c121afe1fecf Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 23 Oct 2019 12:34:45 -0700 Subject: [PATCH 302/499] version 1.1.1 --- CHANGELOG.md | 4 ++++ src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8d513b..4b11fe3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.1.1] - 2019-10-23 +### Fixed: +- The JSON serialization of `User` was producing an extra `Anonymous` property in addition to `anonymous`. If Newtonsoft.Json was configured globally to force all properties to lowercase, this would cause an exception when serializing a user since the two properties would end up with the same name. ([#22](https://github.com/launchdarkly/xamarin-client-sdk/issues/22)) + ## [1.1.0] - 2019-10-17 ### Added: - Added support for upcoming LaunchDarkly experimentation features. See `ILdClient.Track(string, LdValue, double)`. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 6995707e..be3ae78f 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -5,7 +5,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; $(LD_TARGET_FRAMEWORKS) - 1.1.0 + 1.1.1 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From 8325192a1dfca3b463e3229e6a771647214a287b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 5 Nov 2019 22:51:46 -0800 Subject: [PATCH 303/499] add EnumVariation extension methods --- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 4 + .../ILdClientExtensions.cs | 90 ++++++++++++++ .../ILdClientExtensionsTest.cs | 116 ++++++++++++++++++ .../LaunchDarkly.XamarinSdk.Tests.csproj | 1 + 4 files changed, 211 insertions(+) create mode 100644 src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index 04fbff99..22077794 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -8,6 +8,10 @@ namespace LaunchDarkly.Xamarin /// /// Interface for the standard SDK client methods and properties. The only implementation of this is . /// + /// + /// See also , which provides convenience methods that build upon + /// this interface. + /// public interface ILdClient : IDisposable { /// diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs new file mode 100644 index 00000000..2247aaf6 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs @@ -0,0 +1,90 @@ +using System; +using LaunchDarkly.Client; + +namespace LaunchDarkly.Xamarin +{ + /// + /// Convenience methods that extend the interface. + /// + /// + /// These are implemented outside of and because they do not + /// rely on any implementation details of ; they are decorators that would work equally + /// well with a stub or test implementation of the interface. + /// + public static class ILdClientExtensions + { + /// + /// Equivalent to , but converts the + /// flag's string value to an enum value. + /// + /// + /// + /// If the flag has a value that is not one of the allowed enum value names, or is not a string, + /// defaultValue is returned. + /// + /// + /// Note that there is no type constraint to guarantee that T really is an enum type, because that is + /// a C# 7.3 feature that is unavailable in older versions of .NET Standard. If you try to use a + /// non-enum type, you will simply receive the default value back. + /// + /// + /// the enum type + /// the client instance + /// the unique feature key for the feature flag + /// the default value of the flag (as an enum value) + /// the variation for the given user, or defaultValue if the flag cannot + /// be evaluated or does not have a valid enum value + public static T EnumVariation(this ILdClient client, string key, T defaultValue) + { + var stringVal = client.StringVariation(key, defaultValue.ToString()); + if (stringVal != null) + { + try + { + return (T)Enum.Parse(typeof(T), stringVal, true); + } + catch (ArgumentException) + { } + } + return defaultValue; + } + + /// + /// Equivalent to , but converts the + /// flag's string value to an enum value. + /// + /// + /// + /// If the flag has a value that is not one of the allowed enum value names, or is not a string, + /// defaultValue is returned. + /// + /// + /// Note that there is no type constraint to guarantee that T really is an enum type, because that is + /// a C# 7.3 feature that is unavailable in older versions of .NET Standard. If you try to use a + /// non-enum type, you will simply receive the default value back. + /// + /// + /// the enum type + /// the client instance + /// the unique feature key for the feature flag + /// the default value of the flag (as an enum value) + /// an object + public static EvaluationDetail EnumVariationDetail(this ILdClient client, string key, T defaultValue) + { + var stringDetail = client.StringVariationDetail(key, defaultValue.ToString()); + if (stringDetail.Value != null) + { + try + { + var enumValue = (T)Enum.Parse(typeof(T), stringDetail.Value, true); + return new EvaluationDetail(enumValue, stringDetail.VariationIndex, stringDetail.Reason); + } + catch (ArgumentException) + { + return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + } + } + return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, stringDetail.Reason); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs new file mode 100644 index 00000000..b740b8c6 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs @@ -0,0 +1,116 @@ +using LaunchDarkly.Client; +using Moq; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests +{ + public class ILdClientExtensionsTest + { + enum MyEnum + { + Red, + Green, + Blue + }; + + [Fact] + public void EnumVariationConvertsStringToEnum() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("Green"); + var client = clientMock.Object; + + var result = client.EnumVariation("key", MyEnum.Blue); + Assert.Equal(MyEnum.Green, result); + } + + [Fact] + public void EnumVariationReturnsDefaultValueForInvalidFlagValue() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("not-a-color"); + var client = clientMock.Object; + + var defaultValue = MyEnum.Blue; + var result = client.EnumVariation("key", defaultValue); + Assert.Equal(MyEnum.Blue, defaultValue); + } + + [Fact] + public void EnumVariationReturnsDefaultValueForNullFlagValue() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns((string)null); + var client = clientMock.Object; + + var defaultValue = MyEnum.Blue; + var result = client.EnumVariation("key", defaultValue); + Assert.Equal(defaultValue, result); + } + + [Fact] + public void EnumVariationReturnsDefaultValueForNonEnumType() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("Green"); + var client = clientMock.Object; + + var defaultValue = "this is a string, not an enum"; + var result = client.EnumVariation("key", defaultValue); + Assert.Equal(defaultValue, result); + } + + [Fact] + public void EnumVariationDetailConvertsStringToEnum() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) + .Returns(new EvaluationDetail("Green", 1, EvaluationReason.Fallthrough.Instance)); + var client = clientMock.Object; + + var result = client.EnumVariationDetail("key", MyEnum.Blue); + var expected = new EvaluationDetail(MyEnum.Green, 1, EvaluationReason.Fallthrough.Instance); + Assert.Equal(expected, result); + } + + [Fact] + public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) + .Returns(new EvaluationDetail("not-a-color", 1, EvaluationReason.Fallthrough.Instance)); + var client = clientMock.Object; + + var result = client.EnumVariationDetail("key", MyEnum.Blue); + var expected = new EvaluationDetail(MyEnum.Blue, 1, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + Assert.Equal(expected, result); + } + + [Fact] + public void EnumVariationDetailReturnsDefaultValueForNullFlagValue() + { + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) + .Returns(new EvaluationDetail(null, 1, EvaluationReason.Fallthrough.Instance)); + var client = clientMock.Object; + + var result = client.EnumVariationDetail("key", MyEnum.Blue); + var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.Fallthrough.Instance); + Assert.Equal(expected, result); + } + + [Fact] + public void EnumVariationDetailReturnsDefaultValueForNonEnumType() + { + var defaultValue = "this is a string, not an enum"; + var clientMock = new Mock(); + clientMock.Setup(c => c.StringVariationDetail("key", defaultValue)) + .Returns(new EvaluationDetail("Green", 1, EvaluationReason.Fallthrough.Instance)); + var client = clientMock.Object; + + var result = client.EnumVariationDetail("key", defaultValue); + var expected = new EvaluationDetail(defaultValue, 1, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + Assert.Equal(expected, result); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 1318e999..4cba6da5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -12,6 +12,7 @@ + From 7c50c587dc81b8a87f34a4446525b5e9039e86aa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 12 Nov 2019 16:45:07 -0800 Subject: [PATCH 304/499] update CommonSdk, don't use deprecated reason types --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 4 ++-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index be3ae78f..80ce7245 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 8767d630..c5f70297 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -396,7 +396,7 @@ EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, T defaultValue = converter.ToType(defaultJson); EvaluationDetail errorResult(EvaluationErrorKind kind) => - new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(kind)); + new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(kind)); var flag = flagCacheManager.FlagForUser(featureKey, User); if (flag == null) @@ -437,7 +437,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (checkType && !defaultJson.IsNull && flag.value.Type != defaultJson.Type) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + result = new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); } else { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 1318e999..f69dd3ea 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From ffc64e2877656dd59614a91189260ae170373f8a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Jan 2020 14:23:51 -0800 Subject: [PATCH 305/499] use transformed user, not original user, when requesting flags after Identify --- src/LaunchDarkly.XamarinSdk/LdClient.cs | 2 +- .../LdClientTests.cs | 79 +++++++++++++++++++ .../MockComponents.cs | 2 + 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index c5f70297..31bd128b 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -519,7 +519,7 @@ public async Task IdentifyAsync(User user) SendEventIfOnline(_eventFactoryDefault.NewIdentifyEvent(newUser)); return await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, user, flagCacheManager, _inBackground), + Factory.CreateUpdateProcessorFactory(_config, newUser, flagCacheManager, _inBackground), true ); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index c4671d83..b4642bcf 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -44,6 +44,41 @@ public void CanCreateClientWithInfiniteWaitTime() var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } TestUtil.ClearClient(); + } + + [Fact] + public async void InitPassesUserToUpdateProcessorFactory() + { + MockPollingProcessor stub = new MockPollingProcessor("{}"); + User testUser = User.WithKey("new-user"); + + var config = TestUtil.ConfigWithFlagsJson(testUser, appKey, "{}") + .UpdateProcessorFactory(stub.AsFactory()) + .Build(); + + using (var client = await LdClient.InitAsync(config, testUser)) + { + Assert.Same(testUser, stub.ReceivedUser); + } + } + + [Fact] + public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProcessorFactory() + { + MockPollingProcessor stub = new MockPollingProcessor("{}"); + User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); + + var config = TestUtil.ConfigWithFlagsJson(anonUserIn, appKey, "{}") + .UpdateProcessorFactory(stub.AsFactory()) + .Build(); + + using (var client = await LdClient.InitAsync(config, anonUserIn)) + { + Assert.NotSame(anonUserIn, stub.ReceivedUser); + Assert.NotNull(stub.ReceivedUser.Key); + Assert.NotEqual("", stub.ReceivedUser.Key); + Assert.True(stub.ReceivedUser.Anonymous); + } } [Fact] @@ -146,6 +181,50 @@ public void IdentifyAsyncWithNullUserThrowsException() } } + [Fact] + public async void IdentifyPassesUserToUpdateProcessorFactory() + { + MockPollingProcessor stub = new MockPollingProcessor("{}"); + User newUser = User.WithKey("new-user"); + + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .UpdateProcessorFactory(stub.AsFactory()) + .Build(); + + using (var client = await LdClient.InitAsync(config, simpleUser)) + { + Assert.Same(simpleUser, stub.ReceivedUser); + + await client.IdentifyAsync(newUser); + + Assert.Same(newUser, stub.ReceivedUser); + } + } + + [Fact] + public async void IdentifyWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProcessorFactory() + { + MockPollingProcessor stub = new MockPollingProcessor("{}"); + User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); + + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + .UpdateProcessorFactory(stub.AsFactory()) + .Build(); + + using (var client = await LdClient.InitAsync(config, simpleUser)) + { + Assert.Same(simpleUser, stub.ReceivedUser); + + await client.IdentifyAsync(anonUserIn); + + Assert.NotSame(simpleUser, stub.ReceivedUser); + Assert.NotEqual(simpleUser.Key, stub.ReceivedUser.Key); + Assert.NotNull(stub.ReceivedUser.Key); + Assert.NotEqual("", stub.ReceivedUser.Key); + Assert.True(stub.ReceivedUser.Anonymous); + } + } + [Fact] public void SharedClientIsTheOnlyClientAvailable() { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 6348ef2a..8cc5918b 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -175,6 +175,8 @@ internal class MockPollingProcessor : IMobileUpdateProcessor private User _user; private string _flagsJson; + public User ReceivedUser => _user; + public MockPollingProcessor(string flagsJson) : this(null, null, flagsJson) { } private MockPollingProcessor(IFlagCacheManager cacheManager, User user, string flagsJson) From 7ebe3812f5cf5a6e66e3c5b7a387c18c0dc7814c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Jan 2020 14:31:25 -0800 Subject: [PATCH 306/499] fix some more deprecated usages --- .../ILdClientExtensions.cs | 2 +- .../ILdClientExtensionsTest.cs | 16 ++++++++-------- .../LdClientEvaluationTests.cs | 2 +- .../LdClientEventTests.cs | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs index 2247aaf6..bf002166 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs @@ -81,7 +81,7 @@ public static EvaluationDetail EnumVariationDetail(this ILdClient client, } catch (ArgumentException) { - return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); } } return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, stringDetail.Reason); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs index b740b8c6..c0e499f3 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs @@ -65,11 +65,11 @@ public void EnumVariationDetailConvertsStringToEnum() { var clientMock = new Mock(); clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail("Green", 1, EvaluationReason.Fallthrough.Instance)); + .Returns(new EvaluationDetail("Green", 1, EvaluationReason.FallthroughReason)); var client = clientMock.Object; var result = client.EnumVariationDetail("key", MyEnum.Blue); - var expected = new EvaluationDetail(MyEnum.Green, 1, EvaluationReason.Fallthrough.Instance); + var expected = new EvaluationDetail(MyEnum.Green, 1, EvaluationReason.FallthroughReason); Assert.Equal(expected, result); } @@ -78,11 +78,11 @@ public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() { var clientMock = new Mock(); clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail("not-a-color", 1, EvaluationReason.Fallthrough.Instance)); + .Returns(new EvaluationDetail("not-a-color", 1, EvaluationReason.FallthroughReason)); var client = clientMock.Object; var result = client.EnumVariationDetail("key", MyEnum.Blue); - var expected = new EvaluationDetail(MyEnum.Blue, 1, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, result); } @@ -91,11 +91,11 @@ public void EnumVariationDetailReturnsDefaultValueForNullFlagValue() { var clientMock = new Mock(); clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail(null, 1, EvaluationReason.Fallthrough.Instance)); + .Returns(new EvaluationDetail(null, 1, EvaluationReason.FallthroughReason)); var client = clientMock.Object; var result = client.EnumVariationDetail("key", MyEnum.Blue); - var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.Fallthrough.Instance); + var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.FallthroughReason); Assert.Equal(expected, result); } @@ -105,11 +105,11 @@ public void EnumVariationDetailReturnsDefaultValueForNonEnumType() var defaultValue = "this is a string, not an enum"; var clientMock = new Mock(); clientMock.Setup(c => c.StringVariationDetail("key", defaultValue)) - .Returns(new EvaluationDetail("Green", 1, EvaluationReason.Fallthrough.Instance)); + .Returns(new EvaluationDetail("Green", 1, EvaluationReason.FallthroughReason)); var client = clientMock.Object; var result = client.EnumVariationDetail("key", defaultValue); - var expected = new EvaluationDetail(defaultValue, 1, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(defaultValue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, result); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 7de5e099..b782e37e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -38,7 +38,7 @@ public void BoolVariationReturnsDefaultForUnknownFlag() [Fact] public void BoolVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; + var reason = EvaluationReason.OffReason; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(true), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 913e54e6..73ecd4e2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -228,7 +228,7 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); - Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); + Assert.Equal(EvaluationReason.OffReason, fe.Reason); }); } } @@ -245,7 +245,7 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() { EvaluationDetail result = client.StringVariationDetail("flag", "b"); Assert.Equal("a", result.Value); - Assert.Equal(EvaluationReason.Off.Instance, result.Reason); + Assert.Equal(EvaluationReason.OffReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { @@ -257,7 +257,7 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Equal(2000, fe.DebugEventsUntilDate); - Assert.Equal(EvaluationReason.Off.Instance, fe.Reason); + Assert.Equal(EvaluationReason.OffReason, fe.Reason); }); } } @@ -268,7 +268,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() using (LdClient client = MakeClient(user, "{}")) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); - var expectedReason = new EvaluationReason.Error(EvaluationErrorKind.FLAG_NOT_FOUND); + var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, @@ -298,7 +298,7 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); - var expectedReason = new EvaluationReason.Error(EvaluationErrorKind.CLIENT_NOT_READY); + var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.CLIENT_NOT_READY); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, From 9cde1385a6e47ebfb2bdfd0639ae6e3772d999a8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Jan 2020 14:36:01 -0800 Subject: [PATCH 307/499] fix some more deprecated usages --- .../LdClientEvaluationTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index b782e37e..187ad92c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -79,7 +79,7 @@ public void IntVariationReturnsDefaultForUnknownFlag() [Fact] public void IntVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; + var reason = EvaluationReason.OffReason; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { @@ -120,7 +120,7 @@ public void FloatVariationReturnsDefaultForUnknownFlag() [Fact] public void FloatVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; + var reason = EvaluationReason.OffReason; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2.5f), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { @@ -151,7 +151,7 @@ public void StringVariationReturnsDefaultForUnknownFlag() [Fact] public void StringVariationDetailReturnsValue() { - var reason = EvaluationReason.Off.Instance; + var reason = EvaluationReason.OffReason; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value"), 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { @@ -185,7 +185,7 @@ public void JsonVariationReturnsDefaultForUnknownFlag() public void JsonVariationDetailReturnsValue() { var jsonValue = LdValue.Convert.String.ObjectFrom(new Dictionary { { "thing", "stuff" } }); - var reason = EvaluationReason.Off.Instance; + var reason = EvaluationReason.OffReason; string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); using (var client = ClientWithFlagsJson(flagsJson)) { @@ -226,7 +226,7 @@ public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { - var expected = new EvaluationDetail(3, null, new EvaluationReason.Error(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); } } From b2e51c263827c50f6ea90aaaebf7daec425f939b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Jan 2020 14:42:30 -0800 Subject: [PATCH 308/499] fix test logic for detecting generated user key --- tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs | 7 ++----- tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs | 4 ++++ tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index b4642bcf..8feea2cf 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -75,8 +75,7 @@ public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProces using (var client = await LdClient.InitAsync(config, anonUserIn)) { Assert.NotSame(anonUserIn, stub.ReceivedUser); - Assert.NotNull(stub.ReceivedUser.Key); - Assert.NotEqual("", stub.ReceivedUser.Key); + Assert.Equal(MockDeviceInfo.GeneratedId, stub.ReceivedUser.Key); Assert.True(stub.ReceivedUser.Anonymous); } } @@ -218,9 +217,7 @@ public async void IdentifyWithAutoGeneratedAnonUserPassesGeneratedUserToUpdatePr await client.IdentifyAsync(anonUserIn); Assert.NotSame(simpleUser, stub.ReceivedUser); - Assert.NotEqual(simpleUser.Key, stub.ReceivedUser.Key); - Assert.NotNull(stub.ReceivedUser.Key); - Assert.NotEqual("", stub.ReceivedUser.Key); + Assert.Equal(MockDeviceInfo.GeneratedId, stub.ReceivedUser.Key); Assert.True(stub.ReceivedUser.Anonymous); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 8cc5918b..74b6e2c6 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -49,8 +49,12 @@ public void Connect(bool online) internal class MockDeviceInfo : IDeviceInfo { + internal const string GeneratedId = "fake-generated-id"; + private readonly string key; + public MockDeviceInfo() : this(GeneratedId) { } + public MockDeviceInfo(string key) { this.key = key; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 525811fe..96051089 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -145,7 +145,7 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe .EventProcessor(new MockEventProcessor()) .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) .PersistentStorage(new MockPersistentStorage()) - .DeviceInfo(new MockDeviceInfo("")); + .DeviceInfo(new MockDeviceInfo()); } public static void AssertJsonEquals(JToken expected, JToken actual) From b6a05c78246de3379764d1cba8d33b121a37568b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Jan 2020 15:07:19 -0800 Subject: [PATCH 309/499] use CommonSdk 4.3.0 --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 80ce7245..5c6e4821 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 09fbb50a..075b3bfb 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + From bb64dfa93318f289ebb4c90d9a6757d526151b23 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 15 Jan 2020 12:25:26 -0800 Subject: [PATCH 310/499] update CommonSdk to 4.3.1 for event payload ID fix --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 5eb96490..a93fcf8b 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -33,7 +33,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index ec852d38..71d4d20f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 8feea2cf..c449d673 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -328,7 +328,7 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() { var user = User.Builder("") - .SecondaryKey("secondary") + .Secondary("secondary") .IPAddress("10.0.0.1") .Country("US") .FirstName("John") @@ -353,7 +353,7 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() Assert.Equal(user.LastName, newUser.LastName); Assert.Equal(user.Name, newUser.Name); Assert.Equal(user.IPAddress, newUser.IPAddress); - Assert.Equal(user.SecondaryKey, newUser.SecondaryKey); + Assert.Equal(user.Secondary, newUser.Secondary); Assert.Equal(user.Custom["attr"], newUser.Custom["attr"]); Assert.True(newUser.Anonymous); } From 7c88d8bca726b9fcf9faf524d594e36ab6679220 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 15 Jan 2020 13:00:16 -0800 Subject: [PATCH 311/499] version 1.2.0 --- CHANGELOG.md | 22 +++++++++++++++++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b11fe3c..30408b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.2.0] - 2020-01-15 +### Added: +- Added `ILdClient` extension methods `EnumVariation` and `EnumVariationDetail`, which convert strings to enums. +- `User.Secondary`, `IUserBuilder.Secondary` (replaces `SecondaryKey`). +- `EvaluationReason` static methods and properties for creating reason instances. +- `LdValue` helpers for dealing with array/object values, without having to use an intermediate `List` or `Dictionary`: `BuildArray`, `BuildObject`, `Count`, `Get`. +- `LdValue.Parse()`. + +### Changed: +- `EvaluationReason` properties all exist on the base class now, so for instance you do not need to cast to `RuleMatch` to get the `RuleId` property. This is in preparation for a future API change in which `EvaluationReason` will become a struct instead of a base class. + +### Fixed: +- Calling `Identify` or `IdentifyAsync` with a user that has a null key and `Anonymous(true)`-- which should generate a unique key for the anonymous user-- did not work. The symptom was that the client would fail to retrieve the flags, and the call would either never complete (for `IdentifyAsync`) or time out (for `Identify`). This has been fixed. +- Improved memory usage and performance when processing analytics events: the SDK now encodes event data to JSON directly, instead of creating intermediate objects and serializing them via reflection. +- When parsing arbitrary JSON values, the SDK now always stores them internally as `LdValue` rather than `JToken`. This means that no additional copying step is required when the application accesses that value, if it is of a complex type. +- `LdValue.Equals()` incorrectly returned true for object (dictionary) values that were not equal. +- The SDK now specifies a uniquely identifiable request header when sending events to LaunchDarkly to ensure that events are only processed once, even if the SDK sends them two times due to a failed initial attempt. + +### Deprecated: +- `IUserBuilder.SecondaryKey`, `User.SecondaryKey`. +- `EvaluationReason` subclasses. Use only the base class properties and methods to ensure compatibility with future versions. + ## [1.1.1] - 2019-10-23 ### Fixed: - The JSON serialization of `User` was producing an extra `Anonymous` property in addition to `anonymous`. If Newtonsoft.Json was configured globally to force all properties to lowercase, this would cause an exception when serializing a user since the two properties would end up with the same name. ([#22](https://github.com/launchdarkly/xamarin-client-sdk/issues/22)) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index a93fcf8b..e261d24e 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -5,7 +5,7 @@ netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; $(LD_TARGET_FRAMEWORKS) - 1.1.1 + 1.2.0 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk From 631cfa938d0f5577c132a8e2d95a2ef23c836ae7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 15 Jan 2020 16:32:01 -0800 Subject: [PATCH 312/499] project file cleanup --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 5af64397..717cc5f2 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -133,14 +133,6 @@ - - - - - - - - SharedTestCode\BaseTest.cs From c61c5a0c1c7843bcef669f528396c75b7c7246b2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 15 Jan 2020 19:49:09 -0800 Subject: [PATCH 313/499] revert project file change --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 717cc5f2..5af64397 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -133,6 +133,14 @@ + + + + + + + + SharedTestCode\BaseTest.cs From 4aa689ca8057fc3e811e99988583093696eb78e1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 09:02:25 -0800 Subject: [PATCH 314/499] try setting CodesignKey --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 5af64397..b5e54ee1 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -15,6 +15,7 @@ true NSUrlSessionHandler true + iPhone Developer true From c0925c3ffb9e76eed5898b7bb82c61609da132d2 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 09:21:05 -0800 Subject: [PATCH 315/499] build in debug mode --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 040d2344..dde1c7c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -111,11 +111,11 @@ jobs: - run: name: Build SDK - command: msbuild /restore /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj - run: name: Build test project - command: msbuild /restore tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore /p:Configuration=Debug tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - run: name: Start simulator From 3891e5b377ececdf90331be845a8358369dba6e6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 09:39:41 -0800 Subject: [PATCH 316/499] set platform explicitly to simulator in CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dde1c7c7..35cdef29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -115,7 +115,7 @@ jobs: - run: name: Build test project - command: msbuild /restore /p:Configuration=Debug tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - run: name: Start simulator From 0a1f9497225c3815ad118851b2b04eeb05b3d886 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 09:51:49 -0800 Subject: [PATCH 317/499] use Xcode 11.3 in CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 35cdef29..8408d8dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -87,7 +87,7 @@ jobs: test-ios: macos: - xcode: "10.2.1" + xcode: "11.3.0" environment: PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands From 228241f3fce4e9aa5f6f05653f4601099916f75d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 10:09:05 -0800 Subject: [PATCH 318/499] fix brew install of Xamarin tools --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8408d8dc..a0150999 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,6 +99,7 @@ jobs: - run: name: Install Xamarin tools command: | + brew untap caskroom/versions # avoids conflict on mono-mdk516 brew cask install xamarin xamarin-ios mono-mdk # Note, "mono-mdk" provides the msbuild CLI tool From 59389f5d044b40cc06d9b6bd86e70e010fcb3aa6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 13:38:40 -0800 Subject: [PATCH 319/499] fix iOS build path --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a0150999..6c961c4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -126,7 +126,7 @@ jobs: - run: name: Load test app into simulator - command: xcrun simctl install "xm-ios" tests/LaunchDarkly.XamarinSdk.iOS.Tests/bin/iPhoneSimulator/Debug/LaunchDarkly.XamarinSdk.iOS.Tests.app + command: xcrun simctl install "xm-ios" tests/LaunchDarkly.XamarinSdk.iOS.Tests/bin/Debug/xamarin.ios10/LaunchDarkly.XamarinSdk.iOS.Tests.app - run: name: Start capturing log output From 18a50d8a0136be03f3fa5ebf3e0959e0d8c59eb7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jan 2020 13:47:42 -0800 Subject: [PATCH 320/499] add comment, rm unnecessary property --- .circleci/config.yml | 7 ++++++- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c961c4b..3d9100e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -117,7 +117,12 @@ jobs: - run: name: Build test project command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj - + # Note that we must specify Platform=iPhoneSimulator here explicitly because, when using a current + # version of msbuild with a project file that uses MSBuild.Sdk.Extras, it seems like Platform does *not* + # default to an empty string (I think it defaults to "AnyCPU"), therefore it will try to build it for a + # real iPhone, which will fail because it can't do code signing. We want a debug build that we will only + # be running in the simulator. + - run: name: Start simulator command: | diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index b5e54ee1..5af64397 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -15,7 +15,6 @@ true NSUrlSessionHandler true - iPhone Developer true From a36bc34f2c55efc926bacf51a6917e8e0f65a3fa Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 5 Feb 2020 16:49:44 -0800 Subject: [PATCH 321/499] execute Xamarin SDK releases via Releaser (#98) --- .ldrelease/config.yml | 37 ++++++++++++++++++++++++++++++++++++ .ldrelease/mac-build.sh | 8 ++++++++ .ldrelease/mac-prepare.sh | 32 +++++++++++++++++++++++++++++++ .ldrelease/mac-publish.sh | 11 +++++++++++ .ldrelease/mac-test.sh | 7 +++++++ .ldrelease/update-version.sh | 11 +++++++++++ .ldrelease/windows-build.ps1 | 9 +++++++++ CHANGELOG.md | 2 +- 8 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 .ldrelease/config.yml create mode 100755 .ldrelease/mac-build.sh create mode 100755 .ldrelease/mac-prepare.sh create mode 100755 .ldrelease/mac-publish.sh create mode 100755 .ldrelease/mac-test.sh create mode 100755 .ldrelease/update-version.sh create mode 100644 .ldrelease/windows-build.ps1 diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml new file mode 100644 index 00000000..2a0cbae9 --- /dev/null +++ b/.ldrelease/config.yml @@ -0,0 +1,37 @@ +repo: + public: xamarin-client-sdk + private: xamarin-client-sdk-private + +circleci: + mac: + xcode: "11.3.0" + steps: + - step: prepare + cachePaths: + - /usr/local/Homebrew + - /usr/local/share/android-sdk + - step: build + - step: test + - step: publish + windows: # only for building documentation + env: + LD_RELEASE_DOCS_TARGET_FRAMEWORK: netstandard2.0 + LD_RELEASE_DOCS_ASSEMBLIES: LaunchDarkly.XamarinSdk LaunchDarkly.CommonSdk + steps: + - step: build + - step: build-docs + - step: publish-docs + +template: + name: dotnet-windows # only for building documentation + +publications: + - url: https://www.nuget.org/packages/LaunchDarkly.XamarinSdk + description: NuGet + +documentation: + title: LaunchDarkly Client-Side SDK for Xamarin + githubPages: true + +sdk: + displayName: "Xamarin" diff --git a/.ldrelease/mac-build.sh b/.ldrelease/mac-build.sh new file mode 100755 index 00000000..a2ab24e8 --- /dev/null +++ b/.ldrelease/mac-build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -eu + +# Build the project for all target frameworks. This includes building the .nupkg, because of +# the directive in our project file. + +msbuild /restore /p:Configuration=Debug src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh new file mode 100755 index 00000000..61863b2e --- /dev/null +++ b/.ldrelease/mac-prepare.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -eu +set +o pipefail + +export HOMEBREW_NO_AUTO_UPDATE=1 + +brew tap isen-ng/dotnet-sdk-versions + +brew cask install \ + android-sdk \ + dotnet-sdk-2.2.400 \ + mono-mdk \ + xamarin \ + xamarin-android \ + xamarin-ios + +brew install awscli + +for path_component in /Library/Frameworks/Mono.framework/Commands /usr/local/share/android-sdk/tools/bin /usr/local/share/android-sdk/platform-tools; do + echo "export PATH=\"\$PATH:$path_component\"" >> $BASH_ENV +done + +echo "export ANDROID_HOME=/usr/local/share/android-sdk" >> $BASH_ENV +echo "export ANDROID_SDK_HOME=/usr/local/share/android-sdk" >> $BASH_ENV +echo "export ANDROID_SDK_ROOT=/usr/local/share/android-sdk" >> $BASH_ENV + +source $BASH_ENV + +sudo mkdir -p /usr/local/android-sdk-linux/licenses +yes | sdkmanager "platform-tools" "platforms;android-25" "platforms;android-26" "platforms;android-27" "build-tools;26.0.2" +yes | sdkmanager --licenses diff --git a/.ldrelease/mac-publish.sh b/.ldrelease/mac-publish.sh new file mode 100755 index 00000000..6d04971d --- /dev/null +++ b/.ldrelease/mac-publish.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -eu + +# Since we are currently publishing in Debug configuration, we can push the .nupkg that build.sh already built. + +export AWS_DEFAULT_REGION=us-east-1 +NUGET_KEY=$(aws ssm get-parameter --name /production/common/services/nuget/api_key --with-decryption --query "Parameter.Value" --output text) + +nuget push "./src/LaunchDarkly.XamarinSdk/bin/Debug/LaunchDarkly.XamarinSdk.${LD_RELEASE_VERSION}.nupkg" \ + -ApiKey "${NUGET_KEY}" -Source https://www.nuget.org diff --git a/.ldrelease/mac-test.sh b/.ldrelease/mac-test.sh new file mode 100755 index 00000000..19f45074 --- /dev/null +++ b/.ldrelease/mac-test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eu + +# Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by regular CI jobs) + +dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 diff --git a/.ldrelease/update-version.sh b/.ldrelease/update-version.sh new file mode 100755 index 00000000..a27e8fa1 --- /dev/null +++ b/.ldrelease/update-version.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This script gets run on the Releaser host, not in the Mac or Windows CI jobs + +set -eu + +PROJECT_FILE=./src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +TEMP_FILE="${PROJECT_FILE}.tmp" + +sed "s#^\( *\)[^<]*#\1${LD_RELEASE_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" +mv "${TEMP_FILE}" "${PROJECT_FILE}" diff --git a/.ldrelease/windows-build.ps1 b/.ldrelease/windows-build.ps1 new file mode 100644 index 00000000..c5481658 --- /dev/null +++ b/.ldrelease/windows-build.ps1 @@ -0,0 +1,9 @@ + +# Different from the standard build.ps1 for .NET projects because we must use msbuild instead of dotnet build + +$ErrorActionPreference = "Stop" + +$scriptDir = split-path -parent $MyInvocation.MyCommand.Definition +Import-Module "$scriptDir/circleci/template/helpers.psm1" -Force + +ExecuteOrFail { msbuild /restore /p:TargetFramework=netstandard2.0 /p:Configuration=Debug src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj } diff --git a/CHANGELOG.md b/CHANGELOG.md index 30408b6b..2dc32392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change log -All notable changes to the LaunchDarkly Client-side SDK for Xamarin will be documented in this file. +All notable changes to the LaunchDarkly Client-Side SDK for Xamarin will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). ## [1.2.0] - 2020-01-15 From 6500cccea4aa2dfa908c2b23d13e7c159a26ebb4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 10 Mar 2020 15:31:15 -0700 Subject: [PATCH 322/499] fix Android CI build (#99) --- .circleci/config.yml | 78 ++++++++++++---- .circleci/scripts/LICENSE | 21 +++++ .circleci/scripts/circle-android | 88 +++++++++++++++++++ ...unchDarkly.XamarinSdk.Android.Tests.csproj | 22 +++-- .../LdClientTests.cs | 16 +++- 5 files changed, 197 insertions(+), 28 deletions(-) create mode 100644 .circleci/scripts/LICENSE create mode 100755 .circleci/scripts/circle-android diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d9100e0..b3da870b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,45 +21,87 @@ jobs: - run: dotnet test -v=normal tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 test-android: - docker: - - image: ldcircleci/ld-xamarin-android-linux:api27 + macos: + xcode: "10.3.0" environment: - LD_SKIP_XML_DOCS: 1 # there seems to be a bug in Xamarin Android builds on Linux that makes the XML build step fail + PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands + TERM: dumb + QEMU_AUDIO_DRV: none + ANDROID_HOME: "/usr/local/share/android-sdk" + ANDROID_SDK_HOME: "/usr/local/share/android-sdk" + ANDROID_SDK_ROOT: "/usr/local/share/android-sdk" steps: - - checkout + - run: + name: Setup env + command: | + echo 'export PATH="$PATH:/$ANDROID_HOME/tools/bin"' >> $BASH_ENV + echo 'export PATH="$PATH:/$ANDROID_HOME/platform-tools"' >> $BASH_ENV + + - restore_cache: + key: homebrew-xamarin-android - run: - name: Build SDK - command: msbuild /restore /p:TargetFramework=MonoAndroid81 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + name: Install Xamarin Android tools + command: | + HOMEBREW_NO_AUTO_UPDATE=1 brew cask install xamarin xamarin-android mono-mdk + # Note, "mono-mdk" provides the msbuild CLI tool - run: - name: Build test project - command: ~/xamarin-android/bin/Debug/bin/xabuild /restore /t:SignAndroidPackage tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj - # Note, xabuild is just a wrapper for msbuild that adds various tools paths etc. necessary for building an - # Android app. See: https://github.com/xamarin/xamarin-android/blob/master/tools/scripts/xabuild + name: Install Android SDK + command: | + if [ ! -d "$ANDROID_HOME" ]; then + HOMEBREW_NO_AUTO_UPDATE=1 brew cask install android-sdk + fi + + - run: + name: Set up Android SDK + command: | + sudo mkdir -p /usr/local/android-sdk-linux/licenses + yes | sdkmanager "platform-tools" "platforms;android-27" "extras;intel;Hardware_Accelerated_Execution_Manager" \ + "build-tools;26.0.2" "system-images;android-27;default;x86" "emulator" | grep -v = || true + set +o pipefail + yes | sdkmanager --licenses >/dev/null + + - save_cache: + key: homebrew-xamarin-android + paths: + - /usr/local/Homebrew + - /usr/local/share/android-sdk + + - checkout - run: name: Set up emulator - command: echo no | avdmanager create avd -n xm-android -f -k "system-images;android-24;default;armeabi-v7a" + command: echo no | avdmanager create avd -n ci-android-avd -f -k "system-images;android-27;default;x86" - run: name: Start emulator - command: emulator -avd xm-android -netdelay none -netspeed full -no-audio -no-window -no-snapshot -use-system-libs -no-boot-anim + command: $ANDROID_HOME/emulator/emulator -avd ci-android-avd -netdelay none -netspeed full -no-audio -no-window -no-snapshot -no-boot-anim background: true timeout: 1200 - no_output_timeout: 20m + no_output_timeout: 2h + + - run: + name: Build SDK + command: | + msbuild /restore /p:TargetFramework=MonoAndroid81 \ + src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + + - run: + name: Build test project + command: | + msbuild /restore /t:SignAndroidPackage \ + tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj - run: - name: Wait for emulator to become available - command: circle-android wait-for-boot - # this script is provided in the CircleCI Android images: - # https://raw.githubusercontent.com/circleci/circleci-images/master/android/bin/circle-android + name: Wait for emulator + command: .circleci/scripts/circle-android wait-for-boot - run: name: Start capturing log output - command: adb logcat mono-stdout:D *:S | tee test-run.log + command: adb logcat mono-stdout:D AndroidRuntime:D *:S | tee test-run.log # mono-stdout is the default tag for standard output from a Xamarin app - that's where our test runner output goes background: true no_output_timeout: 10m diff --git a/.circleci/scripts/LICENSE b/.circleci/scripts/LICENSE new file mode 100644 index 00000000..13115564 --- /dev/null +++ b/.circleci/scripts/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2019 Circle Internet Services, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.circleci/scripts/circle-android b/.circleci/scripts/circle-android new file mode 100755 index 00000000..4524b60b --- /dev/null +++ b/.circleci/scripts/circle-android @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +# See LICENSE file in this directory for copyright and license information +# Above LICENSE notice added 10/16/2019 by Gavin Whelan + +from sys import argv, exit, stdout +from time import sleep, time +from os import system +from subprocess import check_output, CalledProcessError +from threading import Thread, Event +from functools import partial + +class StoppableThread(Thread): + + def __init__(self): + super(StoppableThread, self).__init__() + self._stop_event = Event() + self.daemon = True + + def stopped(self): + return self._stop_event.is_set() + + def run(self): + while not self.stopped(): + stdout.write('.') + stdout.flush() + sleep(2) + + def stop(self): + self._stop_event.set() + +def shell_getprop(name): + try: + return check_output(['adb', 'shell', 'getprop', name]).strip() + except CalledProcessError as e: + return '' + +start_time = time() + +def wait_for(name, fn): + stdout.write('Waiting for %s' % name) + spinner = StoppableThread() + spinner.start() + stdout.flush() + while True: + if fn(): + spinner.stop() + time_taken = int(time() - start_time) + print('\n%s is ready after %d seconds' % (name, time_taken)) + break + sleep(1) + +def device_ready(): + return system('adb wait-for-device') == 0 + +def shell_ready(): + return system('adb shell true &> /dev/null') == 0 + +def prop_has_value(prop, value): + return shell_getprop(prop) == value + +def wait_for_sys_prop(name, prop, value): + # return shell_getprop('init.svc.bootanim') == 'stopped' + wait_for(name, partial(prop_has_value, prop, value)) + +usage = """ +%s, a collection of tools for CI with android. + +Usage: + %s wait-for-boot - wait for a device to fully boot. + (adb wait-for-device only waits for it to be ready for shell access). +""" + +if __name__ == "__main__": + + if len(argv) != 2 or argv[1] != 'wait-for-boot': + print(usage % (argv[0], argv[0])) + exit(0) + + wait_for('Device', device_ready) + wait_for('Shell', shell_ready) + wait_for_sys_prop('Boot animation complete', 'init.svc.bootanim', 'stopped') + wait_for_sys_prop('Boot animation exited', 'service.bootanim.exit', '1') + wait_for_sys_prop('System boot complete', 'sys.boot_completed', '1') + wait_for_sys_prop('GSM Ready', 'gsm.sim.state', 'READY') + #wait_for_sys_prop('init.svc.clear-bcb' ,'init.svc.clear-bcb', 'stopped') + + diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index daa6226d..f0108d15 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -16,12 +16,15 @@ Resources\Resource.designer.cs Resource Off - MonoAndroid81 + v8.1 Properties\AndroidManifest.xml Resources Assets true False + {2E7720E4-01A0-403B-863C-C6C596DF5926} + False + armeabi-v7a;arm64-v8a True @@ -31,9 +34,9 @@ DEBUG;TRACE prompt 4 - True None - False + True + armeabi-v7a;arm64-v8a;x86 True @@ -44,9 +47,9 @@ prompt 4 true - False SdkOnly True + armeabi-v7a;arm64-v8a @@ -57,6 +60,10 @@ + + + + SharedTestCode\BaseTest.cs @@ -158,6 +165,9 @@ + {7717A2B2-9905-40A7-989F-790139D69543} + LaunchDarkly.XamarinSdk - \ No newline at end of file + + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index c449d673..ace185dd 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -58,7 +58,9 @@ public async void InitPassesUserToUpdateProcessorFactory() using (var client = await LdClient.InitAsync(config, testUser)) { - Assert.Same(testUser, stub.ReceivedUser); + var actualUser = client.User; // may have been transformed e.g. to add device/OS properties + Assert.Equal(testUser.Key, actualUser.Key); + Assert.Equal(actualUser, stub.ReceivedUser); } } @@ -192,11 +194,15 @@ public async void IdentifyPassesUserToUpdateProcessorFactory() using (var client = await LdClient.InitAsync(config, simpleUser)) { - Assert.Same(simpleUser, stub.ReceivedUser); + var actualUser = client.User; // may have been transformed e.g. to add device/OS properties + Assert.Equal(simpleUser.Key, actualUser.Key); + Assert.Equal(actualUser, stub.ReceivedUser); await client.IdentifyAsync(newUser); - Assert.Same(newUser, stub.ReceivedUser); + actualUser = client.User; + Assert.Equal(newUser.Key, actualUser.Key); + Assert.Equal(actualUser, stub.ReceivedUser); } } @@ -212,7 +218,9 @@ public async void IdentifyWithAutoGeneratedAnonUserPassesGeneratedUserToUpdatePr using (var client = await LdClient.InitAsync(config, simpleUser)) { - Assert.Same(simpleUser, stub.ReceivedUser); + var actualUser = client.User; // may have been transformed e.g. to add device/OS properties + Assert.Equal(simpleUser.Key, actualUser.Key); + Assert.Equal(actualUser, stub.ReceivedUser); await client.IdentifyAsync(anonUserIn); From fb51711326f619348fe82a8bc57178b16fdee513 Mon Sep 17 00:00:00 2001 From: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Date: Wed, 3 Feb 2021 15:13:03 -0800 Subject: [PATCH 323/499] Removed the guides link --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d595977..f6c73c2e 100644 --- a/README.md +++ b/README.md @@ -50,4 +50,3 @@ About LaunchDarkly * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates - * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies From f4be21f899929d354440c0b106758513cc309975 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 5 Mar 2021 14:59:45 -0800 Subject: [PATCH 324/499] don't drop base paths from custom base URIs --- src/LaunchDarkly.XamarinSdk/Configuration.cs | 25 +++++++++- src/LaunchDarkly.XamarinSdk/Constants.cs | 2 +- src/LaunchDarkly.XamarinSdk/Extensions.cs | 23 +++++++++ .../FeatureFlagRequestor.cs | 9 +--- .../MobileStreamingProcessor.cs | 9 +--- .../FeatureFlagRequestorTests.cs | 49 +++++++------------ .../LDClientEndToEndTests.cs | 38 ++++++++++++++ .../MobileStreamingProcessorTests.cs | 22 +++++++-- 8 files changed, 127 insertions(+), 50 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index fd642cc8..87c90839 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -357,7 +357,30 @@ private class EventProcessorAdapter : IEventProcessorConfiguration public bool AllAttributesPrivate => Config.AllAttributesPrivate; public int EventCapacity => Config.EventCapacity; public TimeSpan EventFlushInterval => Config.EventFlushInterval; - public Uri EventsUri => Config.EventsUri; + public Uri EventsUri + { + get + { + // This is a hack to work around the fact that the implementation of DefaultEventProcessor + // in LaunchDarkly.CommonSdk 4.x does not use the path concatenation logic that we implemented + // in this assembly in Extensions.AddPath(Uri, string), which assumes a trailing slash in + // the base path. Instead it just calls new Uri(Uri, string) which will drop the last path + // component of the base path if there's no trailing slash. So we add the trailing slash + // here, and we do *not* include a leading slash in Constants.EVENTS_PATH. + // + // In the next major version of the SDK, we'll be using a different implementation of + // DefaultEventProcessor that's in a different assembly and giving it a pre-concatenated URI. + // So there's no point in fixing this in the current LaunchDarkly.CommonSdk implementation, + // which isn't used anywhere else. + var ub = new UriBuilder(Config.EventsUri); + if (ub.Path.EndsWith("/")) + { + return Config.EventsUri; + } + ub.Path += "/"; + return ub.Uri; + } + } public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; public bool InlineUsersInEvents => Config.InlineUsersInEvents; public IImmutableSet PrivateAttributeNames => Config.PrivateAttributeNames; diff --git a/src/LaunchDarkly.XamarinSdk/Constants.cs b/src/LaunchDarkly.XamarinSdk/Constants.cs index cdea0913..db69acc5 100644 --- a/src/LaunchDarkly.XamarinSdk/Constants.cs +++ b/src/LaunchDarkly.XamarinSdk/Constants.cs @@ -19,7 +19,7 @@ internal static class Constants public const string PATCH = "patch"; public const string DELETE = "delete"; public const string PING = "ping"; - public const string EVENTS_PATH = "/mobile/events/bulk"; + public const string EVENTS_PATH = "mobile/events/bulk"; public const string UNIQUE_ID_KEY = "unique_id_key"; } } diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs index 8df77793..5f1a469d 100644 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ b/src/LaunchDarkly.XamarinSdk/Extensions.cs @@ -15,5 +15,28 @@ public static string AsJson(this User user) { return JsonUtil.EncodeJson(user); } + + // This differs from "new Uri(baseUri, path)" in that it always assumes a trailing "/" in + // baseUri. For instance, if baseUri is http://hostname/basepath and path is "relativepath", + // baseUri.AddPath(path) will return http://hostname/basepath/relativepath, whereas + // new Uri(baseUri, path) would return http://hostname/relativepath (since, with no trailing + // slash, it would treat "basepath" as the equivalent of a filename rather than the + // equivalent of a directory name). We should assume that if an application has specified a + // base URL with a non-empty path, the intention is to use that as a prefix for everything. + public static Uri AddPath(this Uri baseUri, string path) + { + var ub = new UriBuilder(baseUri); + ub.Path = ub.Path.TrimEnd('/') + "/" + path.TrimStart('/'); + return ub.Uri; + } + + public static Uri AddQuery(this Uri baseUri, string query) + { + var ub = new UriBuilder(baseUri); + ub.Query = string.IsNullOrEmpty(ub.Query) ? + ("?" + query) : + ub.Query + "&" + query; + return ub.Uri; + } } } diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index db5ae4ae..ca3d1ed8 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -68,13 +68,8 @@ private HttpRequestMessage ReportRequestMessage() private Uri MakeRequestUriWithPath(string path) { - var uri = new UriBuilder(_configuration.BaseUri); - uri.Path = path; - if (_configuration.EvaluationReasons) - { - uri.Query = "withReasons=true"; - } - return uri.Uri; + var uri = _configuration.BaseUri.AddPath(path); + return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; } private async Task MakeRequest(HttpRequestMessage request) diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index d016355b..26073ad5 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -68,13 +68,8 @@ private StreamProperties MakeStreamPropertiesForReport() private Uri MakeRequestUriWithPath(string path) { - var uri = new UriBuilder(_configuration.StreamUri); - uri.Path = path; - if (_configuration.EvaluationReasons) - { - uri.Query = "withReasons=true"; - } - return uri.Uri; + var uri = _configuration.StreamUri.AddPath(path); + return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; } #region IStreamProcessor diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 4013db30..3516ac70 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -18,14 +18,27 @@ public class FeatureFlagRequestorTests : BaseTest private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response - [Fact] - public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync() + [Theory] + [InlineData("", false, "/msdk/evalx/users/", "")] + [InlineData("", true, "/msdk/evalx/users/", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/msdk/evalx/users/", "")] + [InlineData("/basepath", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/msdk/evalx/users/", "")] + [InlineData("/basepath/", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] + public async Task GetFlagsUsesCorrectUriAndMethodInGetModexAsync( + string baseUriExtraPath, + bool withReasons, + string expectedPathWithoutUser, + string expectedQuery + ) { await WithServerAsync(async server => { server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) + var config = Configuration.Builder(_mobileKey) + .BaseUri(new Uri(server.GetUrl() + baseUriExtraPath)) + .EvaluationReasons(withReasons) .Build(); using (var requestor = new FeatureFlagRequestor(config, _user)) @@ -36,34 +49,8 @@ await WithServerAsync(async server => var req = server.GetLastRequest(); Assert.Equal("GET", req.Method); - Assert.Equal($"/msdk/evalx/users/{_encodedUser}", req.Path); - Assert.Equal("", req.RawQuery); - Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - Assert.Null(req.Body); - } - }); - } - - [Fact] - public async Task GetFlagsUsesCorrectUriAndMethodInGetModeWithReasonsAsync() - { - await WithServerAsync(async server => - { - server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - - var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - .EvaluationReasons(true).Build(); - - using (var requestor = new FeatureFlagRequestor(config, _user)) - { - var resp = await requestor.FeatureFlagsAsync(); - Assert.Equal(200, resp.statusCode); - Assert.Equal(_allDataJson, resp.jsonResponse); - - var req = server.GetLastRequest(); - Assert.Equal("GET", req.Method); - Assert.Equal($"/msdk/evalx/users/{_encodedUser}", req.Path); - Assert.Equal("?withReasons=true", req.RawQuery); + Assert.Equal(expectedPathWithoutUser + _encodedUser, req.Path); + Assert.Equal(expectedQuery, req.RawQuery); Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); Assert.Null(req.Body); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index d3862501..ed7d2bca 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -7,6 +8,7 @@ using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; +using WireMock; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; @@ -255,6 +257,42 @@ public void IdentifyCanTimeOutSync(UpdateMode mode) }); } + [Theory] + [InlineData("", "/mobile/events/bulk")] + [InlineData("/basepath", "/basepath/mobile/events/bulk")] + [InlineData("/basepath/", "/basepath/mobile/events/bulk")] + public void EventsAreSentToCorrectEndpointAsync( + string baseUriExtraPath, + string expectedPath + ) + { + var requests = new BlockingCollection(); + var gotRequest = new EventWaitHandle(false, EventResetMode.ManualReset); + WithServer(server => + { + server.ForAllRequests(r => r.WithCallback(req => + { + requests.Add(req); + return new ResponseMessage() { StatusCode = 202 }; + })); + + var config = Configuration.BuilderInternal(_mobileKey) + .UpdateProcessorFactory(MockPollingProcessor.Factory("{}")) + .EventsUri(new Uri(server.Urls[0] + baseUriExtraPath)) + .PersistFlagValues(false) + .Build(); + + using (var client = TestUtil.CreateClient(config, _user)) + { + client.Flush(); + Assert.True(requests.TryTake(out var request, TimeSpan.FromSeconds(5))); + + Assert.Equal("POST", request.Method); + Assert.Equal(expectedPath, request.Path); + } + }); + } + [Fact] public void OfflineClientUsesCachedFlagsSync() { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 58de0838..71dc9cfd 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -46,14 +46,30 @@ private IMobileUpdateProcessor MobileStreamingProcessorStarted() return processor; } - [Fact] - public void StreamUriInGetModeHasUser() + [Theory] + [InlineData("", false, "/meval/", "")] + [InlineData("", true, "/meval/", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/meval/", "")] + [InlineData("/basepath", true, "/basepath/meval/", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/meval/", "")] + [InlineData("/basepath/", true, "/basepath/meval/", "?withReasons=true")] + public void RequestHasCorrectUriAndMethodInGetMode( + string baseUriExtraPath, + bool withReasons, + string expectedPathWithoutUser, + string expectedQuery + ) { + var fakeRootUri = "http://fake-stream-host"; + var fakeBaseUri = fakeRootUri + baseUriExtraPath; + configBuilder.StreamUri(new Uri(fakeBaseUri)); + configBuilder.EvaluationReasons(withReasons); var config = configBuilder.Build(); MobileStreamingProcessorStarted(); var props = eventSourceFactory.ReceivedProperties; Assert.Equal(HttpMethod.Get, props.Method); - Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser), props.StreamUri); + Assert.Equal(new Uri(fakeRootUri + expectedPathWithoutUser + encodedUser + expectedQuery), + props.StreamUri); } [Fact] From 588e6ce68d75654d0c51e02b40ea7db2925540d8 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Thu, 11 Mar 2021 13:02:34 -0800 Subject: [PATCH 325/499] Update base64 encoding of user JSON to be URL safe. (#102) --- src/LaunchDarkly.XamarinSdk/Extensions.cs | 4 ++-- src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs | 2 +- src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs | 2 +- .../FeatureFlagRequestorTests.cs | 8 +++++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs index 8df77793..8d219aba 100644 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ b/src/LaunchDarkly.XamarinSdk/Extensions.cs @@ -5,10 +5,10 @@ namespace LaunchDarkly.Xamarin { internal static class Extensions { - public static string Base64Encode(this string plainText) + public static string UrlSafeBase64Encode(this string plainText) { var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes); + return Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_'); } public static string AsJson(this User user) diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index db5ae4ae..50e3fbfc 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -55,7 +55,7 @@ public async Task FeatureFlagsAsync() private HttpRequestMessage GetRequestMessage() { - var path = Constants.FLAG_REQUEST_PATH_GET + _currentUser.AsJson().Base64Encode(); + var path = Constants.FLAG_REQUEST_PATH_GET + _currentUser.AsJson().UrlSafeBase64Encode(); return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index d016355b..a26f05e4 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -55,7 +55,7 @@ async Task IMobileUpdateProcessor.Start() private StreamProperties MakeStreamPropertiesForGet() { - var userEncoded = _user.AsJson().Base64Encode(); + var userEncoded = _user.AsJson().UrlSafeBase64Encode(); var path = Constants.STREAM_REQUEST_PATH + userEncoded; return new StreamProperties(MakeRequestUriWithPath(path), HttpMethod.Get, null); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index 4013db30..d95d91dd 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -10,9 +10,11 @@ public class FeatureFlagRequestorTests : BaseTest { private const string _mobileKey = "FAKE_KEY"; - private static readonly User _user = User.WithKey("foo"); - private const string _userJson = "{\"key\":\"foo\"}"; - private const string _encodedUser = "eyJrZXkiOiJmb28ifQ=="; + // User key constructed to test base64 encoding of 62 and 63, which differ between the standard and "URL and Filename safe" + // base64 encodings from RFC4648. We need to use the URL safe encoding for flag requests. + private static readonly User _user = User.WithKey("foo>bar__?"); + private const string _userJson = "{\"key\":\"foo>bar__?\"}"; + private const string _encodedUser = "eyJrZXkiOiJmb28-YmFyX18_In0="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. From 977b360339d7bd86f05440750b3f35e8f2932722 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 25 Mar 2021 09:39:30 -0700 Subject: [PATCH 326/499] make the Android CI build work (#103) --- .circleci/config.yml | 50 +++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b3da870b..3c17ca20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: test-android: macos: - xcode: "10.3.0" + xcode: "12.4.0" environment: PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands @@ -33,27 +33,38 @@ jobs: ANDROID_SDK_ROOT: "/usr/local/share/android-sdk" steps: + - checkout + - run: name: Setup env command: | - echo 'export PATH="$PATH:/$ANDROID_HOME/tools/bin"' >> $BASH_ENV - echo 'export PATH="$PATH:/$ANDROID_HOME/platform-tools"' >> $BASH_ENV + echo 'export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:/$ANDROID_HOME/tools/bin:$PATH"' >> $BASH_ENV + echo 'export PATH="$ANDROID_HOME/platform-tools:$PATH"' >> $BASH_ENV - - restore_cache: - key: homebrew-xamarin-android + - run: + name: Install .NET command-line tools + command: brew install --cask dotnet-sdk mono-mdk android-sdk + # mono-mdk provides the msbuild command - run: - name: Install Xamarin Android tools - command: | - HOMEBREW_NO_AUTO_UPDATE=1 brew cask install xamarin xamarin-android mono-mdk - # Note, "mono-mdk" provides the msbuild CLI tool + name: Install installer tool + command: dotnet tool install --global boots - run: - name: Install Android SDK + name: Install Xamarin Android + command: boots https://aka.ms/xamarin-android-commercial-d16-8-macos + # The Homebrew installer for Xamarin Android is out of date and unmaintained + + - run: + name: Download Android SDK command-line tools + # we don't use Homebrew for this, because the version they had (as of March 2021) + # was out of date and incompatible with Java 11 command: | - if [ ! -d "$ANDROID_HOME" ]; then - HOMEBREW_NO_AUTO_UPDATE=1 brew cask install android-sdk - fi + mkdir -p $ANDROID_HOME + mkdir -p $ANDROID_HOME/cmdline-tools + mkdir -p /tmp/android-sdk + curl https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip >/tmp/android-sdk/android-sdk.zip + cd /tmp/android-sdk && unzip android-sdk.zip && mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest - run: name: Set up Android SDK @@ -64,12 +75,6 @@ jobs: set +o pipefail yes | sdkmanager --licenses >/dev/null - - save_cache: - key: homebrew-xamarin-android - paths: - - /usr/local/Homebrew - - /usr/local/share/android-sdk - - checkout - run: @@ -129,20 +134,21 @@ jobs: test-ios: macos: - xcode: "11.3.0" + xcode: "12.4.0" environment: PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands steps: + - checkout + - restore_cache: key: homebrew - run: name: Install Xamarin tools command: | - brew untap caskroom/versions # avoids conflict on mono-mdk516 - brew cask install xamarin xamarin-ios mono-mdk + brew install --cask xamarin xamarin-ios mono-mdk # Note, "mono-mdk" provides the msbuild CLI tool - save_cache: From ffb5c5949cfde2d6b2364124a0bc04e44aa01c4f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 25 Mar 2021 14:03:37 -0700 Subject: [PATCH 327/499] Removed redundant dependencies on android support libraries. This allows to use the package with mono droid 10 and Jetpack. (#104) Co-authored-by: Vladimir-Mischenchuk --- .../LaunchDarkly.XamarinSdk.csproj | 18 ------------------ .../PlatformSpecific/Permissions.android.cs | 2 -- 2 files changed, 20 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index e261d24e..74a13b4a 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -56,24 +56,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs index a63e2a10..0efe66d4 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs @@ -27,8 +27,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android; using Android.Content.PM; using Android.OS; -using Android.Support.V4.App; -using Android.Support.V4.Content; namespace LaunchDarkly.Xamarin.PlatformSpecific { From 9ca73c20c1f27a2bedc10c759b722ce59fd9561d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 26 Mar 2021 15:57:25 -0700 Subject: [PATCH 328/499] better install logic for CI and release (#105) --- .circleci/config.yml | 65 +++--------------------- .ldrelease/config.yml | 10 +--- .ldrelease/mac-prepare.sh | 28 ++--------- .ldrelease/windows-build.ps1 | 4 +- scripts/macos-install-android-sdk.sh | 70 ++++++++++++++++++++++++++ scripts/macos-install-xamarin.sh | 74 ++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 94 deletions(-) create mode 100755 scripts/macos-install-android-sdk.sh create mode 100755 scripts/macos-install-xamarin.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c17ca20..6cc806f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,57 +25,19 @@ jobs: xcode: "12.4.0" environment: - PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands TERM: dumb QEMU_AUDIO_DRV: none - ANDROID_HOME: "/usr/local/share/android-sdk" - ANDROID_SDK_HOME: "/usr/local/share/android-sdk" - ANDROID_SDK_ROOT: "/usr/local/share/android-sdk" steps: - checkout - run: - name: Setup env - command: | - echo 'export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:/$ANDROID_HOME/tools/bin:$PATH"' >> $BASH_ENV - echo 'export PATH="$ANDROID_HOME/platform-tools:$PATH"' >> $BASH_ENV - - - run: - name: Install .NET command-line tools - command: brew install --cask dotnet-sdk mono-mdk android-sdk - # mono-mdk provides the msbuild command - - - run: - name: Install installer tool - command: dotnet tool install --global boots + name: Install .NET/Xamarin build tools + command: ./scripts/macos-install-xamarin.sh android - run: - name: Install Xamarin Android - command: boots https://aka.ms/xamarin-android-commercial-d16-8-macos - # The Homebrew installer for Xamarin Android is out of date and unmaintained - - - run: - name: Download Android SDK command-line tools - # we don't use Homebrew for this, because the version they had (as of March 2021) - # was out of date and incompatible with Java 11 - command: | - mkdir -p $ANDROID_HOME - mkdir -p $ANDROID_HOME/cmdline-tools - mkdir -p /tmp/android-sdk - curl https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip >/tmp/android-sdk/android-sdk.zip - cd /tmp/android-sdk && unzip android-sdk.zip && mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest - - - run: - name: Set up Android SDK - command: | - sudo mkdir -p /usr/local/android-sdk-linux/licenses - yes | sdkmanager "platform-tools" "platforms;android-27" "extras;intel;Hardware_Accelerated_Execution_Manager" \ - "build-tools;26.0.2" "system-images;android-27;default;x86" "emulator" | grep -v = || true - set +o pipefail - yes | sdkmanager --licenses >/dev/null - - - checkout + name: Install Android SDK + command: ./scripts/macos-install-android-sdk.sh 27 - run: name: Set up emulator @@ -136,27 +98,12 @@ jobs: macos: xcode: "12.4.0" - environment: - PATH: /usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Mono.framework/Commands - steps: - checkout - - restore_cache: - key: homebrew - - run: - name: Install Xamarin tools - command: | - brew install --cask xamarin xamarin-ios mono-mdk - # Note, "mono-mdk" provides the msbuild CLI tool - - - save_cache: - key: homebrew - paths: - - /usr/local/Homebrew - - - checkout + name: Install .NET/Xamarin build tools + command: ./scripts/macos-install-xamarin.sh ios - run: name: Build SDK diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml index 2a0cbae9..ccffd23f 100644 --- a/.ldrelease/config.yml +++ b/.ldrelease/config.yml @@ -4,15 +4,7 @@ repo: circleci: mac: - xcode: "11.3.0" - steps: - - step: prepare - cachePaths: - - /usr/local/Homebrew - - /usr/local/share/android-sdk - - step: build - - step: test - - step: publish + xcode: "12.4.0" windows: # only for building documentation env: LD_RELEASE_DOCS_TARGET_FRAMEWORK: netstandard2.0 diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh index 61863b2e..11412c5e 100755 --- a/.ldrelease/mac-prepare.sh +++ b/.ldrelease/mac-prepare.sh @@ -3,30 +3,8 @@ set -eu set +o pipefail -export HOMEBREW_NO_AUTO_UPDATE=1 - -brew tap isen-ng/dotnet-sdk-versions - -brew cask install \ - android-sdk \ - dotnet-sdk-2.2.400 \ - mono-mdk \ - xamarin \ - xamarin-android \ - xamarin-ios +./scripts/macos-install-xamarin.sh android ios +./scripts/macos-install-android-sdk.sh 25 26 27 +export HOMEBREW_NO_AUTO_UPDATE=1 brew install awscli - -for path_component in /Library/Frameworks/Mono.framework/Commands /usr/local/share/android-sdk/tools/bin /usr/local/share/android-sdk/platform-tools; do - echo "export PATH=\"\$PATH:$path_component\"" >> $BASH_ENV -done - -echo "export ANDROID_HOME=/usr/local/share/android-sdk" >> $BASH_ENV -echo "export ANDROID_SDK_HOME=/usr/local/share/android-sdk" >> $BASH_ENV -echo "export ANDROID_SDK_ROOT=/usr/local/share/android-sdk" >> $BASH_ENV - -source $BASH_ENV - -sudo mkdir -p /usr/local/android-sdk-linux/licenses -yes | sdkmanager "platform-tools" "platforms;android-25" "platforms;android-26" "platforms;android-27" "build-tools;26.0.2" -yes | sdkmanager --licenses diff --git a/.ldrelease/windows-build.ps1 b/.ldrelease/windows-build.ps1 index c5481658..5b34ab96 100644 --- a/.ldrelease/windows-build.ps1 +++ b/.ldrelease/windows-build.ps1 @@ -1,5 +1,7 @@ -# Different from the standard build.ps1 for .NET projects because we must use msbuild instead of dotnet build +# Different from the standard build.ps1 for .NET projects because we must use msbuild +# instead of dotnet build. We're only doing this build in order to support building +# documentation, so we only use the .NET Standard 2.0 target and don't need Xamarin. $ErrorActionPreference = "Stop" diff --git a/scripts/macos-install-android-sdk.sh b/scripts/macos-install-android-sdk.sh new file mode 100755 index 00000000..b1a4d198 --- /dev/null +++ b/scripts/macos-install-android-sdk.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Standard steps for installing the desired version(s) of the Android SDK tools on +# a MacOS system. This is used both in our CI build and in releases. +# +# This updates the path and other variables in $BASH_ENV; to get the new values, +# run "source $BASH_ENV" (not necessary in CircleCI because CircleCI starts a new +# shell for each step). + +# Usage: +# macos-install-android-sdk.sh 27 - installs Android API 27 +# macos-install-android-sdk.sh 27 28 - installs Android API 27 & 28... etc. + +set -e + +if [ -z "$1" ]; then + echo "must specify at least one Android API version" >&2 + exit 1 +fi + +ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL=https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip +ANDROID_BUILD_TOOLS_VERSION=26.0.2 + +if [ -z "$ANDROID_HOME" ]; then + export ANDROID_HOME=/usr/local/share/android-sdk + echo "export ANDROID_HOME=$ANDROID_HOME" >> $BASH_ENV +fi +if [ -z "$ANDROID_SDK_HOME" ]; then + export ANDROID_SDK_HOME=$ANDROID_HOME + echo "export ANDROID_SDK_HOME=$ANDROID_SDK_HOME" >> $BASH_ENV +fi +if [ -z "$ANDROID_SDK_ROOT" ]; then + export ANDROID_SDK_ROOT=$ANDROID_HOME + echo "export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $BASH_ENV +fi + +for addpath in \ + /usr/local/share/android-sdk/cmdline-tools/latest/bin \ + /usr/local/share/android-sdk/tools/bin \ + /usr/local/share/android-sdk/platform-tools +do + echo "export PATH=\$PATH:$addpath" >> $BASH_ENV +done +source $BASH_ENV + +# Download the core Android SDK command-line tools - we don't use Homebrew for this, +# because the version they had (as of March 2021) was out of date and incompatible +# with Java 11. +mkdir -p $ANDROID_HOME +mkdir -p $ANDROID_HOME/cmdline-tools +sdk_temp_dir=/tmp/android-sdk-download +rm -rf $sdk_temp_dir +mkdir -p $sdk_temp_dir +echo "Downloading Android tools from $ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL" +curl "$ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL" >$sdk_temp_dir/android-sdk.zip +cd $sdk_temp_dir +unzip android-sdk.zip +mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest + +sdkmanager_args="platform-tools emulator" +sdkmanager_args="$sdkmanager_args extras;intel;Hardware_Accelerated_Execution_Manager" +sdkmanager_args="$sdkmanager_args build-tools;$ANDROID_BUILD_TOOLS_VERSION" +for apiver in "$@"; do + sdkmanager_args="$sdkmanager_args platforms;android-$apiver" + sdkmanager_args="$sdkmanager_args system-images;android-$apiver;default;x86" +done + +echo "Installing Android SDK packages: $sdkmanager_args" +yes | sdkmanager $sdkmanager_args | grep -v = || true +yes | sdkmanager --licenses >/dev/null diff --git a/scripts/macos-install-xamarin.sh b/scripts/macos-install-xamarin.sh new file mode 100755 index 00000000..3771b35d --- /dev/null +++ b/scripts/macos-install-xamarin.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Standard steps for setting up Xamarin command-line build tools on a MacOS system, +# without using the interactive Visual Studio installer. This is used both in our CI +# build and in releases. +# +# This updates the path and other variables in $BASH_ENV; to get the new values, +# run "source $BASH_ENV" (not necessary in CircleCI because CircleCI starts a new +# shell for each step). + +# Formerly, we used Homebrew to install the .NET SDK, Mono, and Xamarin. This had the +# advantage of having stable package identifiers rather than download URLs. However, +# these Homebrew installers seem to be no longer maintained. So we are now downloading +# the lower-level installer packages directly from their current locations online, +# which will undoubtedly need to be updated in the future. + +# Usage: +# macos-install-xamarin.sh android - installs Xamarin.Android +# macos-install-xamarin.sh ios - installs Xamarin.iOS +# macos-install-xamarin.sh android ios - installs both + +set -e + +# The .NET SDK 5.0 installer is pinned to a specific version +DOTNET_SDK_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/de613120-9306-4867-b504-45fcc81ba1b6/2a03f18c549f52cf78f88afa44e6dc6a/dotnet-sdk-5.0.201-osx-x64.pkg + +# Currently we are also pinning the rest of the installers to specific version URLs. +# Alternately, we could use the "latest stable" mode of boots: +# boots --stable Mono +# boots --stable Xamarin.Android +# boots --stable Xamarin.iOS +# Download URLs were found here: +# https://www.mono-project.com/download/stable/ +# https://github.com/xamarin/xamarin-android +# https://github.com/xamarin/xamarin-macios +MONO_INSTALLER_URL=https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.122.macos10.xamarin.universal.pkg +XAMARIN_ANDROID_INSTALLER_URL=https://aka.ms/xamarin-android-commercial-d16-8-macos +XAMARIN_IOS_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/7b60a920-c8b1-4798-b660-ae1a7294eb6d/bbdc2a9c6705520fd0a6d04f71e5ed3e/xamarin.ios-14.2.0.12.pkg + +for addpath in \ + /Library/Frameworks/Mono.framework/Commands \ + /usr/local/share/dotnet \ + $HOME/.dotnet/tools +do + echo "export PATH=\$PATH:$addpath" >> $BASH_ENV +done +source $BASH_ENV + +# Install .NET SDK +curl -s "$DOTNET_SDK_INSTALLER_URL" >/tmp/dotnet-sdk.pkg +sudo installer -package /tmp/dotnet-sdk.pkg -target / +rm /tmp/dotnet-sdk.pkg + +# The "boots" tool is a shortcut for downloading and running package installers. Since +# it is a .NET tool, we can't install it until we've already installed .NET above. +dotnet tool install --global boots + +# Install the basic Mono tools (including msbuild) +boots --url "$MONO_INSTALLER_URL" + +for arg in "$@" +do + case "$arg" in + android) + boots --url "$XAMARIN_ANDROID_INSTALLER_URL" + ;; + ios) + boots --url "$XAMARIN_IOS_INSTALLER_URL" + ;; + *) + echo "unsupported parameter: $arg" >&2 + exit 1 + esac +done From 140afa98da81bac87612e33b91814cd4a048889f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 30 Mar 2021 16:59:06 -0700 Subject: [PATCH 329/499] prevent spurious dependencies in package build --- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index cb1e4acb..42619d12 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -15,6 +15,7 @@ latest True False + True true From 061cef750dcf08652834f317815e2e1d6c31790b Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Wed, 31 Mar 2021 23:01:56 +0000 Subject: [PATCH 330/499] Set up mysterious ibtool fix. (#108) This is a rather unexplained workaround for the iOS CI job being non-functional. I unfortunately do not have any good explanation for _why_ this works. There's some sort of :ghost: statefulness going on that I can't explain. Regardless, this does allow the `msbuild` to complete successfully and tests to run. There's still some sort of issue when actually running the tests related to `System.IO.Pipelines`, but I haven't looked into that. --- .circleci/config.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6cc806f6..433a2358 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,6 +109,18 @@ jobs: name: Build SDK command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + - run: + name: Pre-build storyboard + command: >- + /Applications/Xcode.app/Contents/Developer/usr/bin/ibtool --errors --warnings --notices --output-format xml1 --minimum-deployment-target 10.0 + --target-device iphone --target-device ipad --auto-activate-custom-fonts + --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.4.sdk + --compilation-directory /Users/distiller/project/tests/LaunchDarkly.XamarinSdk.iOS.Tests/obj/iPhoneSimulator/Debug/xamarin.ios10/ibtool + /Users/distiller/project/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard + # This is the exact ibtool command that msbuild runs to build the first storyboard. The difference is that msbuild sets some environment variables + # which cause this command to fail. By pre-running this command, some unknown state gets set up that allows future calls to ibtool to succeed. + # It is unclear where this state resides or how this works... + - run: name: Build test project command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -121,7 +133,7 @@ jobs: - run: name: Start simulator command: | - xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2 + xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-12 com.apple.CoreSimulator.SimRuntime.iOS-14-4 xcrun simctl boot xm-ios - run: From 7e697bbdf6cfcf9163bea6088c8b82c2ac1ba57f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 2 Apr 2021 18:59:52 -0700 Subject: [PATCH 331/499] use EmbedIO instead of WireMock.Net for embedded HTTP server in tests --- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 22 +- .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 54 +- .../FeatureFlagRequestorTests.cs | 21 +- .../HttpHelpers.cs | 481 ++++++++++++++++++ .../HttpHelpersTest.cs | 223 ++++++++ .../LDClientEndToEndTests.cs | 281 +++++----- .../LaunchDarkly.XamarinSdk.Tests.csproj | 3 +- .../LaunchDarkly.XamarinSdk.Tests/LogSink.cs | 14 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 27 +- .../WireMockExtensions.cs | 43 -- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 26 +- 11 files changed, 886 insertions(+), 309 deletions(-) create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index f0108d15..4c44dbbb 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -85,6 +85,9 @@ SharedTestCode\FlagChangedEventTests.cs + + SharedTestCode\HttpHelpers.cs + SharedTestCode\LDClientEndToEndTests.cs @@ -112,9 +115,6 @@ SharedTestCode\UserFlagCacheTests.cs - - SharedTestCode\WireMockExtensions.cs - @@ -148,16 +148,12 @@ - - - - - - - - - - + + + + + + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index b101b5a2..76f4c466 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,12 +1,8 @@ using System; -using System.Net.Http; -using System.Threading.Tasks; using Common.Logging; using LaunchDarkly.Client; -using WireMock.Logging; -using WireMock.Server; -using WireMock.Settings; using Xunit; +using Xunit.Abstractions; namespace LaunchDarkly.Xamarin.Tests { @@ -19,56 +15,20 @@ public BaseTest() TestUtil.ClearClient(); } - public void Dispose() + public BaseTest(ITestOutputHelper testOutput) { + LogManager.Adapter = new LogSinkFactoryAdapter(testOutput.WriteLine); TestUtil.ClearClient(); } - protected void ClearCachedFlags(User user) - { - PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key); - } - - protected void WithServer(Action a) - { - var s = MakeServer(); - try - { - a(s); - } - finally - { - s.Stop(); - } - } - - protected async Task WithServerAsync(Func a) + public void Dispose() { - var s = MakeServer(); - try - { - await a(s); - } - finally - { - s.Stop(); - } + TestUtil.ClearClient(); } - protected FluentMockServer MakeServer() + protected void ClearCachedFlags(User user) { - // currently we don't need to customize any server settings - var server = FluentMockServer.Start(); - - // Perform an initial request to make sure the server has warmed up. On Android in particular, startup - // of the very first server instance in the test run seems to be very slow, which may cause the first - // request made by unit tests to time out. - using (var client = new HttpClient()) - { - AsyncUtils.WaitSafely(() => client.GetAsync(server.Urls[0])); - } - server.ResetLogEntries(); // so the initial request doesn't interfere with test postconditions - return server; + PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index aab52c30..ee27c6d4 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using LaunchDarkly.Client; +using LaunchDarkly.Xamarin.Tests.HttpHelpers; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -27,19 +28,17 @@ public class FeatureFlagRequestorTests : BaseTest [InlineData("/basepath", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] [InlineData("/basepath/", false, "/basepath/msdk/evalx/users/", "")] [InlineData("/basepath/", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] - public async Task GetFlagsUsesCorrectUriAndMethodInGetModexAsync( + public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync( string baseUriExtraPath, bool withReasons, string expectedPathWithoutUser, string expectedQuery ) { - await WithServerAsync(async server => + using (var server = TestHttpServer.Start(Handlers.JsonResponse("{}"))) { - server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - var config = Configuration.Builder(_mobileKey) - .BaseUri(new Uri(server.GetUrl() + baseUriExtraPath)) + .BaseUri(new Uri(server.Uri.ToString() + baseUriExtraPath)) .EvaluationReasons(withReasons) .Build(); @@ -49,14 +48,14 @@ await WithServerAsync(async server => Assert.Equal(200, resp.statusCode); Assert.Equal(_allDataJson, resp.jsonResponse); - var req = server.GetLastRequest(); + var req = server.Recorder.RequireRequest(); Assert.Equal("GET", req.Method); Assert.Equal(expectedPathWithoutUser + _encodedUser, req.Path); - Assert.Equal(expectedQuery, req.RawQuery); - Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + Assert.Equal(expectedQuery, req.Query); + Assert.Equal(_mobileKey, req.Headers["Authorization"]); Assert.Null(req.Body); } - }); + } } // Report mode is currently disabled - ch47341 @@ -81,7 +80,7 @@ await WithServerAsync(async server => // Assert.Equal($"/msdk/evalx/user", req.Path); // Assert.Equal("", req.RawQuery); // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - // TestUtil.AssertJsonEquals(JToken.Parse(_userJson), TestUtil.NormalizeJsonUser(JToken.Parse(req.Body))); + // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); // } // }); //} @@ -107,7 +106,7 @@ await WithServerAsync(async server => // Assert.Equal($"/msdk/evalx/user", req.Path); // Assert.Equal("?withReasons=true", req.RawQuery); // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); - // TestUtil.AssertJsonEquals(JToken.Parse(_userJson), TestUtil.NormalizeJsonUser(JToken.Parse(req.Body))); + // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); // } // }); //} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs new file mode 100644 index 00000000..6b2ce840 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs @@ -0,0 +1,481 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using EmbedIO; +using EmbedIO.Routing; + +namespace LaunchDarkly.Xamarin.Tests.HttpHelpers +{ + /// + /// An abstraction used by implementations to hide the details of the + /// underlying HTTP server framework. + /// + public sealed class RequestContext + { + public RequestInfo RequestInfo { get; } + public CancellationToken CancellationToken { get; } + private IHttpContext InternalContext { get; } + private volatile bool _chunked; + + private RequestContext(IHttpContext context, RequestInfo requestInfo) + { + InternalContext = context; + RequestInfo = requestInfo; + CancellationToken = context.CancellationToken; + } + + internal static async Task FromHttpContext(IHttpContext ctx) + { + var requestInfo = new RequestInfo + { + Method = ctx.Request.HttpMethod, + Path = ctx.RequestedPath, + Query = ctx.Request.Url.Query, + Headers = ctx.Request.Headers + }; + + if (ctx.Request.HasEntityBody) + { + requestInfo.Body = await ctx.GetRequestBodyAsStringAsync(); + } + return new RequestContext(ctx, requestInfo); + } + + /// + /// Sets the response status. + /// + /// the HTTP status + public void SetStatus(int statusCode) => InternalContext.Response.StatusCode = statusCode; + + /// + /// Sets a response header. + /// + /// the header name + /// the header value + public void SetHeader(string name, string value) + { + if (name.ToLower() == "content-type") + { + InternalContext.Response.ContentType = value; + } + else + { + InternalContext.Response.Headers.Set(name, value); + } + } + + /// + /// Adds a response header, allowing multiple values. + /// + /// the header name + /// the header value + public void AddHeader(string name, string value) + { + if (name.ToLower() == "content-type") + { + InternalContext.Response.ContentType = value; + } + else + { + InternalContext.Response.Headers.Add(name, value); + } + } + + /// + /// Writes a chunk of data in a chunked response. + /// + public async Task WriteChunkedDataAsync(byte[] data) + { + if (!_chunked) + { + _chunked = true; + InternalContext.Response.SendChunked = true; + } + await InternalContext.Response.OutputStream.WriteAsync(data, 0, data.Length); + } + + /// + /// Writes a complete response body. + /// + /// the Content-Type header value + /// the data + /// the asynchronous task + public async Task WriteFullResponseAsync(string contentType, byte[] data) + { + InternalContext.Response.ContentLength64 = data.Length; + InternalContext.Response.ContentType = contentType; + await InternalContext.Response.OutputStream.WriteAsync(data, 0, data.Length, + InternalContext.CancellationToken); + } + } + + /// + /// Properties of a request received by a . + /// + public struct RequestInfo + { + public string Method { get; set; } + public string Path { get; set; } + public string Query { get; set; } + public NameValueCollection Headers { get; set; } + public string Body { get; set; } + } + + /// + /// An asynchronous function that handles HTTP requests to a . + /// + /// + /// Use the factory methods in to create standard implementations. + /// + /// the request context + /// the asynchronous task + public delegate Task Handler(RequestContext context); + + /// + /// A simplified system for setting up embedded test HTTP servers. + /// + /// + /// + /// This abstraction is designed to allow writing test code that does not need to know anything + /// about the underlying implementation details of the HTTP framework, so that if a different + /// library needs to be used for compatibility reasons, it can be substituted without disrupting + /// the tests. + /// + /// + /// + /// // Start a server that returns a 200 status for all requests + /// using (var server = StartServer(Handlers.Status(200))) + /// { + /// DoSomethingThatMakesARequestTo(server.Uri); + /// + /// var req = server.RequireRequest(); + /// // Check for expected properties of the request + /// } + /// + /// + /// + public sealed class TestHttpServer : IDisposable + { + private static int nextPort = 10000; + + /// + /// The base URI of the server. + /// + public Uri Uri { get; } + + /// + /// Returns the that captures all requests to this server. + /// + public RequestRecorder Recorder => _baseHandler; + + private readonly WebServer _server; + private readonly RequestRecorder _baseHandler; + + private TestHttpServer(WebServer server, RequestRecorder baseHandler, Uri uri) + { + _server = server; + _baseHandler = baseHandler; + Uri = uri; + } + + /// + /// Shuts down the server. + /// + public void Dispose() => _server.Dispose(); + + /// + /// Starts a new test server. + /// + /// + /// Make sure to close this when done, by calling Dispose or with a using + /// statement. + /// + /// A function that will handle all requests to this server. Use + /// the factory methods in for standard handlers. If you will need + /// to change the behavior of the handler during the lifetime of the server, use + /// . + /// + public static TestHttpServer Start(Handler handler) + { + var recorder = Handlers.RecordAndDelegateTo(handler); + var server = StartWebServerOnAvailablePort(out var uri, recorder); + return new TestHttpServer(server, recorder, uri); + } + + private static WebServer StartWebServerOnAvailablePort(out Uri serverUri, Handler handler) + { + while (true) + { + var port = nextPort++; + var options = new WebServerOptions() + .WithUrlPrefix($"http://*:{port}") + .WithMode(HttpListenerMode.Microsoft); + var server = new WebServer(options); + server.Listener.IgnoreWriteExceptions = true; + server.OnAny("/", async internalContext => + { + var ctx = await RequestContext.FromHttpContext(internalContext); + await handler(ctx); + }); + try + { + _ = server.RunAsync(); + } + catch (HttpListenerException) + { + continue; + } + serverUri = new Uri(string.Format("http://localhost:{0}", port)); + return server; + } + } + } + + /// + /// Factory methods for standard implementations. + /// + public static class Handlers + { + /// + /// Creates a that sleeps for the specified amount of time + /// before passing the request to the target handler. + /// + /// how long to delay + /// the handler to call after the delay + /// a + public static Handler DelayBefore(TimeSpan delay, Handler target) => + async ctx => + { + await Task.Delay(delay, ctx.CancellationToken); + if (!ctx.CancellationToken.IsCancellationRequested) + { + await target(ctx); + } + }; + + /// + /// Creates a that sends a 200 response with a JSON content type. + /// + /// the JSON data + /// additional headers (may be null) + /// + public static Handler JsonResponse( + string jsonBody, + NameValueCollection headers = null + ) => + StringResponse(200, headers, "application/json", jsonBody); + + /// + /// Creates a that captures requests while delegating to + /// another handler + /// + /// the handler to delegate to + /// a + public static RequestRecorder RecordAndDelegateTo(Handler target) => + new RequestRecorder(target); + + /// + /// Creates a that always sends the same response, + /// specifying the response body (if any) as a byte array. + /// + /// the HTTP status code + /// response headers (may be null) + /// response content type (used only if body is not null) + /// response body (may be null) + /// + public static Handler Response( + int statusCode, + NameValueCollection headers, + string contentType = null, + byte[] body = null + ) => + async ctx => + { + ctx.SetStatus(statusCode); + if (headers != null) + { + foreach (var k in headers.AllKeys) + { + foreach (var v in headers.GetValues(k)) + { + ctx.AddHeader(k, v); + } + } + } + if (body != null) + { + await ctx.WriteFullResponseAsync(contentType, body); + } + }; + + /// + /// Creates a that always sends the same response, + /// specifying the response body (if any) as a string. + /// + /// the HTTP status code + /// response headers (may be null) + /// response content type (used only if body is not null) + /// response body (may be null) + /// response encoding (defaults to UTF8) + /// + public static Handler StringResponse( + int statusCode, + NameValueCollection headers, + string contentType = null, + string body = null, + Encoding encoding = null + ) => + Response( + statusCode, + headers, + contentType is null || contentType.Contains("charset=") ? contentType : + contentType + "; charset=" + (encoding ?? Encoding.UTF8).WebName, + body == null ? null : (encoding ?? Encoding.UTF8).GetBytes(body) + ); + + /// + /// Creates a that always returns the specified HTTP + /// response status, with no custom headers and no body. + /// + /// + /// a + public static Handler Status(int statusCode) => + Sync(ctx => ctx.SetStatus(statusCode)); + + /// + /// Creates a for changing handler behavior + /// dynamically. + /// + /// the initial to delegate to + /// a + public static HandlerDelegator DelegateTo(Handler target) => + new HandlerDelegator(target); + + /// + /// Wraps a synchronous action in an asynchronous . + /// + /// the action to run + /// a + public static Handler Sync(Action action) => + ctx => + { + action(ctx); + return Task.CompletedTask; + }; + } + + /// + /// A delegator that forwards requests to another handler, which can be changed at any time. + /// + /// + /// There is an implicit conversion allowing a HandlerDelegator to be used as a + /// . + /// + public sealed class HandlerDelegator + { + private volatile Handler _target; + + /// + /// The handler that will actually handle the request. + /// + public Handler Target + { + get => _target; + set + { + _target = value; + } + } + + /// + /// Returns the stable that is the external entry point to this + /// delegator. This is used implicitly if you use a HandlerDelegator anywhere that + /// a is expected. + /// + public Handler Handler => ctx => _target(ctx); + + internal HandlerDelegator(Handler target) + { + _target = target; + } + + public static implicit operator Handler(HandlerDelegator me) => me.Handler; + } + + /// + /// An object that delegates requests to another handler while recording all requests. + /// + /// + /// Normally you won't need to use this class directly, because + /// has a built-in instance that captures all requests. You can use it if you need to + /// capture only a subset of requests. + /// + public class RequestRecorder + { + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + private readonly BlockingCollection _requests = new BlockingCollection(); + private readonly Handler _target; + + /// + /// Returns the stable that is the external entry point to this + /// delegator. This is used implicitly if you use a RequestRecorder anywhere that + /// a is expected. + /// + public Handler Handler => DoRequestAsync; + + /// + /// The number of requests currently in the queue. + /// + public int Count => _requests.Count; + + internal RequestRecorder(Handler target) + { + _target = target; + } + + /// + /// Consumes and returns the first request in the queue, blocking until one is available. + /// Throws an exception if the timeout expires. + /// + /// the maximum length of time to wait + /// the request information + public RequestInfo RequireRequest(TimeSpan timeout) + { + if (!_requests.TryTake(out var req, timeout)) + { + throw new TimeoutException("timed out waiting for request"); + } + return req; + } + + /// + /// Returns the first request in the queue, blocking until one is available, + /// using . + /// + /// the request information + public RequestInfo RequireRequest() => RequireRequest(DefaultTimeout); + + public void RequireNoRequests(TimeSpan timeout) + { + if (_requests.TryTake(out var _, timeout)) + { + throw new Exception("received an unexpected request"); + } + } + + private async Task DoRequestAsync(RequestContext ctx) + { + _requests.Add(ctx.RequestInfo); + await _target(ctx); + } + + public static implicit operator Handler(RequestRecorder me) => me.Handler; + } +} \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs new file mode 100644 index 00000000..277c3f48 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Specialized; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace LaunchDarkly.Xamarin.Tests.HttpHelpers +{ + public class HttpHelpersTest + { + [Fact] + public async Task ServerWithSimpleStatusHandler() + { + await WithServerAndClient(Handlers.Status(419), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(419, (int)resp.StatusCode); + }); + } + + [Fact] + public async Task ServerCapturesRequestWithoutBody() + { + await WithServerAndClient(Handlers.Status(200), async (server, client) => + { + var req = new HttpRequestMessage(HttpMethod.Get, server.Uri.ToString() + "request/path?a=b"); + req.Headers.Add("header-name", "header-value"); + + var resp = await client.SendAsync(req); + Assert.Equal(200, (int)resp.StatusCode); + + var received = server.Recorder.RequireRequest(); + Assert.Equal("GET", received.Method); + Assert.Equal("/request/path", received.Path); + Assert.Equal("?a=b", received.Query); + Assert.Equal("header-value", received.Headers["header-name"]); + Assert.Null(received.Body); + }); + } + + [Fact] + public async Task ServerCapturesRequestWithBody() + { + await WithServerAndClient(Handlers.Status(200), async (server, client) => + { + var req = new HttpRequestMessage(HttpMethod.Post, server.Uri.ToString() + "request/path"); + req.Content = new StringContent("hello", Encoding.UTF8, "text/plain"); + + var resp = await client.SendAsync(req); + Assert.Equal(200, (int)resp.StatusCode); + + var received = server.Recorder.RequireRequest(); + Assert.Equal("POST", received.Method); + Assert.Equal("/request/path", received.Path); + Assert.Equal("hello", received.Body); + }); + } + + [Fact] + public async Task CustomAsyncHandler() + { + Handler handler = async ctx => + { + await ctx.WriteFullResponseAsync("text/plain", Encoding.UTF8.GetBytes("hello")); + }; + await WithServerAndClient(handler, async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal("hello", await resp.Content.ReadAsStringAsync()); + }); + } + + [Fact] + public async Task CustomSyncHandler() + { + Action action = ctx => + { + ctx.SetStatus(419); + }; + await WithServerAndClient(Handlers.Sync(action), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(419, (int)resp.StatusCode); + }); + } + + [Fact] + public async Task ResponseHandlerWithoutBody() + { + var headers = new NameValueCollection(); + headers.Add("header-name", "header-value"); + await WithServerAndClient(Handlers.Response(419, headers), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(419, (int)resp.StatusCode); + Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); + Assert.Equal("", await resp.Content.ReadAsStringAsync()); + }); + } + + [Fact] + public async Task ResponseHandlerWithBody() + { + var headers = new NameValueCollection(); + headers.Add("header-name", "header-value"); + byte[] data = new byte[] { 1, 2, 3 }; + await WithServerAndClient(Handlers.Response(200, headers, "weird", data), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(200, (int)resp.StatusCode); + Assert.Equal("weird", resp.Content.Headers.GetValues("content-type").First()); + Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); + Assert.Equal(data, await resp.Content.ReadAsByteArrayAsync()); + }); + } + + [Fact] + public async Task StringResponseHandlerWithBody() + { + var headers = new NameValueCollection(); + headers.Add("header-name", "header-value"); + string body = "hello"; + await WithServerAndClient(Handlers.StringResponse(200, headers, "weird", body), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(200, (int)resp.StatusCode); + Assert.Equal("weird; charset=utf-8", resp.Content.Headers.GetValues("content-type").First()); + Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); + Assert.Equal(body, await resp.Content.ReadAsStringAsync()); + }); + } + + [Fact] + public async Task JsonResponseHandler() + { + await WithServerAndClient(Handlers.JsonResponse("true"), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(200, (int)resp.StatusCode); + Assert.Equal("application/json; charset=utf-8", resp.Content.Headers.ContentType.ToString()); + Assert.Equal("true", await resp.Content.ReadAsStringAsync()); + }); + } + + [Fact] + public async Task JsonResponseHandlerWithHeaders() + { + var headers = new NameValueCollection(); + headers.Add("header-name", "header-value"); + await WithServerAndClient(Handlers.JsonResponse("true", headers), async (server, client) => + { + var resp = await client.GetAsync(server.Uri); + Assert.Equal(200, (int)resp.StatusCode); + Assert.Equal("application/json; charset=utf-8", resp.Content.Headers.ContentType.ToString()); + Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); + Assert.Equal("true", await resp.Content.ReadAsStringAsync()); + }); + } + + [Fact] + public async Task SwitchableDelegatorHandler() + { + var switchable = Handlers.DelegateTo(Handlers.Status(200)); + await WithServerAndClient(switchable, async (server, client) => + { + var resp1 = await client.GetAsync(server.Uri); + Assert.Equal(200, (int)resp1.StatusCode); + + switchable.Target = Handlers.Status(400); + + var resp2 = await client.GetAsync(server.Uri); + Assert.Equal(400, (int)resp2.StatusCode); + }); + } + + [Fact] + public async Task ChunkedResponse() + { + Handler handler = async ctx => + { + ctx.SetHeader("Content-Type", "text/plain; charset=utf-8"); + await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("chunk1,")); + await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("chunk2")); + await Task.Delay(Timeout.Infinite, ctx.CancellationToken); + }; + var expected = "chunk1,chunk2"; + await WithServerAndClient(handler, async (server, client) => + { + var resp = await client.GetAsync(server.Uri, HttpCompletionOption.ResponseHeadersRead); + Assert.Equal(200, (int)resp.StatusCode); + Assert.Equal("text/plain; charset=utf-8", resp.Content.Headers.ContentType.ToString()); + var stream = await resp.Content.ReadAsStreamAsync(); + var received = new StringBuilder(); + while (true) + { + var buf = new byte[100]; + int n = await stream.ReadAsync(buf, 0, buf.Length); + received.Append(Encoding.UTF8.GetString(buf, 0, n)); + if (received.Length >= expected.Length) + { + Assert.Equal(expected, received.ToString()); + break; + } + } + }); + } + + private static async Task WithServerAndClient(Handler handler, Func action) + { + using (var server = TestHttpServer.Start(handler)) + { + using (var client = new HttpClient()) + { + await action(server, client); + } + } + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index ed7d2bca..f0a435ba 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -1,18 +1,15 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; +using System.Text; using System.Threading; using System.Threading.Tasks; using Common.Logging; using LaunchDarkly.Client; using LaunchDarkly.Xamarin.PlatformSpecific; -using WireMock; -using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; -using WireMock.Server; +using LaunchDarkly.Xamarin.Tests.HttpHelpers; using Xunit; +using Xunit.Abstractions; namespace LaunchDarkly.Xamarin.Tests { @@ -44,49 +41,46 @@ public class LdClientEndToEndTests : BaseTest { new object[] { UpdateMode.Streaming } } }; + public LdClientEndToEndTests(ITestOutputHelper testOutput) : base(testOutput) { } + [Theory] [MemberData(nameof(PollingAndStreaming))] public void InitGetsFlagsSync(UpdateMode mode) { - WithServer(server => + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) { - SetupResponse(server, _flagData1, mode); - - var config = BaseConfig(server, mode); - using (var client = TestUtil.CreateClient(config, _user)) + var config = BaseConfig(server.Uri, mode); + using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(10))) { - VerifyRequest(server, mode); + VerifyRequest(server.Recorder, mode); VerifyFlagValues(client, _flagData1); } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task InitGetsFlagsAsync(UpdateMode mode) { - await WithServerAsync(async server => + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) { - SetupResponse(server, _flagData1, mode); - - var config = BaseConfig(server, mode); + var config = BaseConfig(server.Uri, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) { - VerifyRequest(server, mode); + VerifyRequest(server.Recorder, mode); } - }); + } } [Fact] public void InitCanTimeOutSync() { - WithServer(server => + var handler = Handlers.DelayBefore(TimeSpan.FromSeconds(2), SetupResponse(_flagData1, UpdateMode.Polling)); + using (var server = TestHttpServer.Start(handler)) { - server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - using (var log = new LogSinkScope()) { - var config = BaseConfig(server, builder => builder.IsStreamingEnabled(false)); + var config = BaseConfig(server.Uri, builder => builder.IsStreamingEnabled(false)); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized); @@ -95,39 +89,35 @@ public void InitCanTimeOutSync() m.Text == "Client did not successfully initialize within 200 milliseconds."); } } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public void InitFailsOn401Sync(UpdateMode mode) { - WithServer(server => + using (var server = TestHttpServer.Start(Handlers.Status(401))) { - server.ForAllRequests(r => r.WithStatusCode(401)); - using (var log = new LogSinkScope()) { - var config = BaseConfig(server, mode); - using (var client = TestUtil.CreateClient(config, _user)) + var config = BaseConfig(server.Uri, mode); + using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(10))) { Assert.False(client.Initialized); } } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task InitFailsOn401Async(UpdateMode mode) { - await WithServerAsync(async server => + using (var server = TestHttpServer.Start(Handlers.Status(401))) { - server.ForAllRequests(r => r.WithStatusCode(401)); - using (var log = new LogSinkScope()) { - var config = BaseConfig(server, mode); + var config = BaseConfig(server.Uri, mode); // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that @@ -137,18 +127,16 @@ await WithServerAsync(async server => Assert.False(client.Initialized); } } - }); + } } [Fact] public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() { // Note, we don't care about polling mode vs. streaming mode for this functionality. - await WithServerAsync(async server => + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); - - var config = BaseConfig(server, UpdateMode.Polling); + var config = BaseConfig(server.Uri, UpdateMode.Polling); var name = "Sue"; var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); @@ -168,93 +156,87 @@ await WithServerAsync(async server => { Assert.Equal(generatedKey, client.User.Key); } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { - WithServer(server => + var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); + using (var server = TestHttpServer.Start(switchable)) { - SetupResponse(server, _flagData1, mode); - - var config = BaseConfig(server, mode); + var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) { - VerifyRequest(server, mode); + var req1 = VerifyRequest(server.Recorder, mode); VerifyFlagValues(client, _flagData1); - var user1RequestPath = server.GetLastRequest().Path; + var user1RequestPath = req1.Path; - server.Reset(); - SetupResponse(server, _flagData2, mode); + switchable.Target = SetupResponse(_flagData2, mode); var success = client.Identify(_otherUser, TimeSpan.FromSeconds(5)); Assert.True(success); Assert.True(client.Initialized); Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes - VerifyRequest(server, mode); - Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); + var req2 = VerifyRequest(server.Recorder, mode); + Assert.NotEqual(user1RequestPath, req2.Path); VerifyFlagValues(client, _flagData2); } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) { - await WithServerAsync(async server => + var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); + using (var server = TestHttpServer.Start(switchable)) { - SetupResponse(server, _flagData1, mode); - - var config = BaseConfig(server, mode); + var config = BaseConfig(server.Uri, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) { - VerifyRequest(server, mode); + var req1 = VerifyRequest(server.Recorder, mode); VerifyFlagValues(client, _flagData1); - var user1RequestPath = server.GetLastRequest().Path; + var user1RequestPath = req1.Path; - server.Reset(); - SetupResponse(server, _flagData2, mode); + switchable.Target = SetupResponse(_flagData2, mode); var success = await client.IdentifyAsync(_otherUser); Assert.True(success); Assert.True(client.Initialized); Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes - VerifyRequest(server, mode); - Assert.NotEqual(user1RequestPath, server.GetLastRequest().Path); + var req2 = VerifyRequest(server.Recorder, mode); + Assert.NotEqual(user1RequestPath, req2.Path); VerifyFlagValues(client, _flagData2); } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public void IdentifyCanTimeOutSync(UpdateMode mode) { - WithServer(server => + var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); + using (var server = TestHttpServer.Start(switchable)) { - SetupResponse(server, _flagData1, mode); - - var config = BaseConfig(server, mode); + var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) { - VerifyRequest(server, mode); + var req1 = VerifyRequest(server.Recorder, mode); VerifyFlagValues(client, _flagData1); - var user1RequestPath = server.GetLastRequest().Path; - server.Reset(); - server.ForAllRequests(r => r.WithDelay(TimeSpan.FromSeconds(2)).WithJsonBody(PollingData(_flagData1))); + switchable.Target = Handlers.DelayBefore(TimeSpan.FromSeconds(2), + SetupResponse(_flagData1, mode)); var success = client.Identify(_otherUser, TimeSpan.FromMilliseconds(100)); Assert.False(success); Assert.False(client.Initialized); Assert.Null(client.StringVariation(_flagData1.First().Key, null)); } - }); + } } [Theory] @@ -266,54 +248,43 @@ public void EventsAreSentToCorrectEndpointAsync( string expectedPath ) { - var requests = new BlockingCollection(); - var gotRequest = new EventWaitHandle(false, EventResetMode.ManualReset); - WithServer(server => + using (var server = TestHttpServer.Start(Handlers.Status(202))) { - server.ForAllRequests(r => r.WithCallback(req => - { - requests.Add(req); - return new ResponseMessage() { StatusCode = 202 }; - })); - var config = Configuration.BuilderInternal(_mobileKey) .UpdateProcessorFactory(MockPollingProcessor.Factory("{}")) - .EventsUri(new Uri(server.Urls[0] + baseUriExtraPath)) + .EventsUri(new Uri(server.Uri.ToString() + baseUriExtraPath)) .PersistFlagValues(false) .Build(); using (var client = TestUtil.CreateClient(config, _user)) { client.Flush(); - Assert.True(requests.TryTake(out var request, TimeSpan.FromSeconds(5))); + var req = server.Recorder.RequireRequest(TimeSpan.FromSeconds(5)); - Assert.Equal("POST", request.Method); - Assert.Equal(expectedPath, request.Path); + Assert.Equal("POST", req.Method); + Assert.Equal(expectedPath, req.Path); + Assert.Equal(LdValueType.Array, LdValue.Parse(req.Body).Type); } - }); + } } [Fact] public void OfflineClientUsesCachedFlagsSync() { - WithServer(server => + // streaming vs. polling should make no difference for this + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - ClearCachedFlags(_user); try { - var config = BaseConfig(server, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); + var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); } // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. - - server.Reset(); // the offline client shouldn't be making any requests, but just in case + // We'll now start over in offline mode, and we should still see the earlier flag values. var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); using (var client = TestUtil.CreateClient(offlineConfig, _user)) { @@ -324,30 +295,25 @@ public void OfflineClientUsesCachedFlagsSync() { ClearCachedFlags(_user); } - }); + } } [Fact] public async Task OfflineClientUsesCachedFlagsAsync() { - await WithServerAsync(async server => + // streaming vs. polling should make no difference for this + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - SetupResponse(server, _flagData1, UpdateMode.Polling); // streaming vs. polling should make no difference for this - ClearCachedFlags(_user); try { - var config = BaseConfig(server, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); + var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); } // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over, but with a server that doesn't respond immediately. When the client times - // out, we should still see the earlier flag values. - - server.Reset(); // the offline client shouldn't be making any requests, but just in case var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { @@ -358,7 +324,7 @@ await WithServerAsync(async server => { ClearCachedFlags(_user); } - }); + } } [Fact] @@ -368,15 +334,15 @@ public async Task BackgroundModeForcesPollingAsync() var backgroundInterval = TimeSpan.FromMilliseconds(50); ClearCachedFlags(_user); - await WithServerAsync(async server => + + var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, UpdateMode.Streaming)); + using (var server = TestHttpServer.Start(switchable)) { - var config = BaseConfig(server, UpdateMode.Streaming, builder => builder + var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder .BackgroundModeManager(mockBackgroundModeManager) .BackgroundPollingIntervalWithoutMinimum(backgroundInterval) .PersistFlagValues(false)); - SetupResponse(server, _flagData1, UpdateMode.Streaming); - using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -384,7 +350,7 @@ await WithServerAsync(async server => // Set it up so that when the client switches to background mode and does a polling request, it will // receive _flagData2, and we will be notified of that via a change event. SetupResponse will only // configure the polling endpoint, so if the client makes a streaming request here it'll fail. - SetupResponse(server, _flagData2, UpdateMode.Polling); + switchable.Target = SetupResponse(_flagData2, UpdateMode.Polling); var receivedChangeSignal = new SemaphoreSlim(0, 1); client.FlagChanged += (sender, args) => { @@ -397,13 +363,13 @@ await WithServerAsync(async server => VerifyFlagValues(client, _flagData2); // Now switch back to streaming - SetupResponse(server, _flagData1, UpdateMode.Streaming); + switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); await receivedChangeSignal.WaitAsync(); VerifyFlagValues(client, _flagData1); } - }); + } } [Fact] @@ -414,23 +380,22 @@ public async Task BackgroundModePollingCanBeDisabledAsync() var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); ClearCachedFlags(_user); - await WithServerAsync(async server => + var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, UpdateMode.Streaming)); + using (var server = TestHttpServer.Start(switchable)) { - var config = BaseConfig(server, UpdateMode.Streaming, builder => builder + var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder .BackgroundModeManager(mockBackgroundModeManager) .EnableBackgroundUpdating(false) .BackgroundPollingInterval(backgroundInterval) .PersistFlagValues(false)); - SetupResponse(server, _flagData1, UpdateMode.Streaming); - using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); // The SDK should *not* hit this polling endpoint, but we're providing some data there so we can // detect whether it does. - SetupResponse(server, _flagData2, UpdateMode.Polling); + switchable.Target = SetupResponse(_flagData2, UpdateMode.Polling); mockBackgroundModeManager.UpdateBackgroundMode(true); await Task.Delay(hackyUpdateDelay); @@ -443,34 +408,33 @@ await WithServerAsync(async server => }; // Now switch back to streaming - SetupResponse(server, _flagData2, UpdateMode.Streaming); + switchable.Target = SetupResponse(_flagData2, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); await receivedChangeSignal.WaitAsync(); VerifyFlagValues(client, _flagData2); } - }); + } } [Theory] [MemberData(nameof(PollingAndStreaming))] public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) { - await WithServerAsync(async server => + using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) { ClearCachedFlags(_user); - var config = BaseConfig(server, mode, builder => builder.Offline(true).PersistFlagValues(false)); + var config = BaseConfig(server.Uri, mode, builder => builder.Offline(true).PersistFlagValues(false)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyNoFlagValues(client, _flagData1); - - SetupResponse(server, _flagData1, mode); + Assert.Equal(0, server.Recorder.Count); await client.SetOfflineAsync(false); VerifyFlagValues(client, _flagData1); } - }); + } } [Theory] @@ -481,71 +445,64 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) // definitely don't want that. Verify that we're disabling that behavior when we parse flags. const string dateLikeString1 = "1970-01-01T00:00:01.001Z"; const string dateLikeString2 = "1970-01-01T00:00:01Z"; - WithServer(server => - { - var flagData = new Dictionary + var flagData = new Dictionary { { "flag1", dateLikeString1 }, { "flag2", dateLikeString2 } }; - SetupResponse(server, flagData, mode); - - var config = BaseConfig(server, mode); + using (var server = TestHttpServer.Start(SetupResponse(flagData, mode))) + { + var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, flagData); } - }); + } } - private Configuration BaseConfig(FluentMockServer server, Func extraConfig = null) + private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) { var builderInternal = Configuration.BuilderInternal(_mobileKey) .EventProcessor(new MockEventProcessor()); builderInternal - .BaseUri(new Uri(server.GetUrl())) - .StreamUri(new Uri(server.GetUrl())) + .BaseUri(serverUri) + .StreamUri(serverUri) .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination var builder = extraConfig == null ? builderInternal : extraConfig(builderInternal); return builder.Build(); } - private Configuration BaseConfig(FluentMockServer server, UpdateMode mode, Func extraConfig = null) + private Configuration BaseConfig(Uri serverUri, UpdateMode mode, Func extraConfig = null) { - return BaseConfig(server, builder => + return BaseConfig(serverUri, builder => { builder.IsStreamingEnabled(mode.IsStreaming); return extraConfig == null ? builder : extraConfig(builder); }); } - // - private SemaphoreSlim SetupResponse(FluentMockServer server, IDictionary data, UpdateMode mode) + private Handler SetupResponse(IDictionary data, UpdateMode mode) { - var signal = new SemaphoreSlim(0, 1); - server.ResetMappings(); - var resp = Response.Create().WithCallback(req => + var body = mode.IsStreaming ? StreamingData(data) : PollingData(data); + return async ctx => { - signal.Release(); - var respBuilder = mode.IsStreaming ? - Response.Create().WithEventsBody(StreamingData(data)) : - Response.Create().WithJsonBody(PollingData(data)); - return ((Response)respBuilder).ResponseMessage; - }); - - // Note: in streaming mode, since WireMock.Net doesn't seem to support streaming responses, the fake response will close - // after the end of the data-- so the SDK will enter retry mode and we may get another identical streaming request. For - // the purposes of these tests, that doesn't matter. The correct processing of a chunked stream is tested in the - // LaunchDarkly.EventSource tests, and the retry logic is tested in LaunchDarkly.CommonSdk. - - server.Given(Request.Create().WithPath(path => Regex.IsMatch(path, mode.FlagsPathRegex))) - .RespondWith(resp); - return signal; + if (mode.IsStreaming) + { + ctx.SetHeader("Content-Type", "text/event-stream"); + var bytes = Encoding.UTF8.GetBytes(body); + await ctx.WriteChunkedDataAsync(bytes); + await Task.Delay(Timeout.Infinite, ctx.CancellationToken); + } + else + { + await Handlers.JsonResponse(body)(ctx); + } + }; } - private void VerifyRequest(FluentMockServer server, UpdateMode mode) + private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) { - var req = server.GetLastRequest(); + var req = recorder.RequireRequest(TimeSpan.FromSeconds(5)); Assert.Equal("GET", req.Method); // Note, we don't check for an exact match of the encoded user string in Req.Path because it is not determinate - the @@ -554,9 +511,11 @@ private void VerifyRequest(FluentMockServer server, UpdateMode mode) // because it is already covered in FeatureFlagRequestorTest. Assert.Matches(mode.FlagsPathRegex, req.Path); - Assert.Equal("", req.RawQuery); - Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + Assert.Equal("", req.Query); + Assert.Equal(_mobileKey, req.Headers["Authorization"]); Assert.Null(req.Body); + + return req; } private void VerifyFlagValues(ILdClient client, IDictionary flags) @@ -577,7 +536,7 @@ private void VerifyNoFlagValues(ILdClient client, IDictionary fl } } - private LdValue FlagJson(string key, string value) + private static LdValue FlagJson(string key, string value) { return LdValue.ObjectFrom(new Dictionary { @@ -586,7 +545,7 @@ private LdValue FlagJson(string key, string value) }); } - private string PollingData(IDictionary flags) + private static string PollingData(IDictionary flags) { var d = new Dictionary(); foreach (var e in flags) @@ -596,7 +555,7 @@ private string PollingData(IDictionary flags) return LdValue.ObjectFrom(d).ToJsonString(); } - private string StreamingData(IDictionary flags) + private static string StreamingData(IDictionary flags) { return "event: put\ndata: " + PollingData(flags) + "\n\n"; } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 71d4d20f..679c7551 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -9,9 +9,8 @@ - - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs index 5cee4c8d..db04d43a 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs @@ -23,6 +23,11 @@ public LogSink() : base("", LogLevel.All, false, false, false, "") {} protected override void WriteInternal(LogLevel level, object message, Exception exception) { var str = message?.ToString(); + try + { + LogSinkFactoryAdapter._logFn?.Invoke(DateTime.Now.ToString() + " [" + level + "] " + str); + } + catch { } if (_showLogs) { Console.WriteLine("*** LOG: [" + level + "] " + str); @@ -39,7 +44,14 @@ public struct LogItem public class LogSinkFactoryAdapter : AbstractSimpleLoggerFactoryAdapter { - public LogSinkFactoryAdapter() : base(null) {} + public static Action _logFn; + + public LogSinkFactoryAdapter() : this(null) { } + + public LogSinkFactoryAdapter(Action logFn) : base(null) + { + _logFn = logFn; + } protected override ILog CreateLogger(string name, LogLevel level, bool showLevel, bool showDateTime, bool showLogName, string dateTimeFormat) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 96051089..2e945dea 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Client; -using Newtonsoft.Json.Linq; using Xunit; namespace LaunchDarkly.Xamarin.Tests @@ -148,24 +146,27 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe .DeviceInfo(new MockDeviceInfo()); } - public static void AssertJsonEquals(JToken expected, JToken actual) + public static void AssertJsonEquals(LdValue expected, LdValue actual) { - if (!JToken.DeepEquals(expected, actual)) - { - Assert.Equal(expected.ToString(), actual.ToString()); // will print the values with the failure - } + Assert.Equal(expected, actual); } - public static JToken NormalizeJsonUser(JToken json) + public static LdValue NormalizeJsonUser(LdValue json) { // It's undefined whether a user with no custom attributes will have "custom":{} or not - if (json is JObject o && o.ContainsKey("custom") && o["custom"] is JObject co) + if (json.Type == LdValueType.Object && json.Get("custom").Type == LdValueType.Object) { - if (co.Count == 0) + if (json.Count == 0) { - JObject o1 = new JObject(o); - o1.Remove("custom"); - return o1; + var o1 = LdValue.BuildObject(); + foreach (var kv in json.AsDictionary(LdValue.Convert.Json)) + { + if (kv.Key != "custom") + { + o1.Add(kv.Key, kv.Value); + } + } + return o1.Build(); } } return json; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs b/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs deleted file mode 100644 index 7e91eb7d..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/WireMockExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using WireMock; -using WireMock.Logging; -using WireMock.RequestBuilders; -using WireMock.ResponseBuilders; -using WireMock.Server; - -namespace LaunchDarkly.Xamarin.Tests -{ - // Convenience methods to streamline the test code's usage of WireMock. - public static class WireMockExtensions - { - public static string GetUrl(this FluentMockServer server) - { - return server.Urls[0]; - } - - public static FluentMockServer ForAllRequests(this FluentMockServer server, Func builderFn) - { - server.Given(Request.Create()).RespondWith(builderFn(Response.Create().WithStatusCode(200))); - return server; - } - - public static IResponseBuilder WithJsonBody(this IResponseBuilder resp, string body) - { - return resp.WithBody(body).WithHeader("Content-Type", "application/json"); - } - - public static IResponseBuilder WithEventsBody(this IResponseBuilder resp, string body) - { - return resp.WithBody(body).WithHeader("Content-Type", "text/event-stream"); - } - - public static RequestMessage GetLastRequest(this FluentMockServer server) - { - foreach (LogEntry le in server.LogEntries) - { - return le.RequestMessage; - } - throw new InvalidOperationException("Did not receive a request"); - } - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 5af64397..2d1d17e9 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -69,21 +69,11 @@ - - 3.4.1 - - - 2.5.25 - - - 2.4.1 - - - 4.0.0.497661 - - - 1.0.22 - + + + + + @@ -163,6 +153,9 @@ SharedTestCode\FlagChangedEventTests.cs + + SharedTestCode\HttpHelpers.cs + SharedTestCode\LdClientEndToEndTests.cs @@ -190,9 +183,6 @@ SharedTestCode\UserFlagCacheTests.cs - - SharedTestCode\WireMockExtensions.cs - From 846e2df7ed5fad258044937e77ba57809ee4b916 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 5 Apr 2021 15:30:28 -0700 Subject: [PATCH 332/499] support ping message used by Relay stream endpoint --- src/LaunchDarkly.XamarinSdk/Factory.cs | 4 +-- .../MobileStreamingProcessor.cs | 26 ++++++++++++++++++- .../LDClientEndToEndTests.cs | 25 ++++++++++++++++++ .../MobileStreamingProcessorTests.cs | 3 ++- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index c8114f03..ea9c0268 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -42,13 +42,13 @@ internal static Func CreateUpdateProcessorFactory(Config return configuration._updateProcessorFactory(configuration, flagCacheManager, user); } + var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); if (configuration.IsStreamingEnabled && !inBackground) { - return new MobileStreamingProcessor(configuration, flagCacheManager, user, null); + return new MobileStreamingProcessor(configuration, flagCacheManager, featureFlagRequestor, user, null); } else { - var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); return new MobilePollingProcessor(featureFlagRequestor, flagCacheManager, user, diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 4a8bbbfe..2de5eb86 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -20,14 +20,17 @@ internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor, IStream private readonly IFlagCacheManager _cacheManager; private readonly User _user; private readonly StreamManager _streamManager; + private readonly IFeatureFlagRequestor _requestor; internal MobileStreamingProcessor(Configuration configuration, IFlagCacheManager cacheManager, + IFeatureFlagRequestor requestor, User user, StreamManager.EventSourceCreator eventSourceCreator) { this._configuration = configuration; this._cacheManager = cacheManager; + this._requestor = requestor; this._user = user; var streamProperties = _configuration.UseReport ? MakeStreamPropertiesForReport() : MakeStreamPropertiesForGet(); @@ -116,7 +119,24 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT } case Constants.PING: { - streamManager.Initialized = true; + try + { + Task.Run(async () => + { + var response = await _requestor.FeatureFlagsAsync(); + if (response.statusCode == 200) + { + var flagsAsJsonString = response.jsonResponse; + var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); + _cacheManager.CacheFlagsFromService(flagsDictionary, _user); + streamManager.Initialized = true; + } + }); + } + catch (Exception ex) + { + Log.ErrorFormat("Error in handling PING message: {1}", Util.ExceptionMessage(ex)); + } break; } default: @@ -166,6 +186,10 @@ private void Dispose(bool disposing) if (disposing) { ((IDisposable)_streamManager).Dispose(); + if (_requestor != null) + { + _requestor.Dispose(); + } } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index f0a435ba..b1549573 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -72,6 +72,31 @@ public async Task InitGetsFlagsAsync(UpdateMode mode) } } + [Fact] + public void StreamingInitMakesPollRequestIfStreamSendsPing() + { + Handler streamHandler = async ctx => + { + ctx.AddHeader("Content-Type", "text/event-stream"); + await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("event: ping\ndata: \n\n")); + await Task.Delay(-1, ctx.CancellationToken); + }; + using (var streamServer = TestHttpServer.Start(streamHandler)) + { + using (var pollServer = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) + { + var config = BaseConfig(streamServer.Uri, UpdateMode.Streaming, + b => b.BaseUri(pollServer.Uri)); + using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(5))) + { + VerifyRequest(streamServer.Recorder, UpdateMode.Streaming); + VerifyRequest(pollServer.Recorder, UpdateMode.Polling); + VerifyFlagValues(client, _flagData1); + } + } + } + } + [Fact] public void InitCanTimeOutSync() { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 71dc9cfd..d39a28cd 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -23,6 +23,7 @@ public class MobileStreamingProcessorTests : BaseTest private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; private IFlagCacheManager mockFlagCacheMgr; + private IFeatureFlagRequestor mockRequestor; private IConfigurationBuilder configBuilder; public MobileStreamingProcessorTests() @@ -41,7 +42,7 @@ public MobileStreamingProcessorTests() private IMobileUpdateProcessor MobileStreamingProcessorStarted() { IMobileUpdateProcessor processor = new MobileStreamingProcessor(configBuilder.Build(), - mockFlagCacheMgr, user, eventSourceFactory.Create()); + mockFlagCacheMgr, null, user, eventSourceFactory.Create()); processor.Start(); return processor; } From e34dfe491e1b27b4c37b1c8dcc6fa8243017a730 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 5 Apr 2021 16:12:48 -0700 Subject: [PATCH 333/499] add lower-level test coverage --- .../MobileStreamingProcessor.cs | 11 +++---- .../MobileStreamingProcessorTests.cs | 31 +++++++++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 2de5eb86..957a40de 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -124,13 +124,10 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT Task.Run(async () => { var response = await _requestor.FeatureFlagsAsync(); - if (response.statusCode == 200) - { - var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); - _cacheManager.CacheFlagsFromService(flagsDictionary, _user); - streamManager.Initialized = true; - } + var flagsAsJsonString = response.jsonResponse; + var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); + _cacheManager.CacheFlagsFromService(flagsDictionary, _user); + streamManager.Initialized = true; }); } catch (Exception ex) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index d39a28cd..62ebbba2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -31,6 +31,7 @@ public MobileStreamingProcessorTests() mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); + mockRequestor = new MockFeatureFlagRequestor(initialFlagsJson); configBuilder = Configuration.BuilderInternal("someKey") .ConnectivityStateManager(new MockConnectivityStateManager(true)) .FlagCacheManager(mockFlagCacheMgr) @@ -42,7 +43,7 @@ public MobileStreamingProcessorTests() private IMobileUpdateProcessor MobileStreamingProcessorStarted() { IMobileUpdateProcessor processor = new MobileStreamingProcessor(configBuilder.Build(), - mockFlagCacheMgr, null, user, eventSourceFactory.Create()); + mockFlagCacheMgr, mockRequestor, user, eventSourceFactory.Create()); processor.Start(); return processor; } @@ -114,7 +115,7 @@ public void StreamUriInGetModeHasReasonsParameterIfConfigured() //} [Fact] - public void PUTstoresFeatureFlags() + public void PutStoresFeatureFlags() { MobileStreamingProcessorStarted(); // should be empty before PUT message arrives @@ -129,7 +130,7 @@ public void PUTstoresFeatureFlags() } [Fact] - public void PATCHupdatesFeatureFlag() + public void PatchUpdatesFeatureFlag() { // before PATCH, fill in flags MobileStreamingProcessorStarted(); @@ -147,7 +148,7 @@ public void PATCHupdatesFeatureFlag() } [Fact] - public void PATCHdoesnotUpdateFlagIfVersionIsLower() + public void PatchDoesnotUpdateFlagIfVersionIsLower() { // before PATCH, fill in flags MobileStreamingProcessorStarted(); @@ -165,7 +166,7 @@ public void PATCHdoesnotUpdateFlagIfVersionIsLower() } [Fact] - public void DELETEremovesFeatureFlag() + public void DeleteRemovesFeatureFlag() { // before DELETE, fill in flags, test it's there MobileStreamingProcessorStarted(); @@ -182,7 +183,7 @@ public void DELETEremovesFeatureFlag() } [Fact] - public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() + public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() { // before DELETE, fill in flags, test it's there MobileStreamingProcessorStarted(); @@ -198,6 +199,24 @@ public void DELTEdoesnotRemoveFeatureFlagIfVersionIsLower() Assert.NotNull(mockFlagCacheMgr.FlagForUser("int-flag", user)); } + [Fact] + public async void PingCausesPoll() + { + MobileStreamingProcessorStarted(); + mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("", null), "ping")); + var deadline = DateTime.Now.Add(TimeSpan.FromSeconds(5)); + while (DateTime.Now < deadline) + { + await Task.Delay(TimeSpan.FromMilliseconds(10)); + if (mockFlagCacheMgr.FlagsForUser(user).Count > 0) + { + Assert.Equal(15, mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt); + return; + } + } + Assert.True(false, "timed out waiting for polled flags"); + } + string UpdatedFlag() { var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":999,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; From a6f90b6149556e0e218d9c7efa09caf164c16259 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 12 Apr 2021 11:36:11 -0700 Subject: [PATCH 334/499] (2.0 - #1) minimum changes to migrate to newer common packages that are used by .NET SDK 6.0 (#100) --- .circleci/config.yml | 7 +- src/LaunchDarkly.XamarinSdk/AsyncUtils.cs | 60 - src/LaunchDarkly.XamarinSdk/Configuration.cs | 91 +- .../ConfigurationBuilder.cs | 41 +- .../ConnectionManager.cs | 20 +- src/LaunchDarkly.XamarinSdk/Constants.cs | 4 +- .../DefaultBackgroundModeManager.cs | 4 +- .../DefaultConnectivityStateManager.cs | 4 +- .../DefaultDeviceInfo.cs | 14 +- .../DefaultPersistentStorage.cs | 20 +- src/LaunchDarkly.XamarinSdk/Extensions.cs | 6 +- src/LaunchDarkly.XamarinSdk/Factory.cs | 70 +- src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 42 +- .../FeatureFlagRequestor.cs | 22 +- .../FlagCacheManager.cs | 3 +- .../FlagChangedEvent.cs | 16 +- .../IBackgroundModeManager.cs | 4 +- .../IConnectivityStateManager.cs | 2 +- src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs | 2 +- .../IFlagCacheManager.cs | 3 +- src/LaunchDarkly.XamarinSdk/ILdClient.cs | 3 +- .../ILdClientExtensions.cs | 5 +- .../IMobileUpdateProcessor.cs | 3 +- .../IPersistentStorage.cs | 2 +- src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs | 3 +- .../Events/DefaultEventProcessorWrapper.cs | 59 + .../Internal/Events/EventFactory.cs | 86 + .../Internal/Events/EventProcessorTypes.cs | 125 + .../Internal/Events/IEventProcessor.cs | 17 + .../Internal/LogNames.cs | 14 + src/LaunchDarkly.XamarinSdk/JsonUtil.cs | 36 +- .../LaunchDarkly.XamarinSdk.csproj | 44 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 126 +- src/LaunchDarkly.XamarinSdk/LockUtils.cs | 2 +- .../MobilePollingProcessor.cs | 26 +- .../MobileSideClientEnvironment.cs | 13 - .../MobileStreamingProcessor.cs | 174 +- src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs | 15 +- .../AsyncScheduler.android.cs | 2 +- .../PlatformSpecific/AsyncScheduler.ios.cs | 2 +- .../AsyncScheduler.netstandard.cs | 2 +- .../PlatformSpecific/AsyncScheduler.shared.cs | 2 +- .../BackgroundDetection.android.cs | 3 +- .../BackgroundDetection.ios.cs | 3 +- .../BackgroundDetection.netstandard.cs | 2 +- .../BackgroundDetection.shared.cs | 2 +- .../ClientIdentifier.android.cs | 10 +- .../PlatformSpecific/ClientIdentifier.ios.cs | 7 +- .../ClientIdentifier.netstandard.cs | 11 +- .../ClientIdentifier.shared.cs | 17 +- .../PlatformSpecific/Connectivity.android.cs | 2 +- .../PlatformSpecific/Connectivity.ios.cs | 2 +- .../Connectivity.netstandard.cs | 2 +- .../PlatformSpecific/Connectivity.shared.cs | 2 +- .../PlatformSpecific/Http.android.cs | 2 +- .../PlatformSpecific/Http.ios.cs | 2 +- .../PlatformSpecific/Http.netstandard.cs | 2 +- .../PlatformSpecific/Http.shared.cs | 2 +- .../PlatformSpecific/Permissions.android.cs | 2 +- .../PlatformSpecific/Permissions.shared.cs | 2 +- .../PlatformSpecific/Platform.android.cs | 2 +- .../PlatformSpecific/Platform.shared.cs | 2 +- .../PlatformSpecific/Preferences.android.cs | 13 +- .../PlatformSpecific/Preferences.ios.cs | 13 +- .../Preferences.netstandard.cs | 55 +- .../PlatformSpecific/Preferences.shared.cs | 44 +- .../PlatformSpecific/UserMetadata.android.cs | 2 +- .../PlatformSpecific/UserMetadata.ios.cs | 2 +- .../UserMetadata.netstandard.cs | 2 +- .../PlatformSpecific/UserMetadata.shared.cs | 2 +- src/LaunchDarkly.XamarinSdk/PlatformType.cs | 2 +- .../UserFlagDeviceCache.cs | 19 +- .../UserFlagInMemoryCache.cs | 3 +- .../AndroidSpecificTests.cs | 5 +- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 11 +- .../Resources/Resource.designer.cs | 9239 ++++++++--------- .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 28 +- .../ConfigurationTest.cs | 5 +- .../DefaultDeviceInfoTests.cs | 11 +- .../ExtensionsTest.cs | 14 + .../FeatureFlagBuilder.cs | 9 +- .../FeatureFlagRequestorTests.cs | 17 +- .../FeatureFlagTests.cs | 23 - .../FlagCacheManagerTests.cs | 13 +- .../FlagChangedEventTests.cs | 8 +- .../HttpHelpers.cs | 2 +- .../HttpHelpersTest.cs | 2 +- .../ILdClientExtensionsTest.cs | 9 +- .../LDClientEndToEndTests.cs | 52 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 14 +- .../LdClientEvaluationTests.cs | 12 +- .../LdClientEventTests.cs | 80 +- .../LdClientTests.cs | 53 +- .../LaunchDarkly.XamarinSdk.Tests/LogSink.cs | 86 - .../MobilePollingProcessorTests.cs | 8 +- .../MobileStreamingProcessorTests.cs | 61 +- .../MockComponents.cs | 24 +- .../TestLogging.cs | 48 + .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 7 +- .../UserFlagCacheTests.cs | 5 +- .../IOsSpecificTests.cs | 5 +- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 12 +- 102 files changed, 5764 insertions(+), 5531 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/AsyncUtils.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs create mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/TestLogging.cs diff --git a/.circleci/config.yml b/.circleci/config.yml index 433a2358..682e43ea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,14 +11,15 @@ workflows: jobs: test-netstandard2.0: docker: - - image: microsoft/dotnet:2.0-sdk-jessie + - image: mcr.microsoft.com/dotnet/sdk:5.0-focal environment: ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests steps: - checkout - - run: dotnet restore + - run: dotnet restore src/LaunchDarkly.XamarinSdk - run: dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 - - run: dotnet test -v=normal tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 + - run: dotnet restore tests/LaunchDarkly.XamarinSdk.Tests + - run: dotnet test -v=normal tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj test-android: macos: diff --git a/src/LaunchDarkly.XamarinSdk/AsyncUtils.cs b/src/LaunchDarkly.XamarinSdk/AsyncUtils.cs deleted file mode 100644 index cf4ca1a6..00000000 --- a/src/LaunchDarkly.XamarinSdk/AsyncUtils.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LaunchDarkly.Xamarin -{ - internal static class AsyncUtils - { - private static readonly TaskFactory _taskFactory = new TaskFactory(CancellationToken.None, - TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); - - // This procedure for blocking on a Task without using Task.Wait is derived from the MIT-licensed ASP.NET - // code here: https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs - // In general, mixing sync and async code is not recommended, and if done in other ways can result in - // deadlocks. See: https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c - // Task.Wait would only be safe if we could guarantee that every intermediate Task within the async - // code had been modified with ConfigureAwait(false), but that is very error-prone and we can't depend - // on feature store implementors doing so. - - internal static void WaitSafely(Func taskFn) - { - _taskFactory.StartNew(taskFn) - .Unwrap() - .GetAwaiter() - .GetResult(); - // Note, GetResult does not throw AggregateException so we don't need to post-process exceptions - } - - internal static bool WaitSafely(Func taskFn, TimeSpan timeout) - { - try - { - return _taskFactory.StartNew(taskFn) - .Unwrap() - .Wait(timeout); - } - catch (AggregateException e) - { - throw UnwrapAggregateException(e); - } - } - - internal static T WaitSafely(Func> taskFn) - { - return _taskFactory.StartNew(taskFn) - .Unwrap() - .GetAwaiter() - .GetResult(); - } - - private static Exception UnwrapAggregateException(AggregateException e) - { - if (e.InnerExceptions.Count == 1) - { - return e.InnerExceptions[0]; - } - return e; - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 87c90839..23868153 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Net.Http; -using LaunchDarkly.Client; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Xamarin.Internal.Events; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Configuration options for . @@ -29,11 +30,12 @@ public sealed class Configuration private readonly HttpMessageHandler _httpMessageHandler; private readonly bool _inlineUsersInEvents; private readonly bool _isStreamingEnabled; + private readonly ILogAdapter _logAdapter; private readonly string _mobileKey; private readonly bool _offline; private readonly bool _persistFlagValues; private readonly TimeSpan _pollingInterval; - private readonly ImmutableHashSet _privateAttributeNames; + private readonly ImmutableHashSet _privateAttributeNames; private readonly TimeSpan _readTimeout; private readonly TimeSpan _reconnectTime; private readonly Uri _streamUri; @@ -57,7 +59,7 @@ public sealed class Configuration /// /// /// By default, this is . If , all of the user attributes - /// will be private, not just the attributes specified with + /// will be private, not just the attributes specified with /// or with the method. /// public bool AllAttributesPrivate => _allAttributesPrivate; @@ -145,6 +147,8 @@ public sealed class Configuration /// public bool IsStreamingEnabled => _isStreamingEnabled; + internal ILogAdapter LogAdapter => _logAdapter; + /// /// The key for your LaunchDarkly environment. /// @@ -180,7 +184,7 @@ public sealed class Configuration /// removed, even if you did not use the /// method when building the user. /// - public IImmutableSet PrivateAttributeNames => _privateAttributeNames; + public IImmutableSet PrivateAttributeNames => _privateAttributeNames; /// /// The timeout when reading data from the streaming connection. @@ -324,6 +328,7 @@ internal Configuration(ConfigurationBuilder builder) builder._httpMessageHandler; _inlineUsersInEvents = builder._inlineUsersInEvents; _isStreamingEnabled = builder._isStreamingEnabled; + _logAdapter = builder._logAdapter; _mobileKey = builder._mobileKey; _offline = builder._offline; _persistFlagValues = builder._persistFlagValues; @@ -347,71 +352,11 @@ internal Configuration(ConfigurationBuilder builder) _updateProcessorFactory = builder._updateProcessorFactory; } - internal IEventProcessorConfiguration EventProcessorConfiguration => new EventProcessorAdapter { Config = this }; - internal IHttpRequestConfiguration HttpRequestConfiguration => new HttpRequestAdapter { Config = this }; - internal IStreamManagerConfiguration StreamManagerConfiguration => new StreamManagerAdapter { Config = this }; - - private class EventProcessorAdapter : IEventProcessorConfiguration - { - internal Configuration Config { get; set; } - public bool AllAttributesPrivate => Config.AllAttributesPrivate; - public int EventCapacity => Config.EventCapacity; - public TimeSpan EventFlushInterval => Config.EventFlushInterval; - public Uri EventsUri - { - get - { - // This is a hack to work around the fact that the implementation of DefaultEventProcessor - // in LaunchDarkly.CommonSdk 4.x does not use the path concatenation logic that we implemented - // in this assembly in Extensions.AddPath(Uri, string), which assumes a trailing slash in - // the base path. Instead it just calls new Uri(Uri, string) which will drop the last path - // component of the base path if there's no trailing slash. So we add the trailing slash - // here, and we do *not* include a leading slash in Constants.EVENTS_PATH. - // - // In the next major version of the SDK, we'll be using a different implementation of - // DefaultEventProcessor that's in a different assembly and giving it a pre-concatenated URI. - // So there's no point in fixing this in the current LaunchDarkly.CommonSdk implementation, - // which isn't used anywhere else. - var ub = new UriBuilder(Config.EventsUri); - if (ub.Path.EndsWith("/")) - { - return Config.EventsUri; - } - ub.Path += "/"; - return ub.Uri; - } - } - public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; - public bool InlineUsersInEvents => Config.InlineUsersInEvents; - public IImmutableSet PrivateAttributeNames => Config.PrivateAttributeNames; - public TimeSpan ReadTimeout => Config.ReadTimeout; - public TimeSpan ReconnectTime => Config.ReconnectTime; - public int UserKeysCapacity => Config.UserKeysCapacity; - public TimeSpan UserKeysFlushInterval => Config.UserKeysFlushInterval; - } - - private class HttpRequestAdapter : IHttpRequestConfiguration - { - internal Configuration Config { get; set; } - public string HttpAuthorizationKey => Config.MobileKey; - public HttpMessageHandler HttpMessageHandler => Config.HttpMessageHandler; - } - - private class StreamManagerAdapter : IStreamManagerConfiguration - { - internal Configuration Config { get; set; } - public string HttpAuthorizationKey => Config.MobileKey; - public HttpMessageHandler HttpMessageHandler => Config.HttpMessageHandler; - public TimeSpan HttpClientTimeout => Config.ConnectionTimeout; - public TimeSpan ReadTimeout => Config.ReadTimeout; - public TimeSpan ReconnectTime => Config.ReconnectTime; - - public Exception TranslateHttpException(Exception e) - { - // TODO: this will be used as part of the fix for ch47489 - it will ensure that platform-specific - // exceptions like java.net.SocketTimeout will be logged as a standard .NET exception type - return e; - } - } + internal HttpProperties HttpProperties => HttpProperties.Default + .WithAuthorizationKey(this.MobileKey) + .WithConnectTimeout(this.ConnectionTimeout) + .WithHttpMessageHandlerFactory(_ => this.HttpMessageHandler) + .WithReadTimeout(this.ReadTimeout) + .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))); } } diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index 5ff0b273..ff681e97 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Net.Http; -using Common.Logging; -using LaunchDarkly.Client; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.Internal.Events; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// A mutable object that uses the Builder pattern to specify properties for a object. @@ -38,7 +38,7 @@ public interface IConfigurationBuilder /// /// /// By default, this is . If , all of the user attributes - /// will be private, not just the attributes specified with + /// will be private, not just the attributes specified with /// or with the method. /// /// true if all attributes should be private @@ -168,6 +168,13 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); + /// + /// Sets the implementation of logging that the SDK will use. + /// + /// an ILogAdapter + /// the same builder + IConfigurationBuilder Logging(ILogAdapter logAdapter); + /// /// Sets the key for your LaunchDarkly environment. /// @@ -220,9 +227,9 @@ public interface IConfigurationBuilder /// You may call this method repeatedly to mark multiple attributes as private. /// /// - /// the attribute name + /// the attribute /// the same builder - IConfigurationBuilder PrivateAttribute(string privateAttributeName); + IConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute); /// /// Sets the timeout when reading data from the streaming connection. @@ -276,8 +283,6 @@ public interface IConfigurationBuilder internal sealed class ConfigurationBuilder : IConfigurationBuilder { - private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationBuilder)); - // This exists so that we can distinguish between leaving the HttpMessageHandler property unchanged // and explicitly setting it to null. If the property value is the exact same instance as this, we // will replace it with a platform-specific implementation. @@ -295,11 +300,12 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; internal bool _inlineUsersInEvents = false; internal bool _isStreamingEnabled = true; + internal ILogAdapter _logAdapter = Logs.None; internal string _mobileKey; internal bool _offline = false; internal bool _persistFlagValues = true; internal TimeSpan _pollingInterval = Configuration.DefaultPollingInterval; - internal HashSet _privateAttributeNames = null; + internal HashSet _privateAttributeNames = null; internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; internal Uri _streamUri = Configuration.DefaultStreamUri; @@ -336,12 +342,13 @@ internal ConfigurationBuilder(Configuration copyFrom) _httpMessageHandler = copyFrom.HttpMessageHandler; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _isStreamingEnabled = copyFrom.IsStreamingEnabled; + _logAdapter = copyFrom.LogAdapter; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; _pollingInterval = copyFrom.PollingInterval; _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : - new HashSet(copyFrom.PrivateAttributeNames); + new HashSet(copyFrom.PrivateAttributeNames); _readTimeout = copyFrom.ReadTimeout; _reconnectTime = copyFrom.ReconnectTime; _streamUri = copyFrom.StreamUri; @@ -365,7 +372,6 @@ public IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollin { if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) { - Log.WarnFormat("BackgroundPollingInterval cannot be less than {0}", Configuration.MinimumBackgroundPollingInterval); _backgroundPollingInterval = Configuration.MinimumBackgroundPollingInterval; } else @@ -435,6 +441,12 @@ public IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) return this; } + public IConfigurationBuilder Logging(ILogAdapter logAdapter) + { + _logAdapter = logAdapter ?? Logs.None; + return this; + } + public IConfigurationBuilder MobileKey(string mobileKey) { _mobileKey = mobileKey; @@ -457,7 +469,6 @@ public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) { if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) { - Log.WarnFormat("PollingInterval cannot be less than {0}", Configuration.MinimumPollingInterval); _pollingInterval = Configuration.MinimumPollingInterval; } else @@ -467,13 +478,13 @@ public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) return this; } - public IConfigurationBuilder PrivateAttribute(string privateAtributeName) + public IConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute) { if (_privateAttributeNames is null) { - _privateAttributeNames = new HashSet(); + _privateAttributeNames = new HashSet(); } - _privateAttributeNames.Add(privateAtributeName); + _privateAttributeNames.Add(privateAttribute); return this; } diff --git a/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs index a7e4aca8..e31dd792 100644 --- a/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs +++ b/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs @@ -1,10 +1,10 @@ using System; using System.Threading; using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Manages our connection to LaunchDarkly, if any, and encapsulates all of the state that @@ -22,8 +22,7 @@ namespace LaunchDarkly.Xamarin /// internal sealed class ConnectionManager : IDisposable { - private static readonly ILog Log = LogManager.GetLogger(typeof(ConnectionManager)); - + private readonly Logger _log; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private bool _disposed = false; private bool _started = false; @@ -53,6 +52,11 @@ internal sealed class ConnectionManager : IDisposable /// public bool Initialized => LockUtils.WithReadLock(_lock, () => _initialized); + internal ConnectionManager(Logger log) + { + _log = log; + } + /// /// Sets whether the client should always be offline, and attempts to connect if appropriate. /// @@ -90,7 +94,7 @@ public Task SetForceOffline(bool forceOffline) return Task.FromResult(false); } _forceOffline = forceOffline; - Log.InfoFormat("Offline mode is now {0}", forceOffline); + _log.Info("Offline mode is now {0}", forceOffline); return OpenOrCloseConnectionIfNecessary(); // not awaiting }); } @@ -132,7 +136,7 @@ public Task SetNetworkEnabled(bool networkEnabled) return Task.FromResult(false); } _networkEnabled = networkEnabled; - Log.InfoFormat("Network availability is now {0}", networkEnabled); + _log.Info("Network availability is now {0}", networkEnabled); return OpenOrCloseConnectionIfNecessary(); // not awaiting }); } @@ -264,7 +268,7 @@ private bool SetInitializedIfUpdateProcessorStartedSuccessfully(Task task) if (task.IsFaulted) { // Don't let exceptions from the update processor propagate up into the SDK. Just say we didn't initialize. - Log.ErrorFormat("Failed to initialize LaunchDarkly connection: {0}", Util.ExceptionMessage(task.Exception)); + LogHelpers.LogException(_log, "Failed to initialize LaunchDarkly connection", task.Exception); return false; } var success = task.Result; diff --git a/src/LaunchDarkly.XamarinSdk/Constants.cs b/src/LaunchDarkly.XamarinSdk/Constants.cs index db69acc5..e02f46d5 100644 --- a/src/LaunchDarkly.XamarinSdk/Constants.cs +++ b/src/LaunchDarkly.XamarinSdk/Constants.cs @@ -1,5 +1,5 @@ -using System; -namespace LaunchDarkly.Xamarin + +namespace LaunchDarkly.Sdk.Xamarin { internal static class Constants { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs index 0f35b823..2531ac0c 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal class DefaultBackgroundModeManager : IBackgroundModeManager { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs b/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs index 8e39748a..f011106a 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class DefaultConnectivityStateManager : IConnectivityStateManager { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs index 4ca093ab..28830a39 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs @@ -1,14 +1,20 @@ -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { // This just delegates to the conditionally-compiled code in LaunchDarkly.Xamarin.PlatformSpecific. // The only reason it is a pluggable component is for unit tests; we don't currently expose IDeviceInfo. internal sealed class DefaultDeviceInfo : IDeviceInfo { - public string UniqueDeviceId() + private readonly Logger _log; + + internal DefaultDeviceInfo(Logger log) { - return ClientIdentifier.GetOrCreateClientId(); + _log = log; } + + public string UniqueDeviceId() => + ClientIdentifier.GetOrCreateClientId(_log); } } diff --git a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs index 175ddab9..57d8598a 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs +++ b/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs @@ -1,17 +1,21 @@ -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class DefaultPersistentStorage : IPersistentStorage { - public void Save(string key, string value) - { - Preferences.Set(key, value); - } + private readonly Logger _log; - public string GetValue(string key) + internal DefaultPersistentStorage(Logger log) { - return Preferences.Get(key, null); + _log = log; } + + public void Save(string key, string value) => + Preferences.Set(key, value, _log); + + public string GetValue(string key) => + Preferences.Get(key, null, _log); } } diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs index a2a016ad..6e3ddb51 100644 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ b/src/LaunchDarkly.XamarinSdk/Extensions.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Client; +using LaunchDarkly.Sdk.Json; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal static class Extensions { @@ -13,7 +13,7 @@ public static string UrlSafeBase64Encode(this string plainText) public static string AsJson(this User user) { - return JsonUtil.EncodeJson(user); + return LdJsonSerialization.SerializeObject(user); } // This differs from "new Uri(baseUri, path)" in that it always assumes a trailing "/" in diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index ea9c0268..3eb50d49 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -1,19 +1,18 @@ using System; -using System.Net.Http; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Events; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal static class Factory { - private static readonly ILog Log = LogManager.GetLogger(typeof(Factory)); - internal static IFlagCacheManager CreateFlagCacheManager(Configuration configuration, IPersistentStorage persister, IFlagChangedEventManager flagChangedEventManager, - User user) + User user, + Logger log) { if (configuration._flagCacheManager != null) { @@ -22,7 +21,7 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura else { var inMemoryCache = new UserFlagInMemoryCache(); - var deviceCache = configuration.PersistFlagValues ? new UserFlagDeviceCache(persister) as IUserFlagCache : new NullUserFlagCache(); + var deviceCache = configuration.PersistFlagValues ? new UserFlagDeviceCache(persister, log) as IUserFlagCache : new NullUserFlagCache(); return new FlagCacheManager(inMemoryCache, deviceCache, flagChangedEventManager, user); } } @@ -33,8 +32,9 @@ internal static IConnectivityStateManager CreateConnectivityStateManager(Configu } internal static Func CreateUpdateProcessorFactory(Configuration configuration, User user, - IFlagCacheManager flagCacheManager, bool inBackground) + IFlagCacheManager flagCacheManager, Logger baseLog, bool inBackground) { + Logger log = baseLog.SubLogger(LogNames.DataSourceSubLog); return () => { if (configuration._updateProcessorFactory != null) @@ -42,10 +42,10 @@ internal static Func CreateUpdateProcessorFactory(Config return configuration._updateProcessorFactory(configuration, flagCacheManager, user); } - var featureFlagRequestor = new FeatureFlagRequestor(configuration, user); + var featureFlagRequestor = new FeatureFlagRequestor(configuration, user, log); if (configuration.IsStreamingEnabled && !inBackground) { - return new MobileStreamingProcessor(configuration, flagCacheManager, featureFlagRequestor, user, null); + return new MobileStreamingProcessor(configuration, flagCacheManager, featureFlagRequestor, user, null, log); } else { @@ -53,34 +53,60 @@ internal static Func CreateUpdateProcessorFactory(Config flagCacheManager, user, inBackground ? configuration.BackgroundPollingInterval : configuration.PollingInterval, - inBackground ? configuration.BackgroundPollingInterval : TimeSpan.Zero); + inBackground ? configuration.BackgroundPollingInterval : TimeSpan.Zero, + log); } }; } - internal static IEventProcessor CreateEventProcessor(Configuration configuration) + internal static IEventProcessor CreateEventProcessor(Configuration configuration, Logger baseLog) { if (configuration._eventProcessor != null) { return configuration._eventProcessor; } - HttpClient httpClient = Util.MakeHttpClient(configuration.HttpRequestConfiguration, MobileClientEnvironment.Instance); - return new DefaultEventProcessor(configuration.EventProcessorConfiguration, null, httpClient, Constants.EVENTS_PATH); + + var log = baseLog.SubLogger(LogNames.EventsSubLog); + var eventsConfig = new EventsConfiguration + { + AllAttributesPrivate = configuration.AllAttributesPrivate, + DiagnosticRecordingInterval = TimeSpan.FromMinutes(15), // TODO + DiagnosticUri = null, + EventCapacity = configuration.EventCapacity, + EventFlushInterval = configuration.EventFlushInterval, + EventsUri = configuration.EventsUri.AddPath(Constants.EVENTS_PATH), + InlineUsersInEvents = configuration.InlineUsersInEvents, + PrivateAttributeNames = configuration.PrivateAttributeNames, + RetryInterval = TimeSpan.FromSeconds(1), + UserKeysCapacity = configuration.UserKeysCapacity, + UserKeysFlushInterval = configuration.UserKeysFlushInterval + }; + var httpProperties = configuration.HttpProperties; + var eventProcessor = new EventProcessor( + eventsConfig, + new DefaultEventSender(httpProperties, eventsConfig, log), + null, + null, + null, + log, + null + ); + return new DefaultEventProcessorWrapper(eventProcessor); } - internal static IPersistentStorage CreatePersistentStorage(Configuration configuration) + internal static IPersistentStorage CreatePersistentStorage(Configuration configuration, Logger log) { - return configuration._persistentStorage ?? new DefaultPersistentStorage(); + return configuration._persistentStorage ?? new DefaultPersistentStorage(log); } - internal static IDeviceInfo CreateDeviceInfo(Configuration configuration) + internal static IDeviceInfo CreateDeviceInfo(Configuration configuration, Logger log) { - return configuration._deviceInfo ?? new DefaultDeviceInfo(); + return configuration._deviceInfo ?? new DefaultDeviceInfo(log); } - internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration) + internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration, Logger log) { - return configuration._flagChangedEventManager ?? new FlagChangedEventManager(); + return configuration._flagChangedEventManager ?? new FlagChangedEventManager(log); } } } diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index 9bb36f0d..1616a21a 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -1,9 +1,7 @@ using System; -using LaunchDarkly.Client; -using LaunchDarkly.Common; using Newtonsoft.Json; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class FeatureFlag : IEquatable { @@ -13,12 +11,12 @@ internal sealed class FeatureFlag : IEquatable public readonly bool trackEvents; public readonly bool trackReason; public readonly int? variation; - public readonly long? debugEventsUntilDate; - public readonly EvaluationReason reason; + public readonly UnixMillisecondTime? debugEventsUntilDate; + public readonly EvaluationReason? reason; [JsonConstructor] public FeatureFlag(LdValue value, int version, int? flagVersion, bool trackEvents, bool trackReason, - int? variation, long? debugEventsUntilDate, EvaluationReason reason) + int? variation, UnixMillisecondTime? debugEventsUntilDate, EvaluationReason? reason) { this.value = value; this.version = version; @@ -38,37 +36,7 @@ public bool Equals(FeatureFlag otherFlag) && trackEvents == otherFlag.trackEvents && variation == otherFlag.variation && debugEventsUntilDate == otherFlag.debugEventsUntilDate - && reason == otherFlag.reason; - } - } - - /// - /// The IFlagEventProperties abstraction is used by LaunchDarkly.Common to communicate properties - /// that affect event generation. We can't just have FeatureFlag itself implement that interface, - /// because it doesn't actually contain its own flag key. - /// - internal struct FeatureFlagEvent : IFlagEventProperties - { - private readonly FeatureFlag _featureFlag; - private readonly string _key; - - public FeatureFlagEvent(string key, FeatureFlag featureFlag) - { - _featureFlag = featureFlag; - _key = key; - } - - public string Key => _key; - public int EventVersion => _featureFlag.flagVersion ?? _featureFlag.version; - public bool TrackEvents => _featureFlag.trackEvents; - public long? DebugEventsUntilDate => _featureFlag.debugEventsUntilDate; - - public bool IsExperiment(EvaluationReason reason) - { - // EventFactory passes the reason parameter to this method because the server-side SDK needs to - // look at the reason; but in this client-side SDK, we don't look at that parameter, because - // LD has already done the relevant calculation for us and sent us the result in trackReason. - return _featureFlag.trackReason; + && reason.Equals(otherFlag.reason); } } } diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index b365746c..e9c4776a 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -5,11 +5,11 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Http; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal struct WebResponse { @@ -32,19 +32,22 @@ internal interface IFeatureFlagRequestor : IDisposable internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor { - private static readonly ILog Log = LogManager.GetLogger(typeof(FeatureFlagRequestor)); private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); private readonly Configuration _configuration; private readonly User _currentUser; private readonly HttpClient _httpClient; + private readonly HttpProperties _httpProperties; + private readonly Logger _log; private volatile EntityTagHeaderValue _etag; - internal FeatureFlagRequestor(Configuration configuration, User user) + internal FeatureFlagRequestor(Configuration configuration, User user, Logger log) { this._configuration = configuration; - this._httpClient = Util.MakeHttpClient(configuration.HttpRequestConfiguration, MobileClientEnvironment.Instance); + this._httpProperties = configuration.HttpProperties; + this._httpClient = configuration.HttpProperties.NewHttpClient(); this._currentUser = user; + this._log = log; } public async Task FeatureFlagsAsync() @@ -74,6 +77,7 @@ private Uri MakeRequestUriWithPath(string path) private async Task MakeRequest(HttpRequestMessage request) { + _httpProperties.AddHeaders(request); using (var cts = new CancellationTokenSource(_configuration.ConnectionTimeout)) { if (_etag != null) @@ -83,12 +87,12 @@ private async Task MakeRequest(HttpRequestMessage request) try { - Log.DebugFormat("Getting flags with uri: {0}", request.RequestUri.AbsoluteUri); + _log.Debug("Getting flags with uri: {0}", request.RequestUri.AbsoluteUri); using (var response = await _httpClient.SendAsync(request, cts.Token).ConfigureAwait(false)) { if (response.StatusCode == HttpStatusCode.NotModified) { - Log.Debug("Get all flags returned 304: not modified"); + _log.Debug("Get all flags returned 304: not modified"); return new WebResponse(304, null, "Get all flags returned 304: not modified"); } _etag = response.Headers.ETag; diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs index 0f045a49..8afd4e33 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class FlagCacheManager : IFlagCacheManager { diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index dcb78815..bc740c21 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -1,9 +1,9 @@ using System; using System.Linq; -using Common.Logging; -using LaunchDarkly.Client; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// An event object that is sent to handlers for the event. @@ -73,10 +73,15 @@ internal interface IFlagChangedEventManager internal sealed class FlagChangedEventManager : IFlagChangedEventManager { - private static readonly ILog Log = LogManager.GetLogger(typeof(IFlagChangedEventManager)); + private readonly Logger _log; public event EventHandler FlagChanged; + internal FlagChangedEventManager(Logger log) + { + _log = log; + } + public bool IsHandlerRegistered(EventHandler handler) { return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); @@ -109,8 +114,7 @@ private void FireEvent(FlagChangedEventArgs eventArgs) } catch (Exception e) { - Log.Warn("Unexpected exception from FlagChanged event handler", e); - Log.Debug(e, e); + LogHelpers.LogException(_log, "Unexpected exception from FlagChanged event handler", e); } }); } diff --git a/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs index 205d4061..c144a038 100644 --- a/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IBackgroundModeManager { diff --git a/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs b/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs index ec36679e..076127bf 100644 --- a/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IConnectivityStateManager { diff --git a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs index 53bbad27..4682663b 100644 --- a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs @@ -1,4 +1,4 @@ -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IDeviceInfo { diff --git a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs index 87cb4377..9e25a5f1 100644 --- a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IFlagCacheManager { diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/ILdClient.cs index 22077794..145b7a42 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClient.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Interface for the standard SDK client methods and properties. The only implementation of this is . diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs index bf002166..0b3ffdf1 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs @@ -1,7 +1,6 @@ using System; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Convenience methods that extend the interface. @@ -81,7 +80,7 @@ public static EvaluationDetail EnumVariationDetail(this ILdClient client, } catch (ArgumentException) { - return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); + return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); } } return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, stringDetail.Reason); diff --git a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs index ce5f4daa..725229d6 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs @@ -1,8 +1,7 @@ using System; using System.Threading.Tasks; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Interface for an object that receives updates to feature flags, user segments, and anything diff --git a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs b/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs index 11a2b8d8..eb83ee4e 100644 --- a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs +++ b/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs @@ -1,4 +1,4 @@ -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IPersistentStorage { diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs index 2676c121..6c046d71 100644 --- a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs +++ b/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal interface IUserFlagCache { diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs new file mode 100644 index 00000000..6bfaeb4a --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -0,0 +1,59 @@ +using LaunchDarkly.Sdk.Internal.Events; + +namespace LaunchDarkly.Sdk.Xamarin.Internal.Events +{ + internal sealed class DefaultEventProcessorWrapper : IEventProcessor + { + private EventProcessor _eventProcessor; + + internal DefaultEventProcessorWrapper(EventProcessor eventProcessor) + { + _eventProcessor = eventProcessor; + } + + public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) + { + _eventProcessor.RecordEvaluationEvent(new EventTypes.EvaluationEvent + { + Timestamp = e.Timestamp, + User = e.User, + FlagKey = e.FlagKey, + FlagVersion = e.FlagVersion, + Variation = e.Variation, + Value = e.Value, + Default = e.Default, + Reason = e.Reason, + TrackEvents = e.TrackEvents, + DebugEventsUntilDate = e.DebugEventsUntilDate + }); + } + + public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) + { + _eventProcessor.RecordIdentifyEvent(new EventTypes.IdentifyEvent + { + Timestamp = e.Timestamp, + User = e.User + }); + } + + public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) + { + _eventProcessor.RecordCustomEvent(new EventTypes.CustomEvent + { + Timestamp = e.Timestamp, + User = e.User, + EventKey = e.EventKey, + Data = e.Data, + MetricValue = e.MetricValue + }); + } + + public void SetOffline(bool offline) => + _eventProcessor.SetOffline(offline); + + public void Flush() => _eventProcessor.Flush(); + + public void Dispose() => _eventProcessor.Dispose(); + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs new file mode 100644 index 00000000..bace0282 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs @@ -0,0 +1,86 @@ + +using static LaunchDarkly.Sdk.Xamarin.Internal.Events.EventProcessorTypes; + +namespace LaunchDarkly.Sdk.Xamarin.Internal.Events +{ + internal sealed class EventFactory + { + private readonly bool _withReasons; + + internal static readonly EventFactory Default = new EventFactory(false); + internal static readonly EventFactory DefaultWithReasons = new EventFactory(true); + + internal EventFactory(bool withReasons) + { + _withReasons = withReasons; + } + + internal EvaluationEvent NewEvaluationEvent( + string flagKey, + FeatureFlag flag, + User user, + EvaluationDetail result, + LdValue defaultValue + ) + { + // EventFactory passes the reason parameter to this method because the server-side SDK needs to + // look at the reason; but in this client-side SDK, we don't look at that parameter, because + // LD has already done the relevant calculation for us and sent us the result in trackReason. + var isExperiment = flag.trackReason; + + return new EvaluationEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user, + FlagKey = flagKey, + FlagVersion = flag.flagVersion ?? flag.version, + Variation = result.VariationIndex, + Value = result.Value, + Default = defaultValue, + Reason = (_withReasons || isExperiment) ? result.Reason : (EvaluationReason?)null, + TrackEvents = flag.trackEvents || isExperiment, + DebugEventsUntilDate = flag.debugEventsUntilDate + }; + } + + internal EvaluationEvent NewDefaultValueEvaluationEvent( + string flagKey, + FeatureFlag flag, + User user, + LdValue defaultValue, + EvaluationErrorKind errorKind + ) + { + return new EvaluationEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user, + FlagKey = flagKey, + FlagVersion = flag.flagVersion ?? flag.version, + Value = defaultValue, + Default = defaultValue, + Reason = _withReasons ? EvaluationReason.ErrorReason(errorKind) : (EvaluationReason?)null, + TrackEvents = flag.trackEvents, + DebugEventsUntilDate = flag.debugEventsUntilDate + }; + } + + internal EvaluationEvent NewUnknownFlagEvaluationEvent( + string flagKey, + User user, + LdValue defaultValue, + EvaluationErrorKind errorKind + ) + { + return new EvaluationEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user, + FlagKey = flagKey, + Value = defaultValue, + Default = defaultValue, + Reason = _withReasons ? EvaluationReason.ErrorReason(errorKind) : (EvaluationReason?)null, + }; + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs b/src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs new file mode 100644 index 00000000..02475502 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs @@ -0,0 +1,125 @@ + +namespace LaunchDarkly.Sdk.Xamarin.Internal.Events +{ + /// + /// Parameter types for use by implementations. + /// + /// + /// Application code normally does not need to use these types or interact directly with any + /// functionality. They are provided to allow a custom implementation + /// or test fixture to be substituted for the SDK's normal analytics event logic. + /// + internal static class EventProcessorTypes + { + /// + /// Parameters for . + /// + public struct EvaluationEvent + { + /// + /// Date/timestamp of the event. + /// + public UnixMillisecondTime Timestamp { get; set; } + + /// + /// Attributes of the user who generated the event. Some attributes may not be sent + /// to LaunchDarkly if they are private. + /// + public User User { get; set; } + + /// + /// The unique key of the feature flag involved in the event. + /// + public string FlagKey { get; set; } + + /// + /// The version of the flag. + /// + public int? FlagVersion { get; set; } + + /// + /// The variation index for the computed value of the flag. + /// + public int? Variation { get; set; } + + /// + /// The computed value of the flag. + /// + public LdValue Value { get; set; } + + /// + /// The default value of the flag. + /// + public LdValue Default { get; set; } + + /// + /// An explanation of how the value was calculated, or null if the reason was not requested. + /// + public EvaluationReason? Reason { get; set; } + + /// + /// The key of the flag that this flag is a prerequisite of, if any. + /// + public string PrerequisiteOf { get; set; } + + /// + /// True if full-fidelity analytics events should be sent for this flag. + /// + public bool TrackEvents { get; set; } + + /// + /// If set, debug events are being generated until this date/time. + /// + public UnixMillisecondTime? DebugEventsUntilDate { get; set; } + } + + /// + /// Parameters for . + /// + public struct IdentifyEvent + { + /// + /// Date/timestamp of the event. + /// + public UnixMillisecondTime Timestamp { get; set; } + + /// + /// Attributes of the user being identified. Some attributes may not be sent + /// to LaunchDarkly if they are private. + /// + public User User { get; set; } + } + + /// + /// Parameters for . + /// + public struct CustomEvent + { + /// + /// Date/timestamp of the event. + /// + public UnixMillisecondTime Timestamp { get; set; } + /// + /// Attributes of the user who generated the event. Some attributes may not be sent + /// to LaunchDarkly if they are private. + /// + public User User { get; set; } + + /// + /// The event key. + /// + public string EventKey { get; set; } + + + /// + /// Custom data provided for the event. + /// + public LdValue Data { get; set; } + + /// + /// An optional numeric value that can be used in analytics. + /// + public double? MetricValue { get; set; } + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs b/src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs new file mode 100644 index 00000000..7009a2db --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs @@ -0,0 +1,17 @@ +using System; + +namespace LaunchDarkly.Sdk.Xamarin.Internal.Events +{ + internal interface IEventProcessor : IDisposable + { + void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e); + + void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e); + + void RecordCustomEvent(EventProcessorTypes.CustomEvent e); + + void SetOffline(bool offline); + + void Flush(); + } +} diff --git a/src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs b/src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs new file mode 100644 index 00000000..088b631b --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs @@ -0,0 +1,14 @@ + +namespace LaunchDarkly.Sdk.Xamarin.Internal +{ + internal static class LogNames + { + internal const string Base = "LaunchDarkly.Sdk.Xamarin.LdClient"; + + internal const string DataSourceSubLog = "DataSource"; + + internal const string DataStoreSubLog = "DataStore"; + + internal const string EventsSubLog = "Events"; + } +} diff --git a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs index 658026c0..343fca18 100644 --- a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs +++ b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; +using LaunchDarkly.Sdk.Json; using Newtonsoft.Json; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal class JsonUtil { private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { + Converters = new List { LdJsonNet.Converter, new UnixMillisecondTimeConverter() }, DateParseHandling = DateParseHandling.None }; @@ -27,5 +30,36 @@ internal static string EncodeJson(object o) { return JsonConvert.SerializeObject(o, _jsonSettings); } + + private class UnixMillisecondTimeConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => + objectType == typeof(UnixMillisecondTime) || objectType == typeof(UnixMillisecondTime?); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (objectType == typeof(UnixMillisecondTime?)) + { + if (reader.TokenType == JsonToken.Null) + { + reader.Skip(); + return null; + } + } + return UnixMillisecondTime.OfMillis((long)reader.Value); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(((UnixMillisecondTime)value).Value); + } + } + } } } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 4aa783d5..ff104d70 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -1,29 +1,33 @@ + 2.0.0-alpha.1 - netstandard1.6;netstandard2.0;net45;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; - netstandard1.6;netstandard2.0;Xamarin.iOS10;MonoAndroid71;MonoAndroid80;MonoAndroid81; + netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 + $(BaseTargetFrameworks);net452 + $(BaseTargetFrameworks) $(LD_TARGET_FRAMEWORKS) - 1.2.2 Library LaunchDarkly.XamarinSdk LaunchDarkly.XamarinSdk false bin\$(Configuration)\$(Framework) true - latest + 7.3 True False True true - - - - bin\Debug\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml - - - bin\Release\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml + bin\$(Configuration)\$(TargetFramework)\LaunchDarkly.XamarinSdk.xml + LaunchDarkly + Copyright 2020 LaunchDarkly + Apache-2.0 + https://github.com/launchdarkly/xamarin-client-sdk + https://github.com/launchdarkly/xamarin-client-sdk + master + true + snupkg + LaunchDarkly.Sdk.Xamarin @@ -33,32 +37,38 @@ - - + + + + + + + + - + - + - + - + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 31bd128b..65f38256 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -3,12 +3,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate @@ -16,8 +17,6 @@ namespace LaunchDarkly.Xamarin /// public sealed class LdClient : ILdClient { - private static readonly ILog Log = LogManager.GetLogger(typeof(LdClient)); - static readonly EventFactory _eventFactoryDefault = EventFactory.Default; static readonly EventFactory _eventFactoryWithReasons = EventFactory.DefaultWithReasons; @@ -34,6 +33,7 @@ public sealed class LdClient : ILdClient readonly IFlagCacheManager flagCacheManager; internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing readonly IPersistentStorage persister; + private readonly Logger _log; // Mutable client state (some state is also in the ConnectionManager) readonly ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim(); @@ -53,7 +53,7 @@ public sealed class LdClient : ILdClient /// /// The current version string of the SDK. /// - public static Version Version => MobileClientEnvironment.Instance.Version; + public static Version Version => AssemblyVersions.GetAssemblyVersionForType(typeof(LdClient)); /// /// The instance used to set up the LdClient. @@ -126,42 +126,50 @@ public event EventHandler FlagChanged _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - persister = Factory.CreatePersistentStorage(configuration); - deviceInfo = Factory.CreateDeviceInfo(configuration); - flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration); + _log = configuration.LogAdapter.Logger(LogNames.Base); + + _log.Info("Starting LaunchDarkly Client {0}", Version); + + persister = Factory.CreatePersistentStorage(configuration, _log); + deviceInfo = Factory.CreateDeviceInfo(configuration, _log); + flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration, _log); _user = DecorateUser(user); - flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User); + flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User, _log); - _connectionManager = new ConnectionManager(); + _connectionManager = new ConnectionManager(_log); _connectionManager.SetForceOffline(configuration.Offline); if (configuration.Offline) { - Log.InfoFormat("Starting LaunchDarkly client in offline mode"); + _log.Info("Starting LaunchDarkly client in offline mode"); } _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(configuration, User, flagCacheManager, _inBackground), + Factory.CreateUpdateProcessorFactory(configuration, User, flagCacheManager, _log, _inBackground), true ); _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); _connectivityStateManager.ConnectionChanged += networkAvailable => { - Log.DebugFormat("Setting online to {0} due to a connectivity change event", networkAvailable); + _log.Debug("Setting online to {0} due to a connectivity change event", networkAvailable); _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result eventProcessor.SetOffline(!networkAvailable || _connectionManager.ForceOffline); }; var isConnected = _connectivityStateManager.IsConnected; _connectionManager.SetNetworkEnabled(isConnected); - eventProcessor = Factory.CreateEventProcessor(configuration); + eventProcessor = Factory.CreateEventProcessor(configuration, _log); eventProcessor.SetOffline(configuration.Offline || !isConnected); // Send an initial identify event, but only if we weren't explicitly set to be offline if (!configuration.Offline) { - eventProcessor.SendEvent(_eventFactoryDefault.NewIdentifyEvent(User)); + eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user + }); } _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); @@ -173,7 +181,7 @@ void Start(TimeSpan maxWaitTime) var success = AsyncUtils.WaitSafely(() => _connectionManager.Start(), maxWaitTime); if (!success) { - Log.WarnFormat("Client did not successfully initialize within {0} milliseconds.", + _log.Warn("Client did not successfully initialize within {0} milliseconds.", maxWaitTime.TotalMilliseconds); } } @@ -313,7 +321,6 @@ static LdClient CreateInstance(Configuration configuration, User user) var c = new LdClient(configuration, user); _instance = c; - Log.InfoFormat("Initialized LaunchDarkly Client {0}", Version); return c; } } @@ -403,59 +410,58 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => { if (!Initialized) { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); - SendEventIfOnline(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.CLIENT_NOT_READY)); - return errorResult(EvaluationErrorKind.CLIENT_NOT_READY); + _log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, User, defaultJson, + EvaluationErrorKind.ClientNotReady)); + return errorResult(EvaluationErrorKind.ClientNotReady); } else { - Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); - SendEventIfOnline(eventFactory.NewUnknownFeatureRequestEvent(featureKey, User, defaultJson, - EvaluationErrorKind.FLAG_NOT_FOUND)); - return errorResult(EvaluationErrorKind.FLAG_NOT_FOUND); + _log.Info("Unknown feature flag {0}; returning default value", featureKey); + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, User, defaultJson, + EvaluationErrorKind.FlagNotFound)); + return errorResult(EvaluationErrorKind.FlagNotFound); } } else { if (!Initialized) { - Log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); + _log.Warn("LaunchDarkly client has not yet been initialized. Returning cached value"); } } - FeatureFlagEvent featureFlagEvent = new FeatureFlagEvent(featureKey, flag); EvaluationDetail result; LdValue valueJson; if (flag.value.IsNull) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, flag.variation, flag.reason); + result = new EvaluationDetail(defaultValue, flag.variation, flag.reason ?? EvaluationReason.OffReason); } else { if (checkType && !defaultJson.IsNull && flag.value.Type != defaultJson.Type) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); + result = new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); } else { valueJson = flag.value; - result = new EvaluationDetail(converter.ToType(flag.value), flag.variation, flag.reason); + result = new EvaluationDetail(converter.ToType(flag.value), flag.variation, flag.reason ?? EvaluationReason.OffReason); } } - var featureEvent = eventFactory.NewFeatureRequestEvent(featureFlagEvent, User, - new EvaluationDetail(valueJson, flag.variation, flag.reason), defaultJson); - SendEventIfOnline(featureEvent); + var featureEvent = eventFactory.NewEvaluationEvent(featureKey, flag, User, + new EvaluationDetail(valueJson, flag.variation, flag.reason ?? EvaluationReason.OffReason), defaultJson); + SendEvaluationEventIfOnline(featureEvent); return result; } - private void SendEventIfOnline(Event e) + private void SendEvaluationEventIfOnline(EventProcessorTypes.EvaluationEvent e) { if (!_connectionManager.ForceOffline) { - eventProcessor.SendEvent(e); + eventProcessor.RecordEvaluationEvent(e); } } @@ -469,13 +475,32 @@ public IDictionary AllFlags() /// public void Track(string eventName, LdValue data, double metricValue) { - eventProcessor.SendEvent(_eventFactoryDefault.NewCustomEvent(eventName, User, data, metricValue)); + if (!_connectionManager.ForceOffline) + { + eventProcessor.RecordCustomEvent(new EventProcessorTypes.CustomEvent + { + Timestamp = UnixMillisecondTime.Now, + EventKey = eventName, + User = User, + Data = data, + MetricValue = metricValue + }); + } } /// public void Track(string eventName, LdValue data) { - SendEventIfOnline(_eventFactoryDefault.NewCustomEvent(eventName, User, data)); + if (!_connectionManager.ForceOffline) + { + eventProcessor.RecordCustomEvent(new EventProcessorTypes.CustomEvent + { + Timestamp = UnixMillisecondTime.Now, + EventKey = eventName, + User = User, + Data = data + }); + } } /// @@ -516,10 +541,17 @@ public async Task IdentifyAsync(User user) _user = newUser; }); - SendEventIfOnline(_eventFactoryDefault.NewIdentifyEvent(newUser)); + if (!_connectionManager.ForceOffline) + { + eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user + }); + } return await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, newUser, flagCacheManager, _inBackground), + Factory.CreateUpdateProcessorFactory(_config, newUser, flagCacheManager, _log, _inBackground), true ); } @@ -578,7 +610,7 @@ void Dispose(bool disposing) { if (disposing) { - Log.InfoFormat("Shutting down the LaunchDarkly client"); + _log.Info("Shutting down the LaunchDarkly client"); _backgroundModeManager.BackgroundModeChanged -= OnBackgroundModeChanged; _connectionManager.Dispose(); @@ -612,19 +644,19 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { return; } - Log.DebugFormat("Background mode is changing to {0}", goingIntoBackground); + _log.Debug("Background mode is changing to {0}", goingIntoBackground); if (goingIntoBackground) { if (!Config.EnableBackgroundUpdating) { - Log.Debug("Background updating is disabled"); + _log.Debug("Background updating is disabled"); await _connectionManager.SetUpdateProcessorFactory(null, false); return; } - Log.Debug("Background updating is enabled, starting polling processor"); + _log.Debug("Background updating is enabled, starting polling processor"); } await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, goingIntoBackground), + Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, _log, goingIntoBackground), false // don't reset initialized state because the user is still the same ); } diff --git a/src/LaunchDarkly.XamarinSdk/LockUtils.cs b/src/LaunchDarkly.XamarinSdk/LockUtils.cs index 338c35ea..09346a42 100644 --- a/src/LaunchDarkly.XamarinSdk/LockUtils.cs +++ b/src/LaunchDarkly.XamarinSdk/LockUtils.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal static class LockUtils { diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index 773927a1..fa1fb678 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -2,21 +2,19 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class MobilePollingProcessor : IMobileUpdateProcessor { - private static readonly ILog Log = LogManager.GetLogger(typeof(MobilePollingProcessor)); - private readonly IFeatureFlagRequestor _featureFlagRequestor; private readonly IFlagCacheManager _flagCacheManager; private readonly User _user; private readonly TimeSpan _pollingInterval; private readonly TimeSpan _initialDelay; + private readonly Logger _log; private readonly TaskCompletionSource _startTask; private readonly TaskCompletionSource _stopTask; private const int UNINITIALIZED = 0; @@ -28,13 +26,15 @@ internal MobilePollingProcessor(IFeatureFlagRequestor featureFlagRequestor, IFlagCacheManager cacheManager, User user, TimeSpan pollingInterval, - TimeSpan initialDelay) + TimeSpan initialDelay, + Logger log) { this._featureFlagRequestor = featureFlagRequestor; this._flagCacheManager = cacheManager; this._user = user; this._pollingInterval = pollingInterval; this._initialDelay = initialDelay; + this._log = log; _startTask = new TaskCompletionSource(); _stopTask = new TaskCompletionSource(); } @@ -46,11 +46,11 @@ Task IMobileUpdateProcessor.Start() if (_initialDelay > TimeSpan.Zero) { - Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0} (waiting {1} first)", _pollingInterval, _initialDelay); + _log.Info("Starting LaunchDarkly PollingProcessor with interval: {0} (waiting {1} first)", _pollingInterval, _initialDelay); } else { - Log.InfoFormat("Starting LaunchDarkly PollingProcessor with interval: {0}", _pollingInterval); + _log.Info("Starting LaunchDarkly PollingProcessor with interval: {0}", _pollingInterval); } Task.Run(() => UpdateTaskLoopAsync()); @@ -90,14 +90,14 @@ private async Task UpdateTaskAsync() if (Interlocked.CompareExchange(ref _initialized, INITIALIZED, UNINITIALIZED) == UNINITIALIZED) { _startTask.SetResult(true); - Log.Info("Initialized LaunchDarkly Polling Processor."); + _log.Info("Initialized LaunchDarkly Polling Processor."); } } } catch (UnsuccessfulResponseException ex) when (ex.StatusCode == 401) { - Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(ex)); - Log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); + _log.Error("Error Updating features: '{0}'", LogValues.ExceptionSummary(ex)); + _log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); if (_initialized == UNINITIALIZED) { _startTask.SetException(ex); @@ -106,7 +106,7 @@ private async Task UpdateTaskAsync() } catch (Exception ex) { - Log.ErrorFormat("Error Updating features: '{0}'", Util.ExceptionMessage(PlatformSpecific.Http.TranslateHttpException(ex))); + LogHelpers.LogException(_log, "Error updating features", PlatformSpecific.Http.TranslateHttpException(ex)); } } diff --git a/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs b/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs deleted file mode 100644 index 4a519a98..00000000 --- a/src/LaunchDarkly.XamarinSdk/MobileSideClientEnvironment.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using LaunchDarkly.Common; - -namespace LaunchDarkly.Xamarin -{ - internal sealed class MobileClientEnvironment : ClientEnvironment - { - internal static readonly MobileClientEnvironment Instance = - new MobileClientEnvironment(); - - public override string UserAgentType { get { return "XamarinClient"; } } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 957a40de..14bc9385 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -1,73 +1,114 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; -using LaunchDarkly.Client; -using LaunchDarkly.Common; using System.Net.Http; using Newtonsoft.Json.Linq; -using System.Text; -using Common.Logging; +using LaunchDarkly.EventSource; +using LaunchDarkly.JsonStream; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Http; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { - internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor, IStreamProcessor + internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor { - private static readonly ILog Log = LogManager.GetLogger(typeof(MobileStreamingProcessor)); + // The read timeout for the stream is not the same read timeout that can be set in the SDK configuration. + // It is a fixed value that is set to be slightly longer than the expected interval between heartbeats + // from the LaunchDarkly streaming server. If this amount of time elapses with no new data, the connection + // will be cycled. + private static readonly TimeSpan LaunchDarklyStreamReadTimeout = TimeSpan.FromMinutes(5); + private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); private readonly Configuration _configuration; private readonly IFlagCacheManager _cacheManager; private readonly User _user; - private readonly StreamManager _streamManager; + private readonly EventSourceCreator _eventSourceCreator; private readonly IFeatureFlagRequestor _requestor; + private readonly TaskCompletionSource _initTask; + private readonly AtomicBoolean _initialized = new AtomicBoolean(false); + private readonly Logger _log; + + private volatile IEventSource _eventSource; + + internal delegate IEventSource EventSourceCreator( + HttpProperties httpProperties, + HttpMethod method, + Uri uri, + string jsonBody + ); internal MobileStreamingProcessor(Configuration configuration, IFlagCacheManager cacheManager, IFeatureFlagRequestor requestor, User user, - StreamManager.EventSourceCreator eventSourceCreator) + EventSourceCreator eventSourceCreator, + Logger log) { this._configuration = configuration; this._cacheManager = cacheManager; this._requestor = requestor; this._user = user; - - var streamProperties = _configuration.UseReport ? MakeStreamPropertiesForReport() : MakeStreamPropertiesForGet(); - - _streamManager = new StreamManager(this, - streamProperties, - _configuration.StreamManagerConfiguration, - MobileClientEnvironment.Instance, - eventSourceCreator); + this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; + this._initTask = new TaskCompletionSource(); + this._log = log; } #region IMobileUpdateProcessor - bool IMobileUpdateProcessor.Initialized() - { - return _streamManager.Initialized; - } + bool IMobileUpdateProcessor.Initialized() => _initialized.Get(); - async Task IMobileUpdateProcessor.Start() + Task IMobileUpdateProcessor.Start() { - return await _streamManager.Start(); + if (_configuration.UseReport) + { + _eventSource = _eventSourceCreator( + _configuration.HttpProperties, + ReportMethod, + MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), + _user.AsJson() + ); + } + else + { + _eventSource = _eventSourceCreator( + _configuration.HttpProperties, + HttpMethod.Get, + MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + _user.AsJson().UrlSafeBase64Encode()), + null + ); + } + + _eventSource.MessageReceived += OnMessage; + _eventSource.Error += OnError; + _eventSource.Opened += OnOpen; + + _ = Task.Run(() => _eventSource.StartAsync()); + return _initTask.Task; } #endregion - private StreamProperties MakeStreamPropertiesForGet() + private IEventSource CreateEventSource( + HttpProperties httpProperties, + HttpMethod method, + Uri uri, + string jsonBody + ) { - var userEncoded = _user.AsJson().UrlSafeBase64Encode(); - var path = Constants.STREAM_REQUEST_PATH + userEncoded; - return new StreamProperties(MakeRequestUriWithPath(path), HttpMethod.Get, null); + var configBuilder = EventSource.Configuration.Builder(uri) + .Method(method) + .HttpMessageHandler(httpProperties.HttpMessageHandlerFactory(httpProperties)) + .ConnectionTimeout(httpProperties.ConnectTimeout) + .InitialRetryDelay(_configuration.ReconnectTime) + .ReadTimeout(LaunchDarklyStreamReadTimeout) + .RequestHeaders(httpProperties.BaseHeaders.ToDictionary(kv => kv.Key, kv => kv.Value)) + .Logger(_log); + return new EventSource.EventSource(configBuilder.Build()); } - - private StreamProperties MakeStreamPropertiesForReport() - { - var content = new StringContent(_user.AsJson(), Encoding.UTF8, Constants.APPLICATION_JSON); - return new StreamProperties(MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), ReportMethod, content); - } private Uri MakeRequestUriWithPath(string path) { @@ -75,16 +116,57 @@ private Uri MakeRequestUriWithPath(string path) return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; } + private void OnOpen(object sender, EventSource.StateChangedEventArgs e) + { + _log.Debug("EventSource Opened"); + } + + private void OnMessage(object sender, EventSource.MessageReceivedEventArgs e) + { + try + { + HandleMessage(e.EventName, e.Message.Data); + } + catch (JsonReadException ex) + { + _log.Error("LaunchDarkly service request failed or received invalid data: {0}", + LogValues.ExceptionSummary(ex)); + } + catch (Exception ex) + { + LogHelpers.LogException(_log, "Unexpected error in stream processing", ex); + } + } + + private void OnError(object sender, EventSource.ExceptionEventArgs e) + { + var ex = e.Exception; + LogHelpers.LogException(_log, "Encountered EventSource error", ex); + if (ex is EventSource.EventSourceServiceUnsuccessfulResponseException respEx) + { + int status = respEx.StatusCode; + _log.Error(HttpErrors.ErrorMessage(status, "streaming connection", "will retry")); + if (!HttpErrors.IsRecoverable(status)) + { + _initTask.TrySetException(ex); // sends this exception to the client if we haven't already started up + ((IDisposable)this).Dispose(); + } + } + } + #region IStreamProcessor - Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageType, string messageData) + void HandleMessage(string messageType, string messageData) { switch (messageType) { case Constants.PUT: { _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(messageData), _user); - streamManager.Initialized = true; + if (!_initialized.GetAndSet(true)) + { + _initTask.SetResult(true); + } break; } case Constants.PATCH: @@ -93,12 +175,12 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT { var parsed = JsonUtil.DecodeJson(messageData); var flagkey = (string)parsed[Constants.KEY]; - var featureFlag = parsed.ToObject(); + var featureFlag = JsonUtil.DecodeJson(messageData); PatchFeatureFlag(flagkey, featureFlag); } catch (Exception ex) { - Log.ErrorFormat("Error parsing PATCH message {0}: {1}", messageData, Util.ExceptionMessage(ex)); + _log.Error("Error parsing PATCH message {0}: {1}", messageData, LogValues.ExceptionSummary(ex)); } break; } @@ -113,7 +195,7 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT } catch (Exception ex) { - Log.ErrorFormat("Error parsing DELETE message {0}: {1}", messageData, Util.ExceptionMessage(ex)); + _log.Error("Error parsing DELETE message {0}: {1}", messageData, LogValues.ExceptionSummary(ex)); } break; } @@ -127,20 +209,21 @@ Task IStreamProcessor.HandleMessage(StreamManager streamManager, string messageT var flagsAsJsonString = response.jsonResponse; var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); _cacheManager.CacheFlagsFromService(flagsDictionary, _user); - streamManager.Initialized = true; + if (!_initialized.GetAndSet(true)) + { + _initTask.SetResult(true); + } }); } catch (Exception ex) { - Log.ErrorFormat("Error in handling PING message: {1}", Util.ExceptionMessage(ex)); + _log.Error("Error in handling PING message: {0}", LogValues.ExceptionSummary(ex)); } break; } default: break; } - - return Task.FromResult(true); } void PatchFeatureFlag(string flagKey, FeatureFlag featureFlag) @@ -182,11 +265,8 @@ private void Dispose(bool disposing) { if (disposing) { - ((IDisposable)_streamManager).Dispose(); - if (_requestor != null) - { - _requestor.Dispose(); - } + _eventSource?.Close(); + _requestor?.Dispose(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs b/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs index e8403291..38c5404b 100644 --- a/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs +++ b/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs @@ -9,23 +9,12 @@ // // However, currently Sandcastle does not correctly resolve links if you use that method. -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// This is the main namespace for the LaunchDarkly Xamarin SDK. You will most often use /// (the SDK client) and (configuration options for the client). The SDK also uses types - /// from , such as . - /// - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - class NamespaceDoc - { - } -} - -namespace LaunchDarkly.Client -{ - /// - /// This namespace contains types that are shared between the Xamarin and .NET SDKs, such as . + /// from , such as . /// [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] class NamespaceDoc diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs index d36e44ea..9cccbda3 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -1,7 +1,7 @@ using System; using Android.OS; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs index de4a2a55..9d3e9a41 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs @@ -1,7 +1,7 @@ using System; using Foundation; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs index ef7411f5..abb3e383 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class AsyncScheduler { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs index b2f9b44a..00363a5f 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // This provides a method for asynchronously starting tasks, such as event handlers, using a mechanism // that may vary by platform. diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs index a3ddc5d1..b10a6c52 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs @@ -1,9 +1,8 @@ using System; -using LaunchDarkly.Xamarin; using Android.App; using Android.OS; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs index 1e3a8342..b8b1ba09 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs @@ -1,9 +1,8 @@ using System; -using LaunchDarkly.Xamarin; using UIKit; using Foundation; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs index 3d768296..ebde6ba1 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same abstraction. It is a stub // that does nothing, since in .NET Standard there is no notion of an application being in the diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs index 146f2e46..2c132229 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class BackgroundDetection { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs index b856dddd..2e15bc59 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -1,19 +1,17 @@ using System; -using Common.Logging; -using Android.App; using Android.Provider; using Android.OS; using Android.Runtime; using Java.Interop; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { - private static readonly ILog Log = LogManager.GetLogger(typeof(ClientIdentifier)); private static JniPeerMembers buildMembers = new XAPeerMembers("android/os/Build", typeof(Build)); - private static string PlatformGetOrCreateClientId() + private static string PlatformGetOrCreateClientId(Logger log) { // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs string serialField; @@ -35,7 +33,7 @@ private static string PlatformGetOrCreateClientId() } catch (Exception ex) { - Log.WarnFormat("Unable to get client ID: {0}", ex); + log.Warn("Unable to get client ID: {0}", LogValues.ExceptionSummary(ex)); return null; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs index 27e779c4..e40b9a89 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -1,11 +1,12 @@ -using UIKit; +using LaunchDarkly.Logging; +using UIKit; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. - private static string PlatformGetOrCreateClientId() + private static string PlatformGetOrCreateClientId(Logger log) { return UIDevice.CurrentDevice.IdentifierForVendor.AsString(); } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs index 1b3e8cc9..5ac30b07 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs @@ -1,14 +1,13 @@ - -namespace LaunchDarkly.Xamarin.PlatformSpecific +using LaunchDarkly.Logging; + +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. // Instead, we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a // user key for this (OS) user account, and if not, generate a randomized ID and cache it. - private static string PlatformGetOrCreateClientId() - { - return GetOrCreateRandomizedClientId(); - } + private static string PlatformGetOrCreateClientId(Logger log) => + GetOrCreateRandomizedClientId(log); } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs index aeef2a81..7a6e3eb0 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -1,6 +1,7 @@ using System; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class ClientIdentifier { @@ -8,12 +9,12 @@ internal static partial class ClientIdentifier private const string PreferencesAnonUserIdKey = "anonUserId"; - public static string GetOrCreateClientId() + public static string GetOrCreateClientId(Logger log) { var id = _id; if (id is null) { - id = PlatformGetOrCreateClientId(); + id = PlatformGetOrCreateClientId(log); _id = id; } return id; @@ -21,23 +22,23 @@ public static string GetOrCreateClientId() // Used only for testing, to keep previous calls to GetOrCreateRandomizedClientId from affecting test state. // On mobile platforms this has no effect. - internal static void ClearCachedClientId() + internal static void ClearCachedClientId(Logger log) { - Preferences.Remove(PreferencesAnonUserIdKey); + Preferences.Remove(PreferencesAnonUserIdKey, log); } - private static string GetOrCreateRandomizedClientId() + private static string GetOrCreateRandomizedClientId(Logger log) { // On non-mobile platforms, there may not be an OS-based notion of a device identifier. Instead, // we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a user key // for this user account (OS user, that is), and if not, generate a randomized ID and cache it. - string cachedKey = Preferences.Get(PreferencesAnonUserIdKey, null); + string cachedKey = Preferences.Get(PreferencesAnonUserIdKey, null, log); if (cachedKey != null) { return cachedKey; } string guid = Guid.NewGuid().ToString(); - Preferences.Set(PreferencesAnonUserIdKey, guid); + Preferences.Set(PreferencesAnonUserIdKey, guid, log); return guid; } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs index c8c6945c..12ee6d92 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs @@ -28,7 +28,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.OS; using Debug = System.Diagnostics.Debug; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal partial class Connectivity { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs index c7196f6c..658d8b21 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs @@ -27,7 +27,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using CoreFoundation; using SystemConfiguration; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Connectivity { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs index 614bc3ea..f8bd3f07 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same Connectivity abstraction. // It is a stub that always reports that we do have network connectivity. diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs index ea018a7d..15038182 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs @@ -24,7 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Collections.Generic; using System.Linq; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal enum ConnectionProfile { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs index 0977c33c..45fc2c07 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs @@ -2,7 +2,7 @@ using System.Net.Http; using Xamarin.Android.Net; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Http { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs index 98294fca..2cdd6d0a 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Http { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs index baa85df1..2f92a9af 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Http { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs index 2865a911..9163a96d 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Http { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs index 0efe66d4..fc3f7216 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs @@ -28,7 +28,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.Content.PM; using Android.OS; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. // Note that we are no longer using the shared Permissions abstraction at all; this Android code is being used directly diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs index 1faed91c..12c6be79 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs @@ -20,7 +20,7 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal enum PermissionStatus { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs index 7e357723..e9a34400 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs @@ -34,7 +34,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class Platform { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs index 54a3f996..5c1ba508 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs @@ -20,7 +20,7 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { #if !NETSTANDARD internal static partial class Platform diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs index 0325457b..e13d986a 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs @@ -25,8 +25,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Android.App; using Android.Content; using Android.Preferences; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby @@ -36,7 +37,7 @@ internal static partial class Preferences { static readonly object locker = new object(); - static bool PlatformContainsKey(string key, string sharedName) + static bool PlatformContainsKey(string key, string sharedName, Logger log) { lock (locker) { @@ -47,7 +48,7 @@ static bool PlatformContainsKey(string key, string sharedName) } } - static void PlatformRemove(string key, string sharedName) + static void PlatformRemove(string key, string sharedName, Logger log) { lock (locker) { @@ -59,7 +60,7 @@ static void PlatformRemove(string key, string sharedName) } } - static void PlatformClear(string sharedName) + static void PlatformClear(string sharedName, Logger log) { lock (locker) { @@ -71,7 +72,7 @@ static void PlatformClear(string sharedName) } } - static void PlatformSet(string key, string value, string sharedName) + static void PlatformSet(string key, string value, string sharedName, Logger log) { lock (locker) { @@ -91,7 +92,7 @@ static void PlatformSet(string key, string value, string sharedName) } } - static string PlatformGet(string key, string defaultValue, string sharedName) + static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) { lock (locker) { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs index 90392ea5..fde3015f 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs @@ -23,8 +23,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Globalization; using Foundation; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby @@ -34,7 +35,7 @@ internal static partial class Preferences { static readonly object locker = new object(); - static bool PlatformContainsKey(string key, string sharedName) + static bool PlatformContainsKey(string key, string sharedName, Logger log) { lock (locker) { @@ -42,7 +43,7 @@ static bool PlatformContainsKey(string key, string sharedName) } } - static void PlatformRemove(string key, string sharedName) + static void PlatformRemove(string key, string sharedName, Logger log) { lock (locker) { @@ -54,7 +55,7 @@ static void PlatformRemove(string key, string sharedName) } } - static void PlatformClear(string sharedName) + static void PlatformClear(string sharedName, Logger log) { lock (locker) { @@ -71,7 +72,7 @@ static void PlatformClear(string sharedName) } } - static void PlatformSet(string key, string value, string sharedName) + static void PlatformSet(string key, string value, string sharedName, Logger log) { lock (locker) { @@ -89,7 +90,7 @@ static void PlatformSet(string key, string value, string sharedName) } } - static string PlatformGet(string key, string defaultValue, string sharedName) + static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) { lock (locker) { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs index e8225639..e090b69b 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs @@ -5,11 +5,11 @@ using System.IO.IsolatedStorage; using System.Linq; using System.Text; -using Common.Logging; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; #endif -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // This code is not from Xamarin Essentials, though it implements the same Preferences abstraction. // @@ -25,29 +25,16 @@ namespace LaunchDarkly.Xamarin.PlatformSpecific internal static partial class Preferences { -#if NETSTANDARD1_6 - static bool PlatformContainsKey(string key, string sharedName) => false; - - static void PlatformRemove(string key, string sharedName) { } - - static void PlatformClear(string sharedName) { } - - static void PlatformSet(string key, string value, string sharedName) { } - - static string PlatformGet(string key, string defaultValue, string sharedName) => defaultValue; -#else - private static readonly ILog Log = LogManager.GetLogger(typeof(Preferences)); - private static AtomicBoolean _loggedOSError = new AtomicBoolean(false); // AtomicBoolean is defined in LaunchDarkly.CommonSdk private const string ConfigDirectoryName = "LaunchDarkly"; - static bool PlatformContainsKey(string key, string sharedName) + static bool PlatformContainsKey(string key, string sharedName, Logger log) { - return WithStore(store => store.FileExists(MakeFilePath(key, sharedName))); + return WithStore(store => store.FileExists(MakeFilePath(key, sharedName)), log); } - static void PlatformRemove(string key, string sharedName) + static void PlatformRemove(string key, string sharedName, Logger log) { WithStore(store => { @@ -56,10 +43,10 @@ static void PlatformRemove(string key, string sharedName) store.DeleteFile(MakeFilePath(key, sharedName)); } catch (IsolatedStorageException) { } // file didn't exist - that's OK - }); + }, log); } - static void PlatformClear(string sharedName) + static void PlatformClear(string sharedName, Logger log) { WithStore(store => { @@ -69,10 +56,10 @@ static void PlatformClear(string sharedName) // The directory will be recreated next time PlatformSet is called with the same sharedName. } catch (IsolatedStorageException) { } // directory didn't exist - that's OK - }); + }, log); } - static void PlatformSet(string key, string value, string sharedName) + static void PlatformSet(string key, string value, string sharedName, Logger log) { WithStore(store => { @@ -85,10 +72,10 @@ static void PlatformSet(string key, string value, string sharedName) sw.Write(value); } } - }); + }, log); } - static string PlatformGet(string key, string defaultValue, string sharedName) + static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) { return WithStore(store => { @@ -105,10 +92,10 @@ static string PlatformGet(string key, string defaultValue, string sharedName) catch (DirectoryNotFoundException) { } // just return null if no preferences have ever been set catch (FileNotFoundException) { } // just return null if this preference was never set return null; - }); + }, log); } - private static T WithStore(Func callback) + private static T WithStore(Func callback, Logger log) { try { @@ -118,12 +105,12 @@ private static T WithStore(Func callback) } catch (Exception e) { - HandleStoreException(e); + HandleStoreException(e, log); return default; } } - private static void HandleStoreException(Exception e) + private static void HandleStoreException(Exception e, Logger log) { if (e is IsolatedStorageException || e is InvalidOperationException) @@ -139,25 +126,24 @@ private static void HandleStoreException(Exception e) if (!_loggedOSError.GetAndSet(true)) { - Log.WarnFormat("Persistent storage is unavailable and has been disabled ({0}: {1})", e.GetType(), e.Message); + log.Warn("Persistent storage is unavailable and has been disabled ({0}: {1})", e.GetType(), e.Message); } } else { // All other errors probably indicate an error in our own code. We don't want to throw these up // into the SDK; the Preferences API is expected to either work or silently fail. - Log.ErrorFormat("Error in accessing persistent storage: {0}: {1}", e.GetType(), e.Message); - Log.Debug(e.StackTrace); + LogHelpers.LogException(log, "Error in accessing persistent storage", e); } } - private static void WithStore(Action callback) + private static void WithStore(Action callback, Logger log) { WithStore(store => { callback(store); return true; - }); + }, log); } private static string MakeDirectoryPath(string sharedName) @@ -200,6 +186,5 @@ private static string EscapeFilenameComponent(string name) } return buf == null ? name : buf.ToString(); } -#endif } } diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs index 9314a6b0..2491994a 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs @@ -20,9 +20,9 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -using System; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class // to store them. Therefore, the overloads for non-string types have been removed, thereby @@ -35,17 +35,17 @@ internal static string GetPrivatePreferencesSharedName(string feature) => // overloads - public static bool ContainsKey(string key) => - ContainsKey(key, null); + public static bool ContainsKey(string key, Logger log) => + ContainsKey(key, null, log); - public static void Remove(string key) => - Remove(key, null); + public static void Remove(string key, Logger log) => + Remove(key, null, log); - public static void Clear() => - Clear(null); + public static void Clear(Logger log) => + Clear(null, log); - public static string Get(string key, string defaultValue) => - Get(key, defaultValue, null); + public static string Get(string key, string defaultValue, Logger log) => + Get(key, defaultValue, null, log); //public static bool Get(string key, bool defaultValue) => // Get(key, defaultValue, null); @@ -62,8 +62,8 @@ public static string Get(string key, string defaultValue) => //public static long Get(string key, long defaultValue) => // Get(key, defaultValue, null); - public static void Set(string key, string value) => - Set(key, value, null); + public static void Set(string key, string value, Logger log) => + Set(key, value, null, log); //public static void Set(string key, bool value) => // Set(key, value, null); @@ -82,17 +82,17 @@ public static void Set(string key, string value) => // shared -> platform - public static bool ContainsKey(string key, string sharedName) => - PlatformContainsKey(key, sharedName); + public static bool ContainsKey(string key, string sharedName, Logger log) => + PlatformContainsKey(key, sharedName, log); - public static void Remove(string key, string sharedName) => - PlatformRemove(key, sharedName); + public static void Remove(string key, string sharedName, Logger log) => + PlatformRemove(key, sharedName, log); - public static void Clear(string sharedName) => - PlatformClear(sharedName); + public static void Clear(string sharedName, Logger log) => + PlatformClear(sharedName, log); - public static string Get(string key, string defaultValue, string sharedName) => - PlatformGet(key, defaultValue, sharedName); + public static string Get(string key, string defaultValue, string sharedName, Logger log) => + PlatformGet(key, defaultValue, sharedName, log); //public static bool Get(string key, bool defaultValue, string sharedName) => // PlatformGet(key, defaultValue, sharedName); @@ -109,8 +109,8 @@ public static string Get(string key, string defaultValue, string sharedName) => //public static long Get(string key, long defaultValue, string sharedName) => // PlatformGet(key, defaultValue, sharedName); - public static void Set(string key, string value, string sharedName) => - PlatformSet(key, value, sharedName); + public static void Set(string key, string value, string sharedName, Logger log) => + PlatformSet(key, value, sharedName, log); //public static void Set(string key, bool value, string sharedName) => // PlatformSet(key, value, sharedName); diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs index 6753aa8b..139ccfad 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs @@ -1,6 +1,6 @@ using Android.OS; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs index 5f5cd365..ae6384c7 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs @@ -1,6 +1,6 @@ using UIKit; -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs index 6b6a2f07..5dec9258 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { internal static partial class UserMetadata { diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs index 90152c3f..41c20aef 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Xamarin.PlatformSpecific +namespace LaunchDarkly.Sdk.Xamarin.PlatformSpecific { // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. diff --git a/src/LaunchDarkly.XamarinSdk/PlatformType.cs b/src/LaunchDarkly.XamarinSdk/PlatformType.cs index 9c34ff70..994460ba 100644 --- a/src/LaunchDarkly.XamarinSdk/PlatformType.cs +++ b/src/LaunchDarkly.XamarinSdk/PlatformType.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { /// /// Values returned by . diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs index 8ddfe823..08142209 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Immutable; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Common; +using LaunchDarkly.Logging; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class UserFlagDeviceCache : IUserFlagCache { - private static readonly ILog Log = LogManager.GetLogger(typeof(UserFlagDeviceCache)); private readonly IPersistentStorage persister; + private readonly Logger _log; - public UserFlagDeviceCache(IPersistentStorage persister) + public UserFlagDeviceCache(IPersistentStorage persister, Logger log) { this.persister = persister; + _log = log; } void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) @@ -25,8 +24,8 @@ void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary } catch (System.Exception ex) { - Log.ErrorFormat("Couldn't set preferences on mobile device: {0}", - Util.ExceptionMessage(ex)); + _log.Error("Couldn't set preferences on mobile device: {0}", + LogValues.ExceptionSummary(ex)); } } @@ -42,8 +41,8 @@ IImmutableDictionary IUserFlagCache.RetrieveFlags(User user } catch (Exception ex) { - Log.ErrorFormat("Couldn't get preferences on mobile device: {0}", - Util.ExceptionMessage(ex)); + _log.Error("Couldn't get preferences on mobile device: {0}", + LogValues.ExceptionSummary(ex)); } return ImmutableDictionary.Create(); diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs index d6e7f373..4bd8442d 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs @@ -1,8 +1,7 @@ using System.Collections.Concurrent; using System.Collections.Immutable; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin +namespace LaunchDarkly.Sdk.Xamarin { internal sealed class UserFlagInMemoryCache : IUserFlagCache { diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs index 7c3c1e07..07260ba7 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs @@ -1,7 +1,6 @@ -using LaunchDarkly.Client; -using Xunit; +using Xunit; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class AndroidSpecificTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 4c44dbbb..aaec15d2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -76,9 +76,6 @@ SharedTestCode\FeatureFlagRequestorTests.cs - - SharedTestCode\FeatureFlagTests.cs - SharedTestCode\FlagCacheManagerTests.cs @@ -100,15 +97,15 @@ SharedTestCode\LdClientTests.cs - - SharedTestCode\LogSink.cs - SharedTestCode\MobilePollingProcessorTests.cs SharedTestCode\MockComponents.cs + + SharedTestCode\TestLogging.cs + SharedTestCode\TestUtil.cs @@ -148,9 +145,7 @@ - - diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs index bdd99699..f70bc24c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,7 +2,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -15,7 +14,7 @@ namespace LaunchDarkly.XamarinSdk.Android.Tests { - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] public partial class Resource { @@ -26,107 +25,6 @@ static Resource() public static void UpdateIdValues() { - global::LaunchDarkly.XamarinSdk.Resource.Attribute.font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.font; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderCerts; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderPackage; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontProviderQuery; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Attribute.fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Attribute.fontWeight; - global::LaunchDarkly.XamarinSdk.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::LaunchDarkly.XamarinSdk.Resource.Color.notification_action_color_filter = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_action_color_filter; - global::LaunchDarkly.XamarinSdk.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.notification_icon_bg_color; - global::LaunchDarkly.XamarinSdk.Resource.Color.ripple_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.ripple_material_light; - global::LaunchDarkly.XamarinSdk.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_action_text_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_subtext_size; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad; - global::LaunchDarkly.XamarinSdk.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_action_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_action_background; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_icon_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_icon_background; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notification_tile_bg; - global::LaunchDarkly.XamarinSdk.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_container; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_divider = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_divider; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_image = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_image; - global::LaunchDarkly.XamarinSdk.Resource.Id.action_text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.action_text; - global::LaunchDarkly.XamarinSdk.Resource.Id.actions = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.actions; - global::LaunchDarkly.XamarinSdk.Resource.Id.async = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.async; - global::LaunchDarkly.XamarinSdk.Resource.Id.blocking = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.blocking; - global::LaunchDarkly.XamarinSdk.Resource.Id.chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.chronometer; - global::LaunchDarkly.XamarinSdk.Resource.Id.forever = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.forever; - global::LaunchDarkly.XamarinSdk.Resource.Id.icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon; - global::LaunchDarkly.XamarinSdk.Resource.Id.icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.icon_group; - global::LaunchDarkly.XamarinSdk.Resource.Id.info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.info; - global::LaunchDarkly.XamarinSdk.Resource.Id.italic = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.italic; - global::LaunchDarkly.XamarinSdk.Resource.Id.line1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line1; - global::LaunchDarkly.XamarinSdk.Resource.Id.line3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.line3; - global::LaunchDarkly.XamarinSdk.Resource.Id.normal = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.normal; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_background; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column; - global::LaunchDarkly.XamarinSdk.Resource.Id.notification_main_column_container = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.notification_main_column_container; - global::LaunchDarkly.XamarinSdk.Resource.Id.right_icon = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_icon; - global::LaunchDarkly.XamarinSdk.Resource.Id.right_side = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.right_side; - global::LaunchDarkly.XamarinSdk.Resource.Id.tag_transition_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.tag_transition_group; - global::LaunchDarkly.XamarinSdk.Resource.Id.text = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text; - global::LaunchDarkly.XamarinSdk.Resource.Id.text2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.text2; - global::LaunchDarkly.XamarinSdk.Resource.Id.time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.time; - global::LaunchDarkly.XamarinSdk.Resource.Id.title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Id.title; - global::LaunchDarkly.XamarinSdk.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_action_tombstone; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_custom_big; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_icon_group; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::LaunchDarkly.XamarinSdk.Resource.Layout.notification_template_part_time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Layout.notification_template_part_time; - global::LaunchDarkly.XamarinSdk.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::LaunchDarkly.XamarinSdk.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::LaunchDarkly.XamarinSdk.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::LaunchDarkly.XamarinSdk.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; @@ -851,13 +749,7 @@ public static void UpdateIdValues() global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_material = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_textfield_search_material; global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_vector_test = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.abc_vector_test; global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_1; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_2; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password_3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_hide_password_3; global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_1 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_1; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_2 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_2; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password_3 = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.avd_show_password_3; global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_bottom_navigation_item_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_bottom_navigation_item_background; global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_fab_background = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_fab_background; global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility = global::LaunchDarkly.XamarinSdk.Android.Tests.Resource.Drawable.design_ic_visibility; @@ -2124,65 +2016,65 @@ public static void UpdateIdValues() public partial class Animation { - // aapt resource value: 0x7f050000 - public const int abc_fade_in = 2131034112; + // aapt resource value: 0x7F010000 + public const int abc_fade_in = 2130771968; - // aapt resource value: 0x7f050001 - public const int abc_fade_out = 2131034113; + // aapt resource value: 0x7F010001 + public const int abc_fade_out = 2130771969; - // aapt resource value: 0x7f050002 - public const int abc_grow_fade_in_from_bottom = 2131034114; + // aapt resource value: 0x7F010002 + public const int abc_grow_fade_in_from_bottom = 2130771970; - // aapt resource value: 0x7f050003 - public const int abc_popup_enter = 2131034115; + // aapt resource value: 0x7F010003 + public const int abc_popup_enter = 2130771971; - // aapt resource value: 0x7f050004 - public const int abc_popup_exit = 2131034116; + // aapt resource value: 0x7F010004 + public const int abc_popup_exit = 2130771972; - // aapt resource value: 0x7f050005 - public const int abc_shrink_fade_out_from_bottom = 2131034117; + // aapt resource value: 0x7F010005 + public const int abc_shrink_fade_out_from_bottom = 2130771973; - // aapt resource value: 0x7f050006 - public const int abc_slide_in_bottom = 2131034118; + // aapt resource value: 0x7F010006 + public const int abc_slide_in_bottom = 2130771974; - // aapt resource value: 0x7f050007 - public const int abc_slide_in_top = 2131034119; + // aapt resource value: 0x7F010007 + public const int abc_slide_in_top = 2130771975; - // aapt resource value: 0x7f050008 - public const int abc_slide_out_bottom = 2131034120; + // aapt resource value: 0x7F010008 + public const int abc_slide_out_bottom = 2130771976; - // aapt resource value: 0x7f050009 - public const int abc_slide_out_top = 2131034121; + // aapt resource value: 0x7F010009 + public const int abc_slide_out_top = 2130771977; - // aapt resource value: 0x7f05000a - public const int design_bottom_sheet_slide_in = 2131034122; + // aapt resource value: 0x7F01000A + public const int design_bottom_sheet_slide_in = 2130771978; - // aapt resource value: 0x7f05000b - public const int design_bottom_sheet_slide_out = 2131034123; + // aapt resource value: 0x7F01000B + public const int design_bottom_sheet_slide_out = 2130771979; - // aapt resource value: 0x7f05000c - public const int design_snackbar_in = 2131034124; + // aapt resource value: 0x7F01000C + public const int design_snackbar_in = 2130771980; - // aapt resource value: 0x7f05000d - public const int design_snackbar_out = 2131034125; + // aapt resource value: 0x7F01000D + public const int design_snackbar_out = 2130771981; - // aapt resource value: 0x7f05000e - public const int EnterFromLeft = 2131034126; + // aapt resource value: 0x7F01000E + public const int EnterFromLeft = 2130771982; - // aapt resource value: 0x7f05000f - public const int EnterFromRight = 2131034127; + // aapt resource value: 0x7F01000F + public const int EnterFromRight = 2130771983; - // aapt resource value: 0x7f050010 - public const int ExitToLeft = 2131034128; + // aapt resource value: 0x7F010010 + public const int ExitToLeft = 2130771984; - // aapt resource value: 0x7f050011 - public const int ExitToRight = 2131034129; + // aapt resource value: 0x7F010011 + public const int ExitToRight = 2130771985; - // aapt resource value: 0x7f050012 - public const int tooltip_enter = 2131034130; + // aapt resource value: 0x7F010012 + public const int tooltip_enter = 2130771986; - // aapt resource value: 0x7f050013 - public const int tooltip_exit = 2131034131; + // aapt resource value: 0x7F010013 + public const int tooltip_exit = 2130771987; static Animation() { @@ -2197,8 +2089,8 @@ private Animation() public partial class Animator { - // aapt resource value: 0x7f060000 - public const int design_appbar_state_list_animator = 2131099648; + // aapt resource value: 0x7F020000 + public const int design_appbar_state_list_animator = 2130837504; static Animator() { @@ -2213,1118 +2105,1118 @@ private Animator() public partial class Attribute { - // aapt resource value: 0x7f01006b - public const int actionBarDivider = 2130772075; + // aapt resource value: 0x7F030000 + public const int actionBarDivider = 2130903040; - // aapt resource value: 0x7f01006c - public const int actionBarItemBackground = 2130772076; + // aapt resource value: 0x7F030001 + public const int actionBarItemBackground = 2130903041; - // aapt resource value: 0x7f010065 - public const int actionBarPopupTheme = 2130772069; + // aapt resource value: 0x7F030002 + public const int actionBarPopupTheme = 2130903042; - // aapt resource value: 0x7f01006a - public const int actionBarSize = 2130772074; + // aapt resource value: 0x7F030003 + public const int actionBarSize = 2130903043; - // aapt resource value: 0x7f010067 - public const int actionBarSplitStyle = 2130772071; + // aapt resource value: 0x7F030004 + public const int actionBarSplitStyle = 2130903044; - // aapt resource value: 0x7f010066 - public const int actionBarStyle = 2130772070; + // aapt resource value: 0x7F030005 + public const int actionBarStyle = 2130903045; - // aapt resource value: 0x7f010061 - public const int actionBarTabBarStyle = 2130772065; + // aapt resource value: 0x7F030006 + public const int actionBarTabBarStyle = 2130903046; - // aapt resource value: 0x7f010060 - public const int actionBarTabStyle = 2130772064; + // aapt resource value: 0x7F030007 + public const int actionBarTabStyle = 2130903047; - // aapt resource value: 0x7f010062 - public const int actionBarTabTextStyle = 2130772066; + // aapt resource value: 0x7F030008 + public const int actionBarTabTextStyle = 2130903048; - // aapt resource value: 0x7f010068 - public const int actionBarTheme = 2130772072; + // aapt resource value: 0x7F030009 + public const int actionBarTheme = 2130903049; - // aapt resource value: 0x7f010069 - public const int actionBarWidgetTheme = 2130772073; + // aapt resource value: 0x7F03000A + public const int actionBarWidgetTheme = 2130903050; - // aapt resource value: 0x7f010086 - public const int actionButtonStyle = 2130772102; + // aapt resource value: 0x7F03000B + public const int actionButtonStyle = 2130903051; - // aapt resource value: 0x7f010082 - public const int actionDropDownStyle = 2130772098; + // aapt resource value: 0x7F03000C + public const int actionDropDownStyle = 2130903052; - // aapt resource value: 0x7f0100dd - public const int actionLayout = 2130772189; + // aapt resource value: 0x7F03000D + public const int actionLayout = 2130903053; - // aapt resource value: 0x7f01006d - public const int actionMenuTextAppearance = 2130772077; + // aapt resource value: 0x7F03000E + public const int actionMenuTextAppearance = 2130903054; - // aapt resource value: 0x7f01006e - public const int actionMenuTextColor = 2130772078; + // aapt resource value: 0x7F03000F + public const int actionMenuTextColor = 2130903055; - // aapt resource value: 0x7f010071 - public const int actionModeBackground = 2130772081; + // aapt resource value: 0x7F030010 + public const int actionModeBackground = 2130903056; - // aapt resource value: 0x7f010070 - public const int actionModeCloseButtonStyle = 2130772080; + // aapt resource value: 0x7F030011 + public const int actionModeCloseButtonStyle = 2130903057; - // aapt resource value: 0x7f010073 - public const int actionModeCloseDrawable = 2130772083; + // aapt resource value: 0x7F030012 + public const int actionModeCloseDrawable = 2130903058; - // aapt resource value: 0x7f010075 - public const int actionModeCopyDrawable = 2130772085; + // aapt resource value: 0x7F030013 + public const int actionModeCopyDrawable = 2130903059; - // aapt resource value: 0x7f010074 - public const int actionModeCutDrawable = 2130772084; + // aapt resource value: 0x7F030014 + public const int actionModeCutDrawable = 2130903060; - // aapt resource value: 0x7f010079 - public const int actionModeFindDrawable = 2130772089; + // aapt resource value: 0x7F030015 + public const int actionModeFindDrawable = 2130903061; - // aapt resource value: 0x7f010076 - public const int actionModePasteDrawable = 2130772086; + // aapt resource value: 0x7F030016 + public const int actionModePasteDrawable = 2130903062; - // aapt resource value: 0x7f01007b - public const int actionModePopupWindowStyle = 2130772091; + // aapt resource value: 0x7F030017 + public const int actionModePopupWindowStyle = 2130903063; - // aapt resource value: 0x7f010077 - public const int actionModeSelectAllDrawable = 2130772087; + // aapt resource value: 0x7F030018 + public const int actionModeSelectAllDrawable = 2130903064; - // aapt resource value: 0x7f010078 - public const int actionModeShareDrawable = 2130772088; + // aapt resource value: 0x7F030019 + public const int actionModeShareDrawable = 2130903065; - // aapt resource value: 0x7f010072 - public const int actionModeSplitBackground = 2130772082; + // aapt resource value: 0x7F03001A + public const int actionModeSplitBackground = 2130903066; - // aapt resource value: 0x7f01006f - public const int actionModeStyle = 2130772079; + // aapt resource value: 0x7F03001B + public const int actionModeStyle = 2130903067; - // aapt resource value: 0x7f01007a - public const int actionModeWebSearchDrawable = 2130772090; + // aapt resource value: 0x7F03001C + public const int actionModeWebSearchDrawable = 2130903068; - // aapt resource value: 0x7f010063 - public const int actionOverflowButtonStyle = 2130772067; + // aapt resource value: 0x7F03001D + public const int actionOverflowButtonStyle = 2130903069; - // aapt resource value: 0x7f010064 - public const int actionOverflowMenuStyle = 2130772068; + // aapt resource value: 0x7F03001E + public const int actionOverflowMenuStyle = 2130903070; - // aapt resource value: 0x7f0100df - public const int actionProviderClass = 2130772191; + // aapt resource value: 0x7F03001F + public const int actionProviderClass = 2130903071; - // aapt resource value: 0x7f0100de - public const int actionViewClass = 2130772190; + // aapt resource value: 0x7F030020 + public const int actionViewClass = 2130903072; - // aapt resource value: 0x7f01008e - public const int activityChooserViewStyle = 2130772110; + // aapt resource value: 0x7F030021 + public const int activityChooserViewStyle = 2130903073; - // aapt resource value: 0x7f0100b3 - public const int alertDialogButtonGroupStyle = 2130772147; + // aapt resource value: 0x7F030022 + public const int alertDialogButtonGroupStyle = 2130903074; - // aapt resource value: 0x7f0100b4 - public const int alertDialogCenterButtons = 2130772148; + // aapt resource value: 0x7F030023 + public const int alertDialogCenterButtons = 2130903075; - // aapt resource value: 0x7f0100b2 - public const int alertDialogStyle = 2130772146; + // aapt resource value: 0x7F030024 + public const int alertDialogStyle = 2130903076; - // aapt resource value: 0x7f0100b5 - public const int alertDialogTheme = 2130772149; + // aapt resource value: 0x7F030025 + public const int alertDialogTheme = 2130903077; - // aapt resource value: 0x7f0100cb - public const int allowStacking = 2130772171; + // aapt resource value: 0x7F030026 + public const int allowStacking = 2130903078; - // aapt resource value: 0x7f0100cc - public const int alpha = 2130772172; + // aapt resource value: 0x7F030027 + public const int alpha = 2130903079; - // aapt resource value: 0x7f0100da - public const int alphabeticModifiers = 2130772186; + // aapt resource value: 0x7F030028 + public const int alphabeticModifiers = 2130903080; - // aapt resource value: 0x7f0100d3 - public const int arrowHeadLength = 2130772179; + // aapt resource value: 0x7F030029 + public const int arrowHeadLength = 2130903081; - // aapt resource value: 0x7f0100d4 - public const int arrowShaftLength = 2130772180; + // aapt resource value: 0x7F03002A + public const int arrowShaftLength = 2130903082; - // aapt resource value: 0x7f0100ba - public const int autoCompleteTextViewStyle = 2130772154; + // aapt resource value: 0x7F03002B + public const int autoCompleteTextViewStyle = 2130903083; - // aapt resource value: 0x7f010054 - public const int autoSizeMaxTextSize = 2130772052; + // aapt resource value: 0x7F03002C + public const int autoSizeMaxTextSize = 2130903084; - // aapt resource value: 0x7f010053 - public const int autoSizeMinTextSize = 2130772051; + // aapt resource value: 0x7F03002D + public const int autoSizeMinTextSize = 2130903085; - // aapt resource value: 0x7f010052 - public const int autoSizePresetSizes = 2130772050; + // aapt resource value: 0x7F03002E + public const int autoSizePresetSizes = 2130903086; - // aapt resource value: 0x7f010051 - public const int autoSizeStepGranularity = 2130772049; + // aapt resource value: 0x7F03002F + public const int autoSizeStepGranularity = 2130903087; - // aapt resource value: 0x7f010050 - public const int autoSizeTextType = 2130772048; + // aapt resource value: 0x7F030030 + public const int autoSizeTextType = 2130903088; - // aapt resource value: 0x7f01002e - public const int background = 2130772014; + // aapt resource value: 0x7F030031 + public const int background = 2130903089; - // aapt resource value: 0x7f010030 - public const int backgroundSplit = 2130772016; + // aapt resource value: 0x7F030032 + public const int backgroundSplit = 2130903090; - // aapt resource value: 0x7f01002f - public const int backgroundStacked = 2130772015; + // aapt resource value: 0x7F030033 + public const int backgroundStacked = 2130903091; - // aapt resource value: 0x7f010116 - public const int backgroundTint = 2130772246; + // aapt resource value: 0x7F030034 + public const int backgroundTint = 2130903092; - // aapt resource value: 0x7f010117 - public const int backgroundTintMode = 2130772247; + // aapt resource value: 0x7F030035 + public const int backgroundTintMode = 2130903093; - // aapt resource value: 0x7f0100d5 - public const int barLength = 2130772181; + // aapt resource value: 0x7F030036 + public const int barLength = 2130903094; - // aapt resource value: 0x7f010141 - public const int behavior_autoHide = 2130772289; + // aapt resource value: 0x7F030037 + public const int behavior_autoHide = 2130903095; - // aapt resource value: 0x7f01011e - public const int behavior_hideable = 2130772254; + // aapt resource value: 0x7F030038 + public const int behavior_hideable = 2130903096; - // aapt resource value: 0x7f01014a - public const int behavior_overlapTop = 2130772298; + // aapt resource value: 0x7F030039 + public const int behavior_overlapTop = 2130903097; - // aapt resource value: 0x7f01011d - public const int behavior_peekHeight = 2130772253; + // aapt resource value: 0x7F03003A + public const int behavior_peekHeight = 2130903098; - // aapt resource value: 0x7f01011f - public const int behavior_skipCollapsed = 2130772255; + // aapt resource value: 0x7F03003B + public const int behavior_skipCollapsed = 2130903099; - // aapt resource value: 0x7f01013f - public const int borderWidth = 2130772287; + // aapt resource value: 0x7F03003D + public const int borderlessButtonStyle = 2130903101; - // aapt resource value: 0x7f01008b - public const int borderlessButtonStyle = 2130772107; + // aapt resource value: 0x7F03003C + public const int borderWidth = 2130903100; - // aapt resource value: 0x7f010139 - public const int bottomSheetDialogTheme = 2130772281; + // aapt resource value: 0x7F03003E + public const int bottomSheetDialogTheme = 2130903102; - // aapt resource value: 0x7f01013a - public const int bottomSheetStyle = 2130772282; + // aapt resource value: 0x7F03003F + public const int bottomSheetStyle = 2130903103; - // aapt resource value: 0x7f010088 - public const int buttonBarButtonStyle = 2130772104; + // aapt resource value: 0x7F030040 + public const int buttonBarButtonStyle = 2130903104; - // aapt resource value: 0x7f0100b8 - public const int buttonBarNegativeButtonStyle = 2130772152; + // aapt resource value: 0x7F030041 + public const int buttonBarNegativeButtonStyle = 2130903105; - // aapt resource value: 0x7f0100b9 - public const int buttonBarNeutralButtonStyle = 2130772153; + // aapt resource value: 0x7F030042 + public const int buttonBarNeutralButtonStyle = 2130903106; - // aapt resource value: 0x7f0100b7 - public const int buttonBarPositiveButtonStyle = 2130772151; + // aapt resource value: 0x7F030043 + public const int buttonBarPositiveButtonStyle = 2130903107; - // aapt resource value: 0x7f010087 - public const int buttonBarStyle = 2130772103; + // aapt resource value: 0x7F030044 + public const int buttonBarStyle = 2130903108; - // aapt resource value: 0x7f01010b - public const int buttonGravity = 2130772235; + // aapt resource value: 0x7F030045 + public const int buttonGravity = 2130903109; - // aapt resource value: 0x7f010043 - public const int buttonPanelSideLayout = 2130772035; + // aapt resource value: 0x7F030046 + public const int buttonPanelSideLayout = 2130903110; - // aapt resource value: 0x7f0100bb - public const int buttonStyle = 2130772155; + // aapt resource value: 0x7F030047 + public const int buttonStyle = 2130903111; - // aapt resource value: 0x7f0100bc - public const int buttonStyleSmall = 2130772156; + // aapt resource value: 0x7F030048 + public const int buttonStyleSmall = 2130903112; - // aapt resource value: 0x7f0100cd - public const int buttonTint = 2130772173; + // aapt resource value: 0x7F030049 + public const int buttonTint = 2130903113; - // aapt resource value: 0x7f0100ce - public const int buttonTintMode = 2130772174; + // aapt resource value: 0x7F03004A + public const int buttonTintMode = 2130903114; - // aapt resource value: 0x7f010017 - public const int cardBackgroundColor = 2130771991; + // aapt resource value: 0x7F03004B + public const int cardBackgroundColor = 2130903115; - // aapt resource value: 0x7f010018 - public const int cardCornerRadius = 2130771992; + // aapt resource value: 0x7F03004C + public const int cardCornerRadius = 2130903116; - // aapt resource value: 0x7f010019 - public const int cardElevation = 2130771993; + // aapt resource value: 0x7F03004D + public const int cardElevation = 2130903117; - // aapt resource value: 0x7f01001a - public const int cardMaxElevation = 2130771994; + // aapt resource value: 0x7F03004E + public const int cardMaxElevation = 2130903118; - // aapt resource value: 0x7f01001c - public const int cardPreventCornerOverlap = 2130771996; + // aapt resource value: 0x7F03004F + public const int cardPreventCornerOverlap = 2130903119; - // aapt resource value: 0x7f01001b - public const int cardUseCompatPadding = 2130771995; + // aapt resource value: 0x7F030050 + public const int cardUseCompatPadding = 2130903120; - // aapt resource value: 0x7f0100bd - public const int checkboxStyle = 2130772157; + // aapt resource value: 0x7F030051 + public const int checkboxStyle = 2130903121; - // aapt resource value: 0x7f0100be - public const int checkedTextViewStyle = 2130772158; + // aapt resource value: 0x7F030052 + public const int checkedTextViewStyle = 2130903122; - // aapt resource value: 0x7f0100ee - public const int closeIcon = 2130772206; + // aapt resource value: 0x7F030053 + public const int closeIcon = 2130903123; - // aapt resource value: 0x7f010040 - public const int closeItemLayout = 2130772032; + // aapt resource value: 0x7F030054 + public const int closeItemLayout = 2130903124; - // aapt resource value: 0x7f01010d - public const int collapseContentDescription = 2130772237; + // aapt resource value: 0x7F030055 + public const int collapseContentDescription = 2130903125; - // aapt resource value: 0x7f01010c - public const int collapseIcon = 2130772236; + // aapt resource value: 0x7F030057 + public const int collapsedTitleGravity = 2130903127; - // aapt resource value: 0x7f01012c - public const int collapsedTitleGravity = 2130772268; + // aapt resource value: 0x7F030058 + public const int collapsedTitleTextAppearance = 2130903128; - // aapt resource value: 0x7f010126 - public const int collapsedTitleTextAppearance = 2130772262; + // aapt resource value: 0x7F030056 + public const int collapseIcon = 2130903126; - // aapt resource value: 0x7f0100cf - public const int color = 2130772175; + // aapt resource value: 0x7F030059 + public const int color = 2130903129; - // aapt resource value: 0x7f0100aa - public const int colorAccent = 2130772138; + // aapt resource value: 0x7F03005A + public const int colorAccent = 2130903130; - // aapt resource value: 0x7f0100b1 - public const int colorBackgroundFloating = 2130772145; + // aapt resource value: 0x7F03005B + public const int colorBackgroundFloating = 2130903131; - // aapt resource value: 0x7f0100ae - public const int colorButtonNormal = 2130772142; + // aapt resource value: 0x7F03005C + public const int colorButtonNormal = 2130903132; - // aapt resource value: 0x7f0100ac - public const int colorControlActivated = 2130772140; + // aapt resource value: 0x7F03005D + public const int colorControlActivated = 2130903133; - // aapt resource value: 0x7f0100ad - public const int colorControlHighlight = 2130772141; + // aapt resource value: 0x7F03005E + public const int colorControlHighlight = 2130903134; - // aapt resource value: 0x7f0100ab - public const int colorControlNormal = 2130772139; + // aapt resource value: 0x7F03005F + public const int colorControlNormal = 2130903135; - // aapt resource value: 0x7f0100ca - public const int colorError = 2130772170; + // aapt resource value: 0x7F030060 + public const int colorError = 2130903136; - // aapt resource value: 0x7f0100a8 - public const int colorPrimary = 2130772136; + // aapt resource value: 0x7F030061 + public const int colorPrimary = 2130903137; - // aapt resource value: 0x7f0100a9 - public const int colorPrimaryDark = 2130772137; + // aapt resource value: 0x7F030062 + public const int colorPrimaryDark = 2130903138; - // aapt resource value: 0x7f0100af - public const int colorSwitchThumbNormal = 2130772143; + // aapt resource value: 0x7F030063 + public const int colorSwitchThumbNormal = 2130903139; - // aapt resource value: 0x7f0100f3 - public const int commitIcon = 2130772211; + // aapt resource value: 0x7F030064 + public const int commitIcon = 2130903140; - // aapt resource value: 0x7f0100e0 - public const int contentDescription = 2130772192; + // aapt resource value: 0x7F030065 + public const int contentDescription = 2130903141; - // aapt resource value: 0x7f010039 - public const int contentInsetEnd = 2130772025; + // aapt resource value: 0x7F030066 + public const int contentInsetEnd = 2130903142; - // aapt resource value: 0x7f01003d - public const int contentInsetEndWithActions = 2130772029; + // aapt resource value: 0x7F030067 + public const int contentInsetEndWithActions = 2130903143; - // aapt resource value: 0x7f01003a - public const int contentInsetLeft = 2130772026; + // aapt resource value: 0x7F030068 + public const int contentInsetLeft = 2130903144; - // aapt resource value: 0x7f01003b - public const int contentInsetRight = 2130772027; + // aapt resource value: 0x7F030069 + public const int contentInsetRight = 2130903145; - // aapt resource value: 0x7f010038 - public const int contentInsetStart = 2130772024; + // aapt resource value: 0x7F03006A + public const int contentInsetStart = 2130903146; - // aapt resource value: 0x7f01003c - public const int contentInsetStartWithNavigation = 2130772028; + // aapt resource value: 0x7F03006B + public const int contentInsetStartWithNavigation = 2130903147; - // aapt resource value: 0x7f01001d - public const int contentPadding = 2130771997; + // aapt resource value: 0x7F03006C + public const int contentPadding = 2130903148; - // aapt resource value: 0x7f010021 - public const int contentPaddingBottom = 2130772001; + // aapt resource value: 0x7F03006D + public const int contentPaddingBottom = 2130903149; - // aapt resource value: 0x7f01001e - public const int contentPaddingLeft = 2130771998; + // aapt resource value: 0x7F03006E + public const int contentPaddingLeft = 2130903150; - // aapt resource value: 0x7f01001f - public const int contentPaddingRight = 2130771999; + // aapt resource value: 0x7F03006F + public const int contentPaddingRight = 2130903151; - // aapt resource value: 0x7f010020 - public const int contentPaddingTop = 2130772000; + // aapt resource value: 0x7F030070 + public const int contentPaddingTop = 2130903152; - // aapt resource value: 0x7f010127 - public const int contentScrim = 2130772263; + // aapt resource value: 0x7F030071 + public const int contentScrim = 2130903153; - // aapt resource value: 0x7f0100b0 - public const int controlBackground = 2130772144; + // aapt resource value: 0x7F030072 + public const int controlBackground = 2130903154; - // aapt resource value: 0x7f010160 - public const int counterEnabled = 2130772320; + // aapt resource value: 0x7F030073 + public const int counterEnabled = 2130903155; - // aapt resource value: 0x7f010161 - public const int counterMaxLength = 2130772321; + // aapt resource value: 0x7F030074 + public const int counterMaxLength = 2130903156; - // aapt resource value: 0x7f010163 - public const int counterOverflowTextAppearance = 2130772323; + // aapt resource value: 0x7F030075 + public const int counterOverflowTextAppearance = 2130903157; - // aapt resource value: 0x7f010162 - public const int counterTextAppearance = 2130772322; + // aapt resource value: 0x7F030076 + public const int counterTextAppearance = 2130903158; - // aapt resource value: 0x7f010031 - public const int customNavigationLayout = 2130772017; + // aapt resource value: 0x7F030077 + public const int customNavigationLayout = 2130903159; - // aapt resource value: 0x7f0100ed - public const int defaultQueryHint = 2130772205; + // aapt resource value: 0x7F030078 + public const int defaultQueryHint = 2130903160; - // aapt resource value: 0x7f010080 - public const int dialogPreferredPadding = 2130772096; + // aapt resource value: 0x7F030079 + public const int dialogPreferredPadding = 2130903161; - // aapt resource value: 0x7f01007f - public const int dialogTheme = 2130772095; + // aapt resource value: 0x7F03007A + public const int dialogTheme = 2130903162; - // aapt resource value: 0x7f010027 - public const int displayOptions = 2130772007; + // aapt resource value: 0x7F03007B + public const int displayOptions = 2130903163; - // aapt resource value: 0x7f01002d - public const int divider = 2130772013; + // aapt resource value: 0x7F03007C + public const int divider = 2130903164; - // aapt resource value: 0x7f01008d - public const int dividerHorizontal = 2130772109; + // aapt resource value: 0x7F03007D + public const int dividerHorizontal = 2130903165; - // aapt resource value: 0x7f0100d9 - public const int dividerPadding = 2130772185; + // aapt resource value: 0x7F03007E + public const int dividerPadding = 2130903166; - // aapt resource value: 0x7f01008c - public const int dividerVertical = 2130772108; + // aapt resource value: 0x7F03007F + public const int dividerVertical = 2130903167; - // aapt resource value: 0x7f0100d1 - public const int drawableSize = 2130772177; + // aapt resource value: 0x7F030080 + public const int drawableSize = 2130903168; - // aapt resource value: 0x7f010022 - public const int drawerArrowStyle = 2130772002; + // aapt resource value: 0x7F030081 + public const int drawerArrowStyle = 2130903169; - // aapt resource value: 0x7f01009f - public const int dropDownListViewStyle = 2130772127; + // aapt resource value: 0x7F030083 + public const int dropdownListPreferredItemHeight = 2130903171; - // aapt resource value: 0x7f010083 - public const int dropdownListPreferredItemHeight = 2130772099; + // aapt resource value: 0x7F030082 + public const int dropDownListViewStyle = 2130903170; - // aapt resource value: 0x7f010094 - public const int editTextBackground = 2130772116; + // aapt resource value: 0x7F030084 + public const int editTextBackground = 2130903172; - // aapt resource value: 0x7f010093 - public const int editTextColor = 2130772115; + // aapt resource value: 0x7F030085 + public const int editTextColor = 2130903173; - // aapt resource value: 0x7f0100bf - public const int editTextStyle = 2130772159; + // aapt resource value: 0x7F030086 + public const int editTextStyle = 2130903174; - // aapt resource value: 0x7f01003e - public const int elevation = 2130772030; + // aapt resource value: 0x7F030087 + public const int elevation = 2130903175; - // aapt resource value: 0x7f01015e - public const int errorEnabled = 2130772318; + // aapt resource value: 0x7F030088 + public const int errorEnabled = 2130903176; - // aapt resource value: 0x7f01015f - public const int errorTextAppearance = 2130772319; + // aapt resource value: 0x7F030089 + public const int errorTextAppearance = 2130903177; - // aapt resource value: 0x7f010042 - public const int expandActivityOverflowButtonDrawable = 2130772034; + // aapt resource value: 0x7F03008A + public const int expandActivityOverflowButtonDrawable = 2130903178; - // aapt resource value: 0x7f010118 - public const int expanded = 2130772248; + // aapt resource value: 0x7F03008B + public const int expanded = 2130903179; - // aapt resource value: 0x7f01012d - public const int expandedTitleGravity = 2130772269; + // aapt resource value: 0x7F03008C + public const int expandedTitleGravity = 2130903180; - // aapt resource value: 0x7f010120 - public const int expandedTitleMargin = 2130772256; + // aapt resource value: 0x7F03008D + public const int expandedTitleMargin = 2130903181; - // aapt resource value: 0x7f010124 - public const int expandedTitleMarginBottom = 2130772260; + // aapt resource value: 0x7F03008E + public const int expandedTitleMarginBottom = 2130903182; - // aapt resource value: 0x7f010123 - public const int expandedTitleMarginEnd = 2130772259; + // aapt resource value: 0x7F03008F + public const int expandedTitleMarginEnd = 2130903183; - // aapt resource value: 0x7f010121 - public const int expandedTitleMarginStart = 2130772257; + // aapt resource value: 0x7F030090 + public const int expandedTitleMarginStart = 2130903184; - // aapt resource value: 0x7f010122 - public const int expandedTitleMarginTop = 2130772258; + // aapt resource value: 0x7F030091 + public const int expandedTitleMarginTop = 2130903185; - // aapt resource value: 0x7f010125 - public const int expandedTitleTextAppearance = 2130772261; + // aapt resource value: 0x7F030092 + public const int expandedTitleTextAppearance = 2130903186; - // aapt resource value: 0x7f010015 - public const int externalRouteEnabledDrawable = 2130771989; + // aapt resource value: 0x7F030093 + public const int externalRouteEnabledDrawable = 2130903187; - // aapt resource value: 0x7f01013d - public const int fabSize = 2130772285; + // aapt resource value: 0x7F030094 + public const int fabSize = 2130903188; - // aapt resource value: 0x7f010004 - public const int fastScrollEnabled = 2130771972; + // aapt resource value: 0x7F030095 + public const int fastScrollEnabled = 2130903189; - // aapt resource value: 0x7f010007 - public const int fastScrollHorizontalThumbDrawable = 2130771975; + // aapt resource value: 0x7F030096 + public const int fastScrollHorizontalThumbDrawable = 2130903190; - // aapt resource value: 0x7f010008 - public const int fastScrollHorizontalTrackDrawable = 2130771976; + // aapt resource value: 0x7F030097 + public const int fastScrollHorizontalTrackDrawable = 2130903191; - // aapt resource value: 0x7f010005 - public const int fastScrollVerticalThumbDrawable = 2130771973; + // aapt resource value: 0x7F030098 + public const int fastScrollVerticalThumbDrawable = 2130903192; - // aapt resource value: 0x7f010006 - public const int fastScrollVerticalTrackDrawable = 2130771974; + // aapt resource value: 0x7F030099 + public const int fastScrollVerticalTrackDrawable = 2130903193; - // aapt resource value: 0x7f010171 - public const int font = 2130772337; + // aapt resource value: 0x7F03009A + public const int font = 2130903194; - // aapt resource value: 0x7f010055 - public const int fontFamily = 2130772053; + // aapt resource value: 0x7F03009B + public const int fontFamily = 2130903195; - // aapt resource value: 0x7f01016a - public const int fontProviderAuthority = 2130772330; + // aapt resource value: 0x7F03009C + public const int fontProviderAuthority = 2130903196; - // aapt resource value: 0x7f01016d - public const int fontProviderCerts = 2130772333; + // aapt resource value: 0x7F03009D + public const int fontProviderCerts = 2130903197; - // aapt resource value: 0x7f01016e - public const int fontProviderFetchStrategy = 2130772334; + // aapt resource value: 0x7F03009E + public const int fontProviderFetchStrategy = 2130903198; - // aapt resource value: 0x7f01016f - public const int fontProviderFetchTimeout = 2130772335; + // aapt resource value: 0x7F03009F + public const int fontProviderFetchTimeout = 2130903199; - // aapt resource value: 0x7f01016b - public const int fontProviderPackage = 2130772331; + // aapt resource value: 0x7F0300A0 + public const int fontProviderPackage = 2130903200; - // aapt resource value: 0x7f01016c - public const int fontProviderQuery = 2130772332; + // aapt resource value: 0x7F0300A1 + public const int fontProviderQuery = 2130903201; - // aapt resource value: 0x7f010170 - public const int fontStyle = 2130772336; + // aapt resource value: 0x7F0300A2 + public const int fontStyle = 2130903202; - // aapt resource value: 0x7f010172 - public const int fontWeight = 2130772338; + // aapt resource value: 0x7F0300A3 + public const int fontWeight = 2130903203; - // aapt resource value: 0x7f010142 - public const int foregroundInsidePadding = 2130772290; + // aapt resource value: 0x7F0300A4 + public const int foregroundInsidePadding = 2130903204; - // aapt resource value: 0x7f0100d2 - public const int gapBetweenBars = 2130772178; + // aapt resource value: 0x7F0300A5 + public const int gapBetweenBars = 2130903205; - // aapt resource value: 0x7f0100ef - public const int goIcon = 2130772207; + // aapt resource value: 0x7F0300A6 + public const int goIcon = 2130903206; - // aapt resource value: 0x7f010148 - public const int headerLayout = 2130772296; + // aapt resource value: 0x7F0300A7 + public const int headerLayout = 2130903207; - // aapt resource value: 0x7f010023 - public const int height = 2130772003; + // aapt resource value: 0x7F0300A8 + public const int height = 2130903208; - // aapt resource value: 0x7f010037 - public const int hideOnContentScroll = 2130772023; + // aapt resource value: 0x7F0300A9 + public const int hideOnContentScroll = 2130903209; - // aapt resource value: 0x7f010164 - public const int hintAnimationEnabled = 2130772324; + // aapt resource value: 0x7F0300AA + public const int hintAnimationEnabled = 2130903210; - // aapt resource value: 0x7f01015d - public const int hintEnabled = 2130772317; + // aapt resource value: 0x7F0300AB + public const int hintEnabled = 2130903211; - // aapt resource value: 0x7f01015c - public const int hintTextAppearance = 2130772316; + // aapt resource value: 0x7F0300AC + public const int hintTextAppearance = 2130903212; - // aapt resource value: 0x7f010085 - public const int homeAsUpIndicator = 2130772101; + // aapt resource value: 0x7F0300AD + public const int homeAsUpIndicator = 2130903213; - // aapt resource value: 0x7f010032 - public const int homeLayout = 2130772018; + // aapt resource value: 0x7F0300AE + public const int homeLayout = 2130903214; - // aapt resource value: 0x7f01002b - public const int icon = 2130772011; + // aapt resource value: 0x7F0300AF + public const int icon = 2130903215; - // aapt resource value: 0x7f0100e2 - public const int iconTint = 2130772194; + // aapt resource value: 0x7F0300B2 + public const int iconifiedByDefault = 2130903218; - // aapt resource value: 0x7f0100e3 - public const int iconTintMode = 2130772195; + // aapt resource value: 0x7F0300B0 + public const int iconTint = 2130903216; - // aapt resource value: 0x7f0100eb - public const int iconifiedByDefault = 2130772203; + // aapt resource value: 0x7F0300B1 + public const int iconTintMode = 2130903217; - // aapt resource value: 0x7f010095 - public const int imageButtonStyle = 2130772117; + // aapt resource value: 0x7F0300B3 + public const int imageButtonStyle = 2130903219; - // aapt resource value: 0x7f010034 - public const int indeterminateProgressStyle = 2130772020; + // aapt resource value: 0x7F0300B4 + public const int indeterminateProgressStyle = 2130903220; - // aapt resource value: 0x7f010041 - public const int initialActivityCount = 2130772033; + // aapt resource value: 0x7F0300B5 + public const int initialActivityCount = 2130903221; - // aapt resource value: 0x7f010149 - public const int insetForeground = 2130772297; + // aapt resource value: 0x7F0300B6 + public const int insetForeground = 2130903222; - // aapt resource value: 0x7f010024 - public const int isLightTheme = 2130772004; + // aapt resource value: 0x7F0300B7 + public const int isLightTheme = 2130903223; - // aapt resource value: 0x7f010146 - public const int itemBackground = 2130772294; + // aapt resource value: 0x7F0300B8 + public const int itemBackground = 2130903224; - // aapt resource value: 0x7f010144 - public const int itemIconTint = 2130772292; + // aapt resource value: 0x7F0300B9 + public const int itemIconTint = 2130903225; - // aapt resource value: 0x7f010036 - public const int itemPadding = 2130772022; + // aapt resource value: 0x7F0300BA + public const int itemPadding = 2130903226; - // aapt resource value: 0x7f010147 - public const int itemTextAppearance = 2130772295; + // aapt resource value: 0x7F0300BB + public const int itemTextAppearance = 2130903227; - // aapt resource value: 0x7f010145 - public const int itemTextColor = 2130772293; + // aapt resource value: 0x7F0300BC + public const int itemTextColor = 2130903228; - // aapt resource value: 0x7f010131 - public const int keylines = 2130772273; + // aapt resource value: 0x7F0300BD + public const int keylines = 2130903229; - // aapt resource value: 0x7f0100ea - public const int layout = 2130772202; + // aapt resource value: 0x7F0300BE + public const int layout = 2130903230; - // aapt resource value: 0x7f010000 - public const int layoutManager = 2130771968; + // aapt resource value: 0x7F0300BF + public const int layoutManager = 2130903231; - // aapt resource value: 0x7f010134 - public const int layout_anchor = 2130772276; + // aapt resource value: 0x7F0300C0 + public const int layout_anchor = 2130903232; - // aapt resource value: 0x7f010136 - public const int layout_anchorGravity = 2130772278; + // aapt resource value: 0x7F0300C1 + public const int layout_anchorGravity = 2130903233; - // aapt resource value: 0x7f010133 - public const int layout_behavior = 2130772275; + // aapt resource value: 0x7F0300C2 + public const int layout_behavior = 2130903234; - // aapt resource value: 0x7f01012f - public const int layout_collapseMode = 2130772271; + // aapt resource value: 0x7F0300C3 + public const int layout_collapseMode = 2130903235; - // aapt resource value: 0x7f010130 - public const int layout_collapseParallaxMultiplier = 2130772272; + // aapt resource value: 0x7F0300C4 + public const int layout_collapseParallaxMultiplier = 2130903236; - // aapt resource value: 0x7f010138 - public const int layout_dodgeInsetEdges = 2130772280; + // aapt resource value: 0x7F0300C5 + public const int layout_dodgeInsetEdges = 2130903237; - // aapt resource value: 0x7f010137 - public const int layout_insetEdge = 2130772279; + // aapt resource value: 0x7F0300C6 + public const int layout_insetEdge = 2130903238; - // aapt resource value: 0x7f010135 - public const int layout_keyline = 2130772277; + // aapt resource value: 0x7F0300C7 + public const int layout_keyline = 2130903239; - // aapt resource value: 0x7f01011b - public const int layout_scrollFlags = 2130772251; + // aapt resource value: 0x7F0300C8 + public const int layout_scrollFlags = 2130903240; - // aapt resource value: 0x7f01011c - public const int layout_scrollInterpolator = 2130772252; + // aapt resource value: 0x7F0300C9 + public const int layout_scrollInterpolator = 2130903241; - // aapt resource value: 0x7f0100a7 - public const int listChoiceBackgroundIndicator = 2130772135; + // aapt resource value: 0x7F0300CA + public const int listChoiceBackgroundIndicator = 2130903242; - // aapt resource value: 0x7f010081 - public const int listDividerAlertDialog = 2130772097; + // aapt resource value: 0x7F0300CB + public const int listDividerAlertDialog = 2130903243; - // aapt resource value: 0x7f010047 - public const int listItemLayout = 2130772039; + // aapt resource value: 0x7F0300CC + public const int listItemLayout = 2130903244; - // aapt resource value: 0x7f010044 - public const int listLayout = 2130772036; + // aapt resource value: 0x7F0300CD + public const int listLayout = 2130903245; - // aapt resource value: 0x7f0100c7 - public const int listMenuViewStyle = 2130772167; + // aapt resource value: 0x7F0300CE + public const int listMenuViewStyle = 2130903246; - // aapt resource value: 0x7f0100a0 - public const int listPopupWindowStyle = 2130772128; + // aapt resource value: 0x7F0300CF + public const int listPopupWindowStyle = 2130903247; - // aapt resource value: 0x7f01009a - public const int listPreferredItemHeight = 2130772122; + // aapt resource value: 0x7F0300D0 + public const int listPreferredItemHeight = 2130903248; - // aapt resource value: 0x7f01009c - public const int listPreferredItemHeightLarge = 2130772124; + // aapt resource value: 0x7F0300D1 + public const int listPreferredItemHeightLarge = 2130903249; - // aapt resource value: 0x7f01009b - public const int listPreferredItemHeightSmall = 2130772123; + // aapt resource value: 0x7F0300D2 + public const int listPreferredItemHeightSmall = 2130903250; - // aapt resource value: 0x7f01009d - public const int listPreferredItemPaddingLeft = 2130772125; + // aapt resource value: 0x7F0300D3 + public const int listPreferredItemPaddingLeft = 2130903251; - // aapt resource value: 0x7f01009e - public const int listPreferredItemPaddingRight = 2130772126; + // aapt resource value: 0x7F0300D4 + public const int listPreferredItemPaddingRight = 2130903252; - // aapt resource value: 0x7f01002c - public const int logo = 2130772012; + // aapt resource value: 0x7F0300D5 + public const int logo = 2130903253; - // aapt resource value: 0x7f010110 - public const int logoDescription = 2130772240; + // aapt resource value: 0x7F0300D6 + public const int logoDescription = 2130903254; - // aapt resource value: 0x7f01014b - public const int maxActionInlineWidth = 2130772299; + // aapt resource value: 0x7F0300D7 + public const int maxActionInlineWidth = 2130903255; - // aapt resource value: 0x7f01010a - public const int maxButtonHeight = 2130772234; + // aapt resource value: 0x7F0300D8 + public const int maxButtonHeight = 2130903256; - // aapt resource value: 0x7f0100d7 - public const int measureWithLargestChild = 2130772183; + // aapt resource value: 0x7F0300D9 + public const int measureWithLargestChild = 2130903257; - // aapt resource value: 0x7f010009 - public const int mediaRouteAudioTrackDrawable = 2130771977; + // aapt resource value: 0x7F0300DA + public const int mediaRouteAudioTrackDrawable = 2130903258; - // aapt resource value: 0x7f01000a - public const int mediaRouteButtonStyle = 2130771978; + // aapt resource value: 0x7F0300DB + public const int mediaRouteButtonStyle = 2130903259; - // aapt resource value: 0x7f010016 - public const int mediaRouteButtonTint = 2130771990; + // aapt resource value: 0x7F0300DC + public const int mediaRouteButtonTint = 2130903260; - // aapt resource value: 0x7f01000b - public const int mediaRouteCloseDrawable = 2130771979; + // aapt resource value: 0x7F0300DD + public const int mediaRouteCloseDrawable = 2130903261; - // aapt resource value: 0x7f01000c - public const int mediaRouteControlPanelThemeOverlay = 2130771980; + // aapt resource value: 0x7F0300DE + public const int mediaRouteControlPanelThemeOverlay = 2130903262; - // aapt resource value: 0x7f01000d - public const int mediaRouteDefaultIconDrawable = 2130771981; + // aapt resource value: 0x7F0300DF + public const int mediaRouteDefaultIconDrawable = 2130903263; - // aapt resource value: 0x7f01000e - public const int mediaRoutePauseDrawable = 2130771982; + // aapt resource value: 0x7F0300E0 + public const int mediaRoutePauseDrawable = 2130903264; - // aapt resource value: 0x7f01000f - public const int mediaRoutePlayDrawable = 2130771983; + // aapt resource value: 0x7F0300E1 + public const int mediaRoutePlayDrawable = 2130903265; - // aapt resource value: 0x7f010010 - public const int mediaRouteSpeakerGroupIconDrawable = 2130771984; + // aapt resource value: 0x7F0300E2 + public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - // aapt resource value: 0x7f010011 - public const int mediaRouteSpeakerIconDrawable = 2130771985; + // aapt resource value: 0x7F0300E3 + public const int mediaRouteSpeakerIconDrawable = 2130903267; - // aapt resource value: 0x7f010012 - public const int mediaRouteStopDrawable = 2130771986; + // aapt resource value: 0x7F0300E4 + public const int mediaRouteStopDrawable = 2130903268; - // aapt resource value: 0x7f010013 - public const int mediaRouteTheme = 2130771987; + // aapt resource value: 0x7F0300E5 + public const int mediaRouteTheme = 2130903269; - // aapt resource value: 0x7f010014 - public const int mediaRouteTvIconDrawable = 2130771988; + // aapt resource value: 0x7F0300E6 + public const int mediaRouteTvIconDrawable = 2130903270; - // aapt resource value: 0x7f010143 - public const int menu = 2130772291; + // aapt resource value: 0x7F0300E7 + public const int menu = 2130903271; - // aapt resource value: 0x7f010045 - public const int multiChoiceItemLayout = 2130772037; + // aapt resource value: 0x7F0300E8 + public const int multiChoiceItemLayout = 2130903272; - // aapt resource value: 0x7f01010f - public const int navigationContentDescription = 2130772239; + // aapt resource value: 0x7F0300E9 + public const int navigationContentDescription = 2130903273; - // aapt resource value: 0x7f01010e - public const int navigationIcon = 2130772238; + // aapt resource value: 0x7F0300EA + public const int navigationIcon = 2130903274; - // aapt resource value: 0x7f010026 - public const int navigationMode = 2130772006; + // aapt resource value: 0x7F0300EB + public const int navigationMode = 2130903275; - // aapt resource value: 0x7f0100db - public const int numericModifiers = 2130772187; + // aapt resource value: 0x7F0300EC + public const int numericModifiers = 2130903276; - // aapt resource value: 0x7f0100e6 - public const int overlapAnchor = 2130772198; + // aapt resource value: 0x7F0300ED + public const int overlapAnchor = 2130903277; - // aapt resource value: 0x7f0100e8 - public const int paddingBottomNoButtons = 2130772200; + // aapt resource value: 0x7F0300EE + public const int paddingBottomNoButtons = 2130903278; - // aapt resource value: 0x7f010114 - public const int paddingEnd = 2130772244; + // aapt resource value: 0x7F0300EF + public const int paddingEnd = 2130903279; - // aapt resource value: 0x7f010113 - public const int paddingStart = 2130772243; + // aapt resource value: 0x7F0300F0 + public const int paddingStart = 2130903280; - // aapt resource value: 0x7f0100e9 - public const int paddingTopNoTitle = 2130772201; + // aapt resource value: 0x7F0300F1 + public const int paddingTopNoTitle = 2130903281; - // aapt resource value: 0x7f0100a4 - public const int panelBackground = 2130772132; + // aapt resource value: 0x7F0300F2 + public const int panelBackground = 2130903282; - // aapt resource value: 0x7f0100a6 - public const int panelMenuListTheme = 2130772134; + // aapt resource value: 0x7F0300F3 + public const int panelMenuListTheme = 2130903283; - // aapt resource value: 0x7f0100a5 - public const int panelMenuListWidth = 2130772133; + // aapt resource value: 0x7F0300F4 + public const int panelMenuListWidth = 2130903284; - // aapt resource value: 0x7f010167 - public const int passwordToggleContentDescription = 2130772327; + // aapt resource value: 0x7F0300F5 + public const int passwordToggleContentDescription = 2130903285; - // aapt resource value: 0x7f010166 - public const int passwordToggleDrawable = 2130772326; + // aapt resource value: 0x7F0300F6 + public const int passwordToggleDrawable = 2130903286; - // aapt resource value: 0x7f010165 - public const int passwordToggleEnabled = 2130772325; + // aapt resource value: 0x7F0300F7 + public const int passwordToggleEnabled = 2130903287; - // aapt resource value: 0x7f010168 - public const int passwordToggleTint = 2130772328; + // aapt resource value: 0x7F0300F8 + public const int passwordToggleTint = 2130903288; - // aapt resource value: 0x7f010169 - public const int passwordToggleTintMode = 2130772329; + // aapt resource value: 0x7F0300F9 + public const int passwordToggleTintMode = 2130903289; - // aapt resource value: 0x7f010091 - public const int popupMenuStyle = 2130772113; + // aapt resource value: 0x7F0300FA + public const int popupMenuStyle = 2130903290; - // aapt resource value: 0x7f01003f - public const int popupTheme = 2130772031; + // aapt resource value: 0x7F0300FB + public const int popupTheme = 2130903291; - // aapt resource value: 0x7f010092 - public const int popupWindowStyle = 2130772114; + // aapt resource value: 0x7F0300FC + public const int popupWindowStyle = 2130903292; - // aapt resource value: 0x7f0100e4 - public const int preserveIconSpacing = 2130772196; + // aapt resource value: 0x7F0300FD + public const int preserveIconSpacing = 2130903293; - // aapt resource value: 0x7f01013e - public const int pressedTranslationZ = 2130772286; + // aapt resource value: 0x7F0300FE + public const int pressedTranslationZ = 2130903294; - // aapt resource value: 0x7f010035 - public const int progressBarPadding = 2130772021; + // aapt resource value: 0x7F0300FF + public const int progressBarPadding = 2130903295; - // aapt resource value: 0x7f010033 - public const int progressBarStyle = 2130772019; + // aapt resource value: 0x7F030100 + public const int progressBarStyle = 2130903296; - // aapt resource value: 0x7f0100f5 - public const int queryBackground = 2130772213; + // aapt resource value: 0x7F030101 + public const int queryBackground = 2130903297; - // aapt resource value: 0x7f0100ec - public const int queryHint = 2130772204; + // aapt resource value: 0x7F030102 + public const int queryHint = 2130903298; - // aapt resource value: 0x7f0100c0 - public const int radioButtonStyle = 2130772160; + // aapt resource value: 0x7F030103 + public const int radioButtonStyle = 2130903299; - // aapt resource value: 0x7f0100c1 - public const int ratingBarStyle = 2130772161; + // aapt resource value: 0x7F030104 + public const int ratingBarStyle = 2130903300; - // aapt resource value: 0x7f0100c2 - public const int ratingBarStyleIndicator = 2130772162; + // aapt resource value: 0x7F030105 + public const int ratingBarStyleIndicator = 2130903301; - // aapt resource value: 0x7f0100c3 - public const int ratingBarStyleSmall = 2130772163; + // aapt resource value: 0x7F030106 + public const int ratingBarStyleSmall = 2130903302; - // aapt resource value: 0x7f010002 - public const int reverseLayout = 2130771970; + // aapt resource value: 0x7F030107 + public const int reverseLayout = 2130903303; - // aapt resource value: 0x7f01013c - public const int rippleColor = 2130772284; + // aapt resource value: 0x7F030108 + public const int rippleColor = 2130903304; - // aapt resource value: 0x7f01012b - public const int scrimAnimationDuration = 2130772267; + // aapt resource value: 0x7F030109 + public const int scrimAnimationDuration = 2130903305; - // aapt resource value: 0x7f01012a - public const int scrimVisibleHeightTrigger = 2130772266; + // aapt resource value: 0x7F03010A + public const int scrimVisibleHeightTrigger = 2130903306; - // aapt resource value: 0x7f0100f1 - public const int searchHintIcon = 2130772209; + // aapt resource value: 0x7F03010B + public const int searchHintIcon = 2130903307; - // aapt resource value: 0x7f0100f0 - public const int searchIcon = 2130772208; + // aapt resource value: 0x7F03010C + public const int searchIcon = 2130903308; - // aapt resource value: 0x7f010099 - public const int searchViewStyle = 2130772121; + // aapt resource value: 0x7F03010D + public const int searchViewStyle = 2130903309; - // aapt resource value: 0x7f0100c4 - public const int seekBarStyle = 2130772164; + // aapt resource value: 0x7F03010E + public const int seekBarStyle = 2130903310; - // aapt resource value: 0x7f010089 - public const int selectableItemBackground = 2130772105; + // aapt resource value: 0x7F03010F + public const int selectableItemBackground = 2130903311; - // aapt resource value: 0x7f01008a - public const int selectableItemBackgroundBorderless = 2130772106; + // aapt resource value: 0x7F030110 + public const int selectableItemBackgroundBorderless = 2130903312; - // aapt resource value: 0x7f0100dc - public const int showAsAction = 2130772188; + // aapt resource value: 0x7F030111 + public const int showAsAction = 2130903313; - // aapt resource value: 0x7f0100d8 - public const int showDividers = 2130772184; + // aapt resource value: 0x7F030112 + public const int showDividers = 2130903314; - // aapt resource value: 0x7f010101 - public const int showText = 2130772225; + // aapt resource value: 0x7F030113 + public const int showText = 2130903315; - // aapt resource value: 0x7f010048 - public const int showTitle = 2130772040; + // aapt resource value: 0x7F030114 + public const int showTitle = 2130903316; - // aapt resource value: 0x7f010046 - public const int singleChoiceItemLayout = 2130772038; + // aapt resource value: 0x7F030115 + public const int singleChoiceItemLayout = 2130903317; - // aapt resource value: 0x7f010001 - public const int spanCount = 2130771969; + // aapt resource value: 0x7F030116 + public const int spanCount = 2130903318; - // aapt resource value: 0x7f0100d0 - public const int spinBars = 2130772176; + // aapt resource value: 0x7F030117 + public const int spinBars = 2130903319; - // aapt resource value: 0x7f010084 - public const int spinnerDropDownItemStyle = 2130772100; + // aapt resource value: 0x7F030118 + public const int spinnerDropDownItemStyle = 2130903320; - // aapt resource value: 0x7f0100c5 - public const int spinnerStyle = 2130772165; + // aapt resource value: 0x7F030119 + public const int spinnerStyle = 2130903321; - // aapt resource value: 0x7f010100 - public const int splitTrack = 2130772224; + // aapt resource value: 0x7F03011A + public const int splitTrack = 2130903322; - // aapt resource value: 0x7f010049 - public const int srcCompat = 2130772041; + // aapt resource value: 0x7F03011B + public const int srcCompat = 2130903323; - // aapt resource value: 0x7f010003 - public const int stackFromEnd = 2130771971; + // aapt resource value: 0x7F03011C + public const int stackFromEnd = 2130903324; - // aapt resource value: 0x7f0100e7 - public const int state_above_anchor = 2130772199; + // aapt resource value: 0x7F03011D + public const int state_above_anchor = 2130903325; - // aapt resource value: 0x7f010119 - public const int state_collapsed = 2130772249; + // aapt resource value: 0x7F03011E + public const int state_collapsed = 2130903326; - // aapt resource value: 0x7f01011a - public const int state_collapsible = 2130772250; + // aapt resource value: 0x7F03011F + public const int state_collapsible = 2130903327; - // aapt resource value: 0x7f010132 - public const int statusBarBackground = 2130772274; + // aapt resource value: 0x7F030120 + public const int statusBarBackground = 2130903328; - // aapt resource value: 0x7f010128 - public const int statusBarScrim = 2130772264; + // aapt resource value: 0x7F030121 + public const int statusBarScrim = 2130903329; - // aapt resource value: 0x7f0100e5 - public const int subMenuArrow = 2130772197; + // aapt resource value: 0x7F030122 + public const int subMenuArrow = 2130903330; - // aapt resource value: 0x7f0100f6 - public const int submitBackground = 2130772214; + // aapt resource value: 0x7F030123 + public const int submitBackground = 2130903331; - // aapt resource value: 0x7f010028 - public const int subtitle = 2130772008; + // aapt resource value: 0x7F030124 + public const int subtitle = 2130903332; - // aapt resource value: 0x7f010103 - public const int subtitleTextAppearance = 2130772227; + // aapt resource value: 0x7F030125 + public const int subtitleTextAppearance = 2130903333; - // aapt resource value: 0x7f010112 - public const int subtitleTextColor = 2130772242; + // aapt resource value: 0x7F030126 + public const int subtitleTextColor = 2130903334; - // aapt resource value: 0x7f01002a - public const int subtitleTextStyle = 2130772010; + // aapt resource value: 0x7F030127 + public const int subtitleTextStyle = 2130903335; - // aapt resource value: 0x7f0100f4 - public const int suggestionRowLayout = 2130772212; + // aapt resource value: 0x7F030128 + public const int suggestionRowLayout = 2130903336; - // aapt resource value: 0x7f0100fe - public const int switchMinWidth = 2130772222; + // aapt resource value: 0x7F030129 + public const int switchMinWidth = 2130903337; - // aapt resource value: 0x7f0100ff - public const int switchPadding = 2130772223; + // aapt resource value: 0x7F03012A + public const int switchPadding = 2130903338; - // aapt resource value: 0x7f0100c6 - public const int switchStyle = 2130772166; + // aapt resource value: 0x7F03012B + public const int switchStyle = 2130903339; - // aapt resource value: 0x7f0100fd - public const int switchTextAppearance = 2130772221; + // aapt resource value: 0x7F03012C + public const int switchTextAppearance = 2130903340; - // aapt resource value: 0x7f01014f - public const int tabBackground = 2130772303; + // aapt resource value: 0x7F03012D + public const int tabBackground = 2130903341; - // aapt resource value: 0x7f01014e - public const int tabContentStart = 2130772302; + // aapt resource value: 0x7F03012E + public const int tabContentStart = 2130903342; - // aapt resource value: 0x7f010151 - public const int tabGravity = 2130772305; + // aapt resource value: 0x7F03012F + public const int tabGravity = 2130903343; - // aapt resource value: 0x7f01014c - public const int tabIndicatorColor = 2130772300; + // aapt resource value: 0x7F030130 + public const int tabIndicatorColor = 2130903344; - // aapt resource value: 0x7f01014d - public const int tabIndicatorHeight = 2130772301; + // aapt resource value: 0x7F030131 + public const int tabIndicatorHeight = 2130903345; - // aapt resource value: 0x7f010153 - public const int tabMaxWidth = 2130772307; + // aapt resource value: 0x7F030132 + public const int tabMaxWidth = 2130903346; - // aapt resource value: 0x7f010152 - public const int tabMinWidth = 2130772306; + // aapt resource value: 0x7F030133 + public const int tabMinWidth = 2130903347; - // aapt resource value: 0x7f010150 - public const int tabMode = 2130772304; + // aapt resource value: 0x7F030134 + public const int tabMode = 2130903348; - // aapt resource value: 0x7f01015b - public const int tabPadding = 2130772315; + // aapt resource value: 0x7F030135 + public const int tabPadding = 2130903349; - // aapt resource value: 0x7f01015a - public const int tabPaddingBottom = 2130772314; + // aapt resource value: 0x7F030136 + public const int tabPaddingBottom = 2130903350; - // aapt resource value: 0x7f010159 - public const int tabPaddingEnd = 2130772313; + // aapt resource value: 0x7F030137 + public const int tabPaddingEnd = 2130903351; - // aapt resource value: 0x7f010157 - public const int tabPaddingStart = 2130772311; + // aapt resource value: 0x7F030138 + public const int tabPaddingStart = 2130903352; - // aapt resource value: 0x7f010158 - public const int tabPaddingTop = 2130772312; + // aapt resource value: 0x7F030139 + public const int tabPaddingTop = 2130903353; - // aapt resource value: 0x7f010156 - public const int tabSelectedTextColor = 2130772310; + // aapt resource value: 0x7F03013A + public const int tabSelectedTextColor = 2130903354; - // aapt resource value: 0x7f010154 - public const int tabTextAppearance = 2130772308; + // aapt resource value: 0x7F03013B + public const int tabTextAppearance = 2130903355; - // aapt resource value: 0x7f010155 - public const int tabTextColor = 2130772309; + // aapt resource value: 0x7F03013C + public const int tabTextColor = 2130903356; - // aapt resource value: 0x7f01004f - public const int textAllCaps = 2130772047; + // aapt resource value: 0x7F03013D + public const int textAllCaps = 2130903357; - // aapt resource value: 0x7f01007c - public const int textAppearanceLargePopupMenu = 2130772092; + // aapt resource value: 0x7F03013E + public const int textAppearanceLargePopupMenu = 2130903358; - // aapt resource value: 0x7f0100a1 - public const int textAppearanceListItem = 2130772129; + // aapt resource value: 0x7F03013F + public const int textAppearanceListItem = 2130903359; - // aapt resource value: 0x7f0100a2 - public const int textAppearanceListItemSecondary = 2130772130; + // aapt resource value: 0x7F030140 + public const int textAppearanceListItemSecondary = 2130903360; - // aapt resource value: 0x7f0100a3 - public const int textAppearanceListItemSmall = 2130772131; + // aapt resource value: 0x7F030141 + public const int textAppearanceListItemSmall = 2130903361; - // aapt resource value: 0x7f01007e - public const int textAppearancePopupMenuHeader = 2130772094; + // aapt resource value: 0x7F030142 + public const int textAppearancePopupMenuHeader = 2130903362; - // aapt resource value: 0x7f010097 - public const int textAppearanceSearchResultSubtitle = 2130772119; + // aapt resource value: 0x7F030143 + public const int textAppearanceSearchResultSubtitle = 2130903363; - // aapt resource value: 0x7f010096 - public const int textAppearanceSearchResultTitle = 2130772118; + // aapt resource value: 0x7F030144 + public const int textAppearanceSearchResultTitle = 2130903364; - // aapt resource value: 0x7f01007d - public const int textAppearanceSmallPopupMenu = 2130772093; + // aapt resource value: 0x7F030145 + public const int textAppearanceSmallPopupMenu = 2130903365; - // aapt resource value: 0x7f0100b6 - public const int textColorAlertDialogListItem = 2130772150; + // aapt resource value: 0x7F030146 + public const int textColorAlertDialogListItem = 2130903366; - // aapt resource value: 0x7f01013b - public const int textColorError = 2130772283; + // aapt resource value: 0x7F030147 + public const int textColorError = 2130903367; - // aapt resource value: 0x7f010098 - public const int textColorSearchUrl = 2130772120; + // aapt resource value: 0x7F030148 + public const int textColorSearchUrl = 2130903368; - // aapt resource value: 0x7f010115 - public const int theme = 2130772245; + // aapt resource value: 0x7F030149 + public const int theme = 2130903369; - // aapt resource value: 0x7f0100d6 - public const int thickness = 2130772182; + // aapt resource value: 0x7F03014A + public const int thickness = 2130903370; - // aapt resource value: 0x7f0100fc - public const int thumbTextPadding = 2130772220; + // aapt resource value: 0x7F03014B + public const int thumbTextPadding = 2130903371; - // aapt resource value: 0x7f0100f7 - public const int thumbTint = 2130772215; + // aapt resource value: 0x7F03014C + public const int thumbTint = 2130903372; - // aapt resource value: 0x7f0100f8 - public const int thumbTintMode = 2130772216; + // aapt resource value: 0x7F03014D + public const int thumbTintMode = 2130903373; - // aapt resource value: 0x7f01004c - public const int tickMark = 2130772044; + // aapt resource value: 0x7F03014E + public const int tickMark = 2130903374; - // aapt resource value: 0x7f01004d - public const int tickMarkTint = 2130772045; + // aapt resource value: 0x7F03014F + public const int tickMarkTint = 2130903375; - // aapt resource value: 0x7f01004e - public const int tickMarkTintMode = 2130772046; + // aapt resource value: 0x7F030150 + public const int tickMarkTintMode = 2130903376; - // aapt resource value: 0x7f01004a - public const int tint = 2130772042; + // aapt resource value: 0x7F030151 + public const int tint = 2130903377; - // aapt resource value: 0x7f01004b - public const int tintMode = 2130772043; + // aapt resource value: 0x7F030152 + public const int tintMode = 2130903378; - // aapt resource value: 0x7f010025 - public const int title = 2130772005; + // aapt resource value: 0x7F030153 + public const int title = 2130903379; - // aapt resource value: 0x7f01012e - public const int titleEnabled = 2130772270; + // aapt resource value: 0x7F030154 + public const int titleEnabled = 2130903380; - // aapt resource value: 0x7f010104 - public const int titleMargin = 2130772228; + // aapt resource value: 0x7F030155 + public const int titleMargin = 2130903381; - // aapt resource value: 0x7f010108 - public const int titleMarginBottom = 2130772232; + // aapt resource value: 0x7F030156 + public const int titleMarginBottom = 2130903382; - // aapt resource value: 0x7f010106 - public const int titleMarginEnd = 2130772230; + // aapt resource value: 0x7F030157 + public const int titleMarginEnd = 2130903383; - // aapt resource value: 0x7f010105 - public const int titleMarginStart = 2130772229; + // aapt resource value: 0x7F03015A + public const int titleMargins = 2130903386; - // aapt resource value: 0x7f010107 - public const int titleMarginTop = 2130772231; + // aapt resource value: 0x7F030158 + public const int titleMarginStart = 2130903384; - // aapt resource value: 0x7f010109 - public const int titleMargins = 2130772233; + // aapt resource value: 0x7F030159 + public const int titleMarginTop = 2130903385; - // aapt resource value: 0x7f010102 - public const int titleTextAppearance = 2130772226; + // aapt resource value: 0x7F03015B + public const int titleTextAppearance = 2130903387; - // aapt resource value: 0x7f010111 - public const int titleTextColor = 2130772241; + // aapt resource value: 0x7F03015C + public const int titleTextColor = 2130903388; - // aapt resource value: 0x7f010029 - public const int titleTextStyle = 2130772009; + // aapt resource value: 0x7F03015D + public const int titleTextStyle = 2130903389; - // aapt resource value: 0x7f010129 - public const int toolbarId = 2130772265; + // aapt resource value: 0x7F03015E + public const int toolbarId = 2130903390; - // aapt resource value: 0x7f010090 - public const int toolbarNavigationButtonStyle = 2130772112; + // aapt resource value: 0x7F03015F + public const int toolbarNavigationButtonStyle = 2130903391; - // aapt resource value: 0x7f01008f - public const int toolbarStyle = 2130772111; + // aapt resource value: 0x7F030160 + public const int toolbarStyle = 2130903392; - // aapt resource value: 0x7f0100c9 - public const int tooltipForegroundColor = 2130772169; + // aapt resource value: 0x7F030161 + public const int tooltipForegroundColor = 2130903393; - // aapt resource value: 0x7f0100c8 - public const int tooltipFrameBackground = 2130772168; + // aapt resource value: 0x7F030162 + public const int tooltipFrameBackground = 2130903394; - // aapt resource value: 0x7f0100e1 - public const int tooltipText = 2130772193; + // aapt resource value: 0x7F030163 + public const int tooltipText = 2130903395; - // aapt resource value: 0x7f0100f9 - public const int track = 2130772217; + // aapt resource value: 0x7F030164 + public const int track = 2130903396; - // aapt resource value: 0x7f0100fa - public const int trackTint = 2130772218; + // aapt resource value: 0x7F030165 + public const int trackTint = 2130903397; - // aapt resource value: 0x7f0100fb - public const int trackTintMode = 2130772219; + // aapt resource value: 0x7F030166 + public const int trackTintMode = 2130903398; - // aapt resource value: 0x7f010140 - public const int useCompatPadding = 2130772288; + // aapt resource value: 0x7F030167 + public const int useCompatPadding = 2130903399; - // aapt resource value: 0x7f0100f2 - public const int voiceIcon = 2130772210; + // aapt resource value: 0x7F030168 + public const int voiceIcon = 2130903400; - // aapt resource value: 0x7f010056 - public const int windowActionBar = 2130772054; + // aapt resource value: 0x7F030169 + public const int windowActionBar = 2130903401; - // aapt resource value: 0x7f010058 - public const int windowActionBarOverlay = 2130772056; + // aapt resource value: 0x7F03016A + public const int windowActionBarOverlay = 2130903402; - // aapt resource value: 0x7f010059 - public const int windowActionModeOverlay = 2130772057; + // aapt resource value: 0x7F03016B + public const int windowActionModeOverlay = 2130903403; - // aapt resource value: 0x7f01005d - public const int windowFixedHeightMajor = 2130772061; + // aapt resource value: 0x7F03016C + public const int windowFixedHeightMajor = 2130903404; - // aapt resource value: 0x7f01005b - public const int windowFixedHeightMinor = 2130772059; + // aapt resource value: 0x7F03016D + public const int windowFixedHeightMinor = 2130903405; - // aapt resource value: 0x7f01005a - public const int windowFixedWidthMajor = 2130772058; + // aapt resource value: 0x7F03016E + public const int windowFixedWidthMajor = 2130903406; - // aapt resource value: 0x7f01005c - public const int windowFixedWidthMinor = 2130772060; + // aapt resource value: 0x7F03016F + public const int windowFixedWidthMinor = 2130903407; - // aapt resource value: 0x7f01005e - public const int windowMinWidthMajor = 2130772062; + // aapt resource value: 0x7F030170 + public const int windowMinWidthMajor = 2130903408; - // aapt resource value: 0x7f01005f - public const int windowMinWidthMinor = 2130772063; + // aapt resource value: 0x7F030171 + public const int windowMinWidthMinor = 2130903409; - // aapt resource value: 0x7f010057 - public const int windowNoTitle = 2130772055; + // aapt resource value: 0x7F030172 + public const int windowNoTitle = 2130903410; static Attribute() { @@ -3339,20 +3231,20 @@ private Attribute() public partial class Boolean { - // aapt resource value: 0x7f0e0000 - public const int abc_action_bar_embed_tabs = 2131623936; + // aapt resource value: 0x7F040000 + public const int abc_action_bar_embed_tabs = 2130968576; - // aapt resource value: 0x7f0e0001 - public const int abc_allow_stacked_button_bar = 2131623937; + // aapt resource value: 0x7F040001 + public const int abc_allow_stacked_button_bar = 2130968577; - // aapt resource value: 0x7f0e0002 - public const int abc_config_actionMenuItemAllCaps = 2131623938; + // aapt resource value: 0x7F040002 + public const int abc_config_actionMenuItemAllCaps = 2130968578; - // aapt resource value: 0x7f0e0003 - public const int abc_config_closeDialogWhenTouchOutside = 2131623939; + // aapt resource value: 0x7F040003 + public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - // aapt resource value: 0x7f0e0004 - public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2131623940; + // aapt resource value: 0x7F040004 + public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; static Boolean() { @@ -3367,314 +3259,314 @@ private Boolean() public partial class Color { - // aapt resource value: 0x7f0d004f - public const int abc_background_cache_hint_selector_material_dark = 2131558479; + // aapt resource value: 0x7F050000 + public const int abc_background_cache_hint_selector_material_dark = 2131034112; - // aapt resource value: 0x7f0d0050 - public const int abc_background_cache_hint_selector_material_light = 2131558480; + // aapt resource value: 0x7F050001 + public const int abc_background_cache_hint_selector_material_light = 2131034113; - // aapt resource value: 0x7f0d0051 - public const int abc_btn_colored_borderless_text_material = 2131558481; + // aapt resource value: 0x7F050002 + public const int abc_btn_colored_borderless_text_material = 2131034114; - // aapt resource value: 0x7f0d0052 - public const int abc_btn_colored_text_material = 2131558482; + // aapt resource value: 0x7F050003 + public const int abc_btn_colored_text_material = 2131034115; - // aapt resource value: 0x7f0d0053 - public const int abc_color_highlight_material = 2131558483; + // aapt resource value: 0x7F050004 + public const int abc_color_highlight_material = 2131034116; - // aapt resource value: 0x7f0d0054 - public const int abc_hint_foreground_material_dark = 2131558484; + // aapt resource value: 0x7F050005 + public const int abc_hint_foreground_material_dark = 2131034117; - // aapt resource value: 0x7f0d0055 - public const int abc_hint_foreground_material_light = 2131558485; + // aapt resource value: 0x7F050006 + public const int abc_hint_foreground_material_light = 2131034118; - // aapt resource value: 0x7f0d0004 - public const int abc_input_method_navigation_guard = 2131558404; + // aapt resource value: 0x7F050007 + public const int abc_input_method_navigation_guard = 2131034119; - // aapt resource value: 0x7f0d0056 - public const int abc_primary_text_disable_only_material_dark = 2131558486; + // aapt resource value: 0x7F050008 + public const int abc_primary_text_disable_only_material_dark = 2131034120; - // aapt resource value: 0x7f0d0057 - public const int abc_primary_text_disable_only_material_light = 2131558487; + // aapt resource value: 0x7F050009 + public const int abc_primary_text_disable_only_material_light = 2131034121; - // aapt resource value: 0x7f0d0058 - public const int abc_primary_text_material_dark = 2131558488; + // aapt resource value: 0x7F05000A + public const int abc_primary_text_material_dark = 2131034122; - // aapt resource value: 0x7f0d0059 - public const int abc_primary_text_material_light = 2131558489; + // aapt resource value: 0x7F05000B + public const int abc_primary_text_material_light = 2131034123; - // aapt resource value: 0x7f0d005a - public const int abc_search_url_text = 2131558490; + // aapt resource value: 0x7F05000C + public const int abc_search_url_text = 2131034124; - // aapt resource value: 0x7f0d0005 - public const int abc_search_url_text_normal = 2131558405; + // aapt resource value: 0x7F05000D + public const int abc_search_url_text_normal = 2131034125; - // aapt resource value: 0x7f0d0006 - public const int abc_search_url_text_pressed = 2131558406; + // aapt resource value: 0x7F05000E + public const int abc_search_url_text_pressed = 2131034126; - // aapt resource value: 0x7f0d0007 - public const int abc_search_url_text_selected = 2131558407; + // aapt resource value: 0x7F05000F + public const int abc_search_url_text_selected = 2131034127; - // aapt resource value: 0x7f0d005b - public const int abc_secondary_text_material_dark = 2131558491; + // aapt resource value: 0x7F050010 + public const int abc_secondary_text_material_dark = 2131034128; - // aapt resource value: 0x7f0d005c - public const int abc_secondary_text_material_light = 2131558492; + // aapt resource value: 0x7F050011 + public const int abc_secondary_text_material_light = 2131034129; - // aapt resource value: 0x7f0d005d - public const int abc_tint_btn_checkable = 2131558493; + // aapt resource value: 0x7F050012 + public const int abc_tint_btn_checkable = 2131034130; - // aapt resource value: 0x7f0d005e - public const int abc_tint_default = 2131558494; + // aapt resource value: 0x7F050013 + public const int abc_tint_default = 2131034131; - // aapt resource value: 0x7f0d005f - public const int abc_tint_edittext = 2131558495; + // aapt resource value: 0x7F050014 + public const int abc_tint_edittext = 2131034132; - // aapt resource value: 0x7f0d0060 - public const int abc_tint_seek_thumb = 2131558496; + // aapt resource value: 0x7F050015 + public const int abc_tint_seek_thumb = 2131034133; - // aapt resource value: 0x7f0d0061 - public const int abc_tint_spinner = 2131558497; + // aapt resource value: 0x7F050016 + public const int abc_tint_spinner = 2131034134; - // aapt resource value: 0x7f0d0062 - public const int abc_tint_switch_track = 2131558498; + // aapt resource value: 0x7F050017 + public const int abc_tint_switch_track = 2131034135; - // aapt resource value: 0x7f0d0008 - public const int accent_material_dark = 2131558408; + // aapt resource value: 0x7F050018 + public const int accent_material_dark = 2131034136; - // aapt resource value: 0x7f0d0009 - public const int accent_material_light = 2131558409; + // aapt resource value: 0x7F050019 + public const int accent_material_light = 2131034137; - // aapt resource value: 0x7f0d000a - public const int background_floating_material_dark = 2131558410; + // aapt resource value: 0x7F05001A + public const int background_floating_material_dark = 2131034138; - // aapt resource value: 0x7f0d000b - public const int background_floating_material_light = 2131558411; + // aapt resource value: 0x7F05001B + public const int background_floating_material_light = 2131034139; - // aapt resource value: 0x7f0d000c - public const int background_material_dark = 2131558412; + // aapt resource value: 0x7F05001C + public const int background_material_dark = 2131034140; - // aapt resource value: 0x7f0d000d - public const int background_material_light = 2131558413; + // aapt resource value: 0x7F05001D + public const int background_material_light = 2131034141; - // aapt resource value: 0x7f0d000e - public const int bright_foreground_disabled_material_dark = 2131558414; + // aapt resource value: 0x7F05001E + public const int bright_foreground_disabled_material_dark = 2131034142; - // aapt resource value: 0x7f0d000f - public const int bright_foreground_disabled_material_light = 2131558415; + // aapt resource value: 0x7F05001F + public const int bright_foreground_disabled_material_light = 2131034143; - // aapt resource value: 0x7f0d0010 - public const int bright_foreground_inverse_material_dark = 2131558416; + // aapt resource value: 0x7F050020 + public const int bright_foreground_inverse_material_dark = 2131034144; - // aapt resource value: 0x7f0d0011 - public const int bright_foreground_inverse_material_light = 2131558417; + // aapt resource value: 0x7F050021 + public const int bright_foreground_inverse_material_light = 2131034145; - // aapt resource value: 0x7f0d0012 - public const int bright_foreground_material_dark = 2131558418; + // aapt resource value: 0x7F050022 + public const int bright_foreground_material_dark = 2131034146; - // aapt resource value: 0x7f0d0013 - public const int bright_foreground_material_light = 2131558419; + // aapt resource value: 0x7F050023 + public const int bright_foreground_material_light = 2131034147; - // aapt resource value: 0x7f0d0014 - public const int button_material_dark = 2131558420; + // aapt resource value: 0x7F050024 + public const int button_material_dark = 2131034148; - // aapt resource value: 0x7f0d0015 - public const int button_material_light = 2131558421; + // aapt resource value: 0x7F050025 + public const int button_material_light = 2131034149; - // aapt resource value: 0x7f0d0000 - public const int cardview_dark_background = 2131558400; + // aapt resource value: 0x7F050026 + public const int cardview_dark_background = 2131034150; - // aapt resource value: 0x7f0d0001 - public const int cardview_light_background = 2131558401; + // aapt resource value: 0x7F050027 + public const int cardview_light_background = 2131034151; - // aapt resource value: 0x7f0d0002 - public const int cardview_shadow_end_color = 2131558402; + // aapt resource value: 0x7F050028 + public const int cardview_shadow_end_color = 2131034152; - // aapt resource value: 0x7f0d0003 - public const int cardview_shadow_start_color = 2131558403; + // aapt resource value: 0x7F050029 + public const int cardview_shadow_start_color = 2131034153; - // aapt resource value: 0x7f0d004d - public const int colorAccent = 2131558477; + // aapt resource value: 0x7F05002A + public const int colorAccent = 2131034154; - // aapt resource value: 0x7f0d004b - public const int colorPrimary = 2131558475; + // aapt resource value: 0x7F05002B + public const int colorPrimary = 2131034155; - // aapt resource value: 0x7f0d004c - public const int colorPrimaryDark = 2131558476; + // aapt resource value: 0x7F05002C + public const int colorPrimaryDark = 2131034156; - // aapt resource value: 0x7f0d0040 - public const int design_bottom_navigation_shadow_color = 2131558464; + // aapt resource value: 0x7F05002D + public const int design_bottom_navigation_shadow_color = 2131034157; - // aapt resource value: 0x7f0d0063 - public const int design_error = 2131558499; + // aapt resource value: 0x7F05002E + public const int design_error = 2131034158; - // aapt resource value: 0x7f0d0041 - public const int design_fab_shadow_end_color = 2131558465; + // aapt resource value: 0x7F05002F + public const int design_fab_shadow_end_color = 2131034159; - // aapt resource value: 0x7f0d0042 - public const int design_fab_shadow_mid_color = 2131558466; + // aapt resource value: 0x7F050030 + public const int design_fab_shadow_mid_color = 2131034160; - // aapt resource value: 0x7f0d0043 - public const int design_fab_shadow_start_color = 2131558467; + // aapt resource value: 0x7F050031 + public const int design_fab_shadow_start_color = 2131034161; - // aapt resource value: 0x7f0d0044 - public const int design_fab_stroke_end_inner_color = 2131558468; + // aapt resource value: 0x7F050032 + public const int design_fab_stroke_end_inner_color = 2131034162; - // aapt resource value: 0x7f0d0045 - public const int design_fab_stroke_end_outer_color = 2131558469; + // aapt resource value: 0x7F050033 + public const int design_fab_stroke_end_outer_color = 2131034163; - // aapt resource value: 0x7f0d0046 - public const int design_fab_stroke_top_inner_color = 2131558470; + // aapt resource value: 0x7F050034 + public const int design_fab_stroke_top_inner_color = 2131034164; - // aapt resource value: 0x7f0d0047 - public const int design_fab_stroke_top_outer_color = 2131558471; + // aapt resource value: 0x7F050035 + public const int design_fab_stroke_top_outer_color = 2131034165; - // aapt resource value: 0x7f0d0048 - public const int design_snackbar_background_color = 2131558472; + // aapt resource value: 0x7F050036 + public const int design_snackbar_background_color = 2131034166; - // aapt resource value: 0x7f0d0064 - public const int design_tint_password_toggle = 2131558500; + // aapt resource value: 0x7F050037 + public const int design_tint_password_toggle = 2131034167; - // aapt resource value: 0x7f0d0016 - public const int dim_foreground_disabled_material_dark = 2131558422; + // aapt resource value: 0x7F050038 + public const int dim_foreground_disabled_material_dark = 2131034168; - // aapt resource value: 0x7f0d0017 - public const int dim_foreground_disabled_material_light = 2131558423; + // aapt resource value: 0x7F050039 + public const int dim_foreground_disabled_material_light = 2131034169; - // aapt resource value: 0x7f0d0018 - public const int dim_foreground_material_dark = 2131558424; + // aapt resource value: 0x7F05003A + public const int dim_foreground_material_dark = 2131034170; - // aapt resource value: 0x7f0d0019 - public const int dim_foreground_material_light = 2131558425; + // aapt resource value: 0x7F05003B + public const int dim_foreground_material_light = 2131034171; - // aapt resource value: 0x7f0d001a - public const int error_color_material = 2131558426; + // aapt resource value: 0x7F05003C + public const int error_color_material = 2131034172; - // aapt resource value: 0x7f0d001b - public const int foreground_material_dark = 2131558427; + // aapt resource value: 0x7F05003D + public const int foreground_material_dark = 2131034173; - // aapt resource value: 0x7f0d001c - public const int foreground_material_light = 2131558428; + // aapt resource value: 0x7F05003E + public const int foreground_material_light = 2131034174; - // aapt resource value: 0x7f0d001d - public const int highlighted_text_material_dark = 2131558429; + // aapt resource value: 0x7F05003F + public const int highlighted_text_material_dark = 2131034175; - // aapt resource value: 0x7f0d001e - public const int highlighted_text_material_light = 2131558430; + // aapt resource value: 0x7F050040 + public const int highlighted_text_material_light = 2131034176; - // aapt resource value: 0x7f0d004e - public const int ic_launcher_background = 2131558478; + // aapt resource value: 0x7F050041 + public const int ic_launcher_background = 2131034177; - // aapt resource value: 0x7f0d001f - public const int material_blue_grey_800 = 2131558431; + // aapt resource value: 0x7F050042 + public const int material_blue_grey_800 = 2131034178; - // aapt resource value: 0x7f0d0020 - public const int material_blue_grey_900 = 2131558432; + // aapt resource value: 0x7F050043 + public const int material_blue_grey_900 = 2131034179; - // aapt resource value: 0x7f0d0021 - public const int material_blue_grey_950 = 2131558433; + // aapt resource value: 0x7F050044 + public const int material_blue_grey_950 = 2131034180; - // aapt resource value: 0x7f0d0022 - public const int material_deep_teal_200 = 2131558434; + // aapt resource value: 0x7F050045 + public const int material_deep_teal_200 = 2131034181; - // aapt resource value: 0x7f0d0023 - public const int material_deep_teal_500 = 2131558435; + // aapt resource value: 0x7F050046 + public const int material_deep_teal_500 = 2131034182; - // aapt resource value: 0x7f0d0024 - public const int material_grey_100 = 2131558436; + // aapt resource value: 0x7F050047 + public const int material_grey_100 = 2131034183; - // aapt resource value: 0x7f0d0025 - public const int material_grey_300 = 2131558437; + // aapt resource value: 0x7F050048 + public const int material_grey_300 = 2131034184; - // aapt resource value: 0x7f0d0026 - public const int material_grey_50 = 2131558438; + // aapt resource value: 0x7F050049 + public const int material_grey_50 = 2131034185; - // aapt resource value: 0x7f0d0027 - public const int material_grey_600 = 2131558439; + // aapt resource value: 0x7F05004A + public const int material_grey_600 = 2131034186; - // aapt resource value: 0x7f0d0028 - public const int material_grey_800 = 2131558440; + // aapt resource value: 0x7F05004B + public const int material_grey_800 = 2131034187; - // aapt resource value: 0x7f0d0029 - public const int material_grey_850 = 2131558441; + // aapt resource value: 0x7F05004C + public const int material_grey_850 = 2131034188; - // aapt resource value: 0x7f0d002a - public const int material_grey_900 = 2131558442; + // aapt resource value: 0x7F05004D + public const int material_grey_900 = 2131034189; - // aapt resource value: 0x7f0d0049 - public const int notification_action_color_filter = 2131558473; + // aapt resource value: 0x7F05004E + public const int notification_action_color_filter = 2131034190; - // aapt resource value: 0x7f0d004a - public const int notification_icon_bg_color = 2131558474; + // aapt resource value: 0x7F05004F + public const int notification_icon_bg_color = 2131034191; - // aapt resource value: 0x7f0d003f - public const int notification_material_background_media_default_color = 2131558463; + // aapt resource value: 0x7F050050 + public const int notification_material_background_media_default_color = 2131034192; - // aapt resource value: 0x7f0d002b - public const int primary_dark_material_dark = 2131558443; + // aapt resource value: 0x7F050051 + public const int primary_dark_material_dark = 2131034193; - // aapt resource value: 0x7f0d002c - public const int primary_dark_material_light = 2131558444; + // aapt resource value: 0x7F050052 + public const int primary_dark_material_light = 2131034194; - // aapt resource value: 0x7f0d002d - public const int primary_material_dark = 2131558445; + // aapt resource value: 0x7F050053 + public const int primary_material_dark = 2131034195; - // aapt resource value: 0x7f0d002e - public const int primary_material_light = 2131558446; + // aapt resource value: 0x7F050054 + public const int primary_material_light = 2131034196; - // aapt resource value: 0x7f0d002f - public const int primary_text_default_material_dark = 2131558447; + // aapt resource value: 0x7F050055 + public const int primary_text_default_material_dark = 2131034197; - // aapt resource value: 0x7f0d0030 - public const int primary_text_default_material_light = 2131558448; + // aapt resource value: 0x7F050056 + public const int primary_text_default_material_light = 2131034198; - // aapt resource value: 0x7f0d0031 - public const int primary_text_disabled_material_dark = 2131558449; + // aapt resource value: 0x7F050057 + public const int primary_text_disabled_material_dark = 2131034199; - // aapt resource value: 0x7f0d0032 - public const int primary_text_disabled_material_light = 2131558450; + // aapt resource value: 0x7F050058 + public const int primary_text_disabled_material_light = 2131034200; - // aapt resource value: 0x7f0d0033 - public const int ripple_material_dark = 2131558451; + // aapt resource value: 0x7F050059 + public const int ripple_material_dark = 2131034201; - // aapt resource value: 0x7f0d0034 - public const int ripple_material_light = 2131558452; + // aapt resource value: 0x7F05005A + public const int ripple_material_light = 2131034202; - // aapt resource value: 0x7f0d0035 - public const int secondary_text_default_material_dark = 2131558453; + // aapt resource value: 0x7F05005B + public const int secondary_text_default_material_dark = 2131034203; - // aapt resource value: 0x7f0d0036 - public const int secondary_text_default_material_light = 2131558454; + // aapt resource value: 0x7F05005C + public const int secondary_text_default_material_light = 2131034204; - // aapt resource value: 0x7f0d0037 - public const int secondary_text_disabled_material_dark = 2131558455; + // aapt resource value: 0x7F05005D + public const int secondary_text_disabled_material_dark = 2131034205; - // aapt resource value: 0x7f0d0038 - public const int secondary_text_disabled_material_light = 2131558456; + // aapt resource value: 0x7F05005E + public const int secondary_text_disabled_material_light = 2131034206; - // aapt resource value: 0x7f0d0039 - public const int switch_thumb_disabled_material_dark = 2131558457; + // aapt resource value: 0x7F05005F + public const int switch_thumb_disabled_material_dark = 2131034207; - // aapt resource value: 0x7f0d003a - public const int switch_thumb_disabled_material_light = 2131558458; + // aapt resource value: 0x7F050060 + public const int switch_thumb_disabled_material_light = 2131034208; - // aapt resource value: 0x7f0d0065 - public const int switch_thumb_material_dark = 2131558501; + // aapt resource value: 0x7F050061 + public const int switch_thumb_material_dark = 2131034209; - // aapt resource value: 0x7f0d0066 - public const int switch_thumb_material_light = 2131558502; + // aapt resource value: 0x7F050062 + public const int switch_thumb_material_light = 2131034210; - // aapt resource value: 0x7f0d003b - public const int switch_thumb_normal_material_dark = 2131558459; + // aapt resource value: 0x7F050063 + public const int switch_thumb_normal_material_dark = 2131034211; - // aapt resource value: 0x7f0d003c - public const int switch_thumb_normal_material_light = 2131558460; + // aapt resource value: 0x7F050064 + public const int switch_thumb_normal_material_light = 2131034212; - // aapt resource value: 0x7f0d003d - public const int tooltip_background_dark = 2131558461; + // aapt resource value: 0x7F050065 + public const int tooltip_background_dark = 2131034213; - // aapt resource value: 0x7f0d003e - public const int tooltip_background_light = 2131558462; + // aapt resource value: 0x7F050066 + public const int tooltip_background_light = 2131034214; static Color() { @@ -3689,497 +3581,497 @@ private Color() public partial class Dimension { - // aapt resource value: 0x7f08001b - public const int abc_action_bar_content_inset_material = 2131230747; + // aapt resource value: 0x7F060000 + public const int abc_action_bar_content_inset_material = 2131099648; - // aapt resource value: 0x7f08001c - public const int abc_action_bar_content_inset_with_nav = 2131230748; + // aapt resource value: 0x7F060001 + public const int abc_action_bar_content_inset_with_nav = 2131099649; - // aapt resource value: 0x7f080010 - public const int abc_action_bar_default_height_material = 2131230736; + // aapt resource value: 0x7F060002 + public const int abc_action_bar_default_height_material = 2131099650; - // aapt resource value: 0x7f08001d - public const int abc_action_bar_default_padding_end_material = 2131230749; + // aapt resource value: 0x7F060003 + public const int abc_action_bar_default_padding_end_material = 2131099651; - // aapt resource value: 0x7f08001e - public const int abc_action_bar_default_padding_start_material = 2131230750; + // aapt resource value: 0x7F060004 + public const int abc_action_bar_default_padding_start_material = 2131099652; - // aapt resource value: 0x7f080020 - public const int abc_action_bar_elevation_material = 2131230752; + // aapt resource value: 0x7F060005 + public const int abc_action_bar_elevation_material = 2131099653; - // aapt resource value: 0x7f080021 - public const int abc_action_bar_icon_vertical_padding_material = 2131230753; + // aapt resource value: 0x7F060006 + public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - // aapt resource value: 0x7f080022 - public const int abc_action_bar_overflow_padding_end_material = 2131230754; + // aapt resource value: 0x7F060007 + public const int abc_action_bar_overflow_padding_end_material = 2131099655; - // aapt resource value: 0x7f080023 - public const int abc_action_bar_overflow_padding_start_material = 2131230755; + // aapt resource value: 0x7F060008 + public const int abc_action_bar_overflow_padding_start_material = 2131099656; - // aapt resource value: 0x7f080011 - public const int abc_action_bar_progress_bar_size = 2131230737; + // aapt resource value: 0x7F060009 + public const int abc_action_bar_progress_bar_size = 2131099657; - // aapt resource value: 0x7f080024 - public const int abc_action_bar_stacked_max_height = 2131230756; + // aapt resource value: 0x7F06000A + public const int abc_action_bar_stacked_max_height = 2131099658; - // aapt resource value: 0x7f080025 - public const int abc_action_bar_stacked_tab_max_width = 2131230757; + // aapt resource value: 0x7F06000B + public const int abc_action_bar_stacked_tab_max_width = 2131099659; - // aapt resource value: 0x7f080026 - public const int abc_action_bar_subtitle_bottom_margin_material = 2131230758; + // aapt resource value: 0x7F06000C + public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - // aapt resource value: 0x7f080027 - public const int abc_action_bar_subtitle_top_margin_material = 2131230759; + // aapt resource value: 0x7F06000D + public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - // aapt resource value: 0x7f080028 - public const int abc_action_button_min_height_material = 2131230760; + // aapt resource value: 0x7F06000E + public const int abc_action_button_min_height_material = 2131099662; - // aapt resource value: 0x7f080029 - public const int abc_action_button_min_width_material = 2131230761; + // aapt resource value: 0x7F06000F + public const int abc_action_button_min_width_material = 2131099663; - // aapt resource value: 0x7f08002a - public const int abc_action_button_min_width_overflow_material = 2131230762; + // aapt resource value: 0x7F060010 + public const int abc_action_button_min_width_overflow_material = 2131099664; - // aapt resource value: 0x7f08000f - public const int abc_alert_dialog_button_bar_height = 2131230735; + // aapt resource value: 0x7F060011 + public const int abc_alert_dialog_button_bar_height = 2131099665; - // aapt resource value: 0x7f08002b - public const int abc_button_inset_horizontal_material = 2131230763; + // aapt resource value: 0x7F060012 + public const int abc_button_inset_horizontal_material = 2131099666; - // aapt resource value: 0x7f08002c - public const int abc_button_inset_vertical_material = 2131230764; + // aapt resource value: 0x7F060013 + public const int abc_button_inset_vertical_material = 2131099667; - // aapt resource value: 0x7f08002d - public const int abc_button_padding_horizontal_material = 2131230765; + // aapt resource value: 0x7F060014 + public const int abc_button_padding_horizontal_material = 2131099668; - // aapt resource value: 0x7f08002e - public const int abc_button_padding_vertical_material = 2131230766; + // aapt resource value: 0x7F060015 + public const int abc_button_padding_vertical_material = 2131099669; - // aapt resource value: 0x7f08002f - public const int abc_cascading_menus_min_smallest_width = 2131230767; + // aapt resource value: 0x7F060016 + public const int abc_cascading_menus_min_smallest_width = 2131099670; - // aapt resource value: 0x7f080014 - public const int abc_config_prefDialogWidth = 2131230740; + // aapt resource value: 0x7F060017 + public const int abc_config_prefDialogWidth = 2131099671; - // aapt resource value: 0x7f080030 - public const int abc_control_corner_material = 2131230768; + // aapt resource value: 0x7F060018 + public const int abc_control_corner_material = 2131099672; - // aapt resource value: 0x7f080031 - public const int abc_control_inset_material = 2131230769; + // aapt resource value: 0x7F060019 + public const int abc_control_inset_material = 2131099673; - // aapt resource value: 0x7f080032 - public const int abc_control_padding_material = 2131230770; + // aapt resource value: 0x7F06001A + public const int abc_control_padding_material = 2131099674; - // aapt resource value: 0x7f080015 - public const int abc_dialog_fixed_height_major = 2131230741; + // aapt resource value: 0x7F06001B + public const int abc_dialog_fixed_height_major = 2131099675; - // aapt resource value: 0x7f080016 - public const int abc_dialog_fixed_height_minor = 2131230742; + // aapt resource value: 0x7F06001C + public const int abc_dialog_fixed_height_minor = 2131099676; - // aapt resource value: 0x7f080017 - public const int abc_dialog_fixed_width_major = 2131230743; + // aapt resource value: 0x7F06001D + public const int abc_dialog_fixed_width_major = 2131099677; - // aapt resource value: 0x7f080018 - public const int abc_dialog_fixed_width_minor = 2131230744; + // aapt resource value: 0x7F06001E + public const int abc_dialog_fixed_width_minor = 2131099678; - // aapt resource value: 0x7f080033 - public const int abc_dialog_list_padding_bottom_no_buttons = 2131230771; + // aapt resource value: 0x7F06001F + public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - // aapt resource value: 0x7f080034 - public const int abc_dialog_list_padding_top_no_title = 2131230772; + // aapt resource value: 0x7F060020 + public const int abc_dialog_list_padding_top_no_title = 2131099680; - // aapt resource value: 0x7f080019 - public const int abc_dialog_min_width_major = 2131230745; + // aapt resource value: 0x7F060021 + public const int abc_dialog_min_width_major = 2131099681; - // aapt resource value: 0x7f08001a - public const int abc_dialog_min_width_minor = 2131230746; + // aapt resource value: 0x7F060022 + public const int abc_dialog_min_width_minor = 2131099682; - // aapt resource value: 0x7f080035 - public const int abc_dialog_padding_material = 2131230773; + // aapt resource value: 0x7F060023 + public const int abc_dialog_padding_material = 2131099683; - // aapt resource value: 0x7f080036 - public const int abc_dialog_padding_top_material = 2131230774; + // aapt resource value: 0x7F060024 + public const int abc_dialog_padding_top_material = 2131099684; - // aapt resource value: 0x7f080037 - public const int abc_dialog_title_divider_material = 2131230775; + // aapt resource value: 0x7F060025 + public const int abc_dialog_title_divider_material = 2131099685; - // aapt resource value: 0x7f080038 - public const int abc_disabled_alpha_material_dark = 2131230776; + // aapt resource value: 0x7F060026 + public const int abc_disabled_alpha_material_dark = 2131099686; - // aapt resource value: 0x7f080039 - public const int abc_disabled_alpha_material_light = 2131230777; + // aapt resource value: 0x7F060027 + public const int abc_disabled_alpha_material_light = 2131099687; - // aapt resource value: 0x7f08003a - public const int abc_dropdownitem_icon_width = 2131230778; + // aapt resource value: 0x7F060028 + public const int abc_dropdownitem_icon_width = 2131099688; - // aapt resource value: 0x7f08003b - public const int abc_dropdownitem_text_padding_left = 2131230779; + // aapt resource value: 0x7F060029 + public const int abc_dropdownitem_text_padding_left = 2131099689; - // aapt resource value: 0x7f08003c - public const int abc_dropdownitem_text_padding_right = 2131230780; + // aapt resource value: 0x7F06002A + public const int abc_dropdownitem_text_padding_right = 2131099690; - // aapt resource value: 0x7f08003d - public const int abc_edit_text_inset_bottom_material = 2131230781; + // aapt resource value: 0x7F06002B + public const int abc_edit_text_inset_bottom_material = 2131099691; - // aapt resource value: 0x7f08003e - public const int abc_edit_text_inset_horizontal_material = 2131230782; + // aapt resource value: 0x7F06002C + public const int abc_edit_text_inset_horizontal_material = 2131099692; - // aapt resource value: 0x7f08003f - public const int abc_edit_text_inset_top_material = 2131230783; + // aapt resource value: 0x7F06002D + public const int abc_edit_text_inset_top_material = 2131099693; - // aapt resource value: 0x7f080040 - public const int abc_floating_window_z = 2131230784; + // aapt resource value: 0x7F06002E + public const int abc_floating_window_z = 2131099694; - // aapt resource value: 0x7f080041 - public const int abc_list_item_padding_horizontal_material = 2131230785; + // aapt resource value: 0x7F06002F + public const int abc_list_item_padding_horizontal_material = 2131099695; - // aapt resource value: 0x7f080042 - public const int abc_panel_menu_list_width = 2131230786; + // aapt resource value: 0x7F060030 + public const int abc_panel_menu_list_width = 2131099696; - // aapt resource value: 0x7f080043 - public const int abc_progress_bar_height_material = 2131230787; + // aapt resource value: 0x7F060031 + public const int abc_progress_bar_height_material = 2131099697; - // aapt resource value: 0x7f080044 - public const int abc_search_view_preferred_height = 2131230788; + // aapt resource value: 0x7F060032 + public const int abc_search_view_preferred_height = 2131099698; - // aapt resource value: 0x7f080045 - public const int abc_search_view_preferred_width = 2131230789; + // aapt resource value: 0x7F060033 + public const int abc_search_view_preferred_width = 2131099699; - // aapt resource value: 0x7f080046 - public const int abc_seekbar_track_background_height_material = 2131230790; + // aapt resource value: 0x7F060034 + public const int abc_seekbar_track_background_height_material = 2131099700; - // aapt resource value: 0x7f080047 - public const int abc_seekbar_track_progress_height_material = 2131230791; + // aapt resource value: 0x7F060035 + public const int abc_seekbar_track_progress_height_material = 2131099701; - // aapt resource value: 0x7f080048 - public const int abc_select_dialog_padding_start_material = 2131230792; + // aapt resource value: 0x7F060036 + public const int abc_select_dialog_padding_start_material = 2131099702; - // aapt resource value: 0x7f08001f - public const int abc_switch_padding = 2131230751; + // aapt resource value: 0x7F060037 + public const int abc_switch_padding = 2131099703; - // aapt resource value: 0x7f080049 - public const int abc_text_size_body_1_material = 2131230793; + // aapt resource value: 0x7F060038 + public const int abc_text_size_body_1_material = 2131099704; - // aapt resource value: 0x7f08004a - public const int abc_text_size_body_2_material = 2131230794; + // aapt resource value: 0x7F060039 + public const int abc_text_size_body_2_material = 2131099705; - // aapt resource value: 0x7f08004b - public const int abc_text_size_button_material = 2131230795; + // aapt resource value: 0x7F06003A + public const int abc_text_size_button_material = 2131099706; - // aapt resource value: 0x7f08004c - public const int abc_text_size_caption_material = 2131230796; + // aapt resource value: 0x7F06003B + public const int abc_text_size_caption_material = 2131099707; - // aapt resource value: 0x7f08004d - public const int abc_text_size_display_1_material = 2131230797; + // aapt resource value: 0x7F06003C + public const int abc_text_size_display_1_material = 2131099708; - // aapt resource value: 0x7f08004e - public const int abc_text_size_display_2_material = 2131230798; + // aapt resource value: 0x7F06003D + public const int abc_text_size_display_2_material = 2131099709; - // aapt resource value: 0x7f08004f - public const int abc_text_size_display_3_material = 2131230799; + // aapt resource value: 0x7F06003E + public const int abc_text_size_display_3_material = 2131099710; - // aapt resource value: 0x7f080050 - public const int abc_text_size_display_4_material = 2131230800; + // aapt resource value: 0x7F06003F + public const int abc_text_size_display_4_material = 2131099711; - // aapt resource value: 0x7f080051 - public const int abc_text_size_headline_material = 2131230801; + // aapt resource value: 0x7F060040 + public const int abc_text_size_headline_material = 2131099712; - // aapt resource value: 0x7f080052 - public const int abc_text_size_large_material = 2131230802; + // aapt resource value: 0x7F060041 + public const int abc_text_size_large_material = 2131099713; - // aapt resource value: 0x7f080053 - public const int abc_text_size_medium_material = 2131230803; + // aapt resource value: 0x7F060042 + public const int abc_text_size_medium_material = 2131099714; - // aapt resource value: 0x7f080054 - public const int abc_text_size_menu_header_material = 2131230804; + // aapt resource value: 0x7F060043 + public const int abc_text_size_menu_header_material = 2131099715; - // aapt resource value: 0x7f080055 - public const int abc_text_size_menu_material = 2131230805; + // aapt resource value: 0x7F060044 + public const int abc_text_size_menu_material = 2131099716; - // aapt resource value: 0x7f080056 - public const int abc_text_size_small_material = 2131230806; + // aapt resource value: 0x7F060045 + public const int abc_text_size_small_material = 2131099717; - // aapt resource value: 0x7f080057 - public const int abc_text_size_subhead_material = 2131230807; + // aapt resource value: 0x7F060046 + public const int abc_text_size_subhead_material = 2131099718; - // aapt resource value: 0x7f080012 - public const int abc_text_size_subtitle_material_toolbar = 2131230738; + // aapt resource value: 0x7F060047 + public const int abc_text_size_subtitle_material_toolbar = 2131099719; - // aapt resource value: 0x7f080058 - public const int abc_text_size_title_material = 2131230808; + // aapt resource value: 0x7F060048 + public const int abc_text_size_title_material = 2131099720; - // aapt resource value: 0x7f080013 - public const int abc_text_size_title_material_toolbar = 2131230739; + // aapt resource value: 0x7F060049 + public const int abc_text_size_title_material_toolbar = 2131099721; - // aapt resource value: 0x7f08000c - public const int cardview_compat_inset_shadow = 2131230732; + // aapt resource value: 0x7F06004A + public const int cardview_compat_inset_shadow = 2131099722; - // aapt resource value: 0x7f08000d - public const int cardview_default_elevation = 2131230733; + // aapt resource value: 0x7F06004B + public const int cardview_default_elevation = 2131099723; - // aapt resource value: 0x7f08000e - public const int cardview_default_radius = 2131230734; + // aapt resource value: 0x7F06004C + public const int cardview_default_radius = 2131099724; - // aapt resource value: 0x7f080094 - public const int compat_button_inset_horizontal_material = 2131230868; + // aapt resource value: 0x7F06004D + public const int compat_button_inset_horizontal_material = 2131099725; - // aapt resource value: 0x7f080095 - public const int compat_button_inset_vertical_material = 2131230869; + // aapt resource value: 0x7F06004E + public const int compat_button_inset_vertical_material = 2131099726; - // aapt resource value: 0x7f080096 - public const int compat_button_padding_horizontal_material = 2131230870; + // aapt resource value: 0x7F06004F + public const int compat_button_padding_horizontal_material = 2131099727; - // aapt resource value: 0x7f080097 - public const int compat_button_padding_vertical_material = 2131230871; + // aapt resource value: 0x7F060050 + public const int compat_button_padding_vertical_material = 2131099728; - // aapt resource value: 0x7f080098 - public const int compat_control_corner_material = 2131230872; + // aapt resource value: 0x7F060051 + public const int compat_control_corner_material = 2131099729; - // aapt resource value: 0x7f080072 - public const int design_appbar_elevation = 2131230834; + // aapt resource value: 0x7F060052 + public const int design_appbar_elevation = 2131099730; - // aapt resource value: 0x7f080073 - public const int design_bottom_navigation_active_item_max_width = 2131230835; + // aapt resource value: 0x7F060053 + public const int design_bottom_navigation_active_item_max_width = 2131099731; - // aapt resource value: 0x7f080074 - public const int design_bottom_navigation_active_text_size = 2131230836; + // aapt resource value: 0x7F060054 + public const int design_bottom_navigation_active_text_size = 2131099732; - // aapt resource value: 0x7f080075 - public const int design_bottom_navigation_elevation = 2131230837; + // aapt resource value: 0x7F060055 + public const int design_bottom_navigation_elevation = 2131099733; - // aapt resource value: 0x7f080076 - public const int design_bottom_navigation_height = 2131230838; + // aapt resource value: 0x7F060056 + public const int design_bottom_navigation_height = 2131099734; - // aapt resource value: 0x7f080077 - public const int design_bottom_navigation_item_max_width = 2131230839; + // aapt resource value: 0x7F060057 + public const int design_bottom_navigation_item_max_width = 2131099735; - // aapt resource value: 0x7f080078 - public const int design_bottom_navigation_item_min_width = 2131230840; + // aapt resource value: 0x7F060058 + public const int design_bottom_navigation_item_min_width = 2131099736; - // aapt resource value: 0x7f080079 - public const int design_bottom_navigation_margin = 2131230841; + // aapt resource value: 0x7F060059 + public const int design_bottom_navigation_margin = 2131099737; - // aapt resource value: 0x7f08007a - public const int design_bottom_navigation_shadow_height = 2131230842; + // aapt resource value: 0x7F06005A + public const int design_bottom_navigation_shadow_height = 2131099738; - // aapt resource value: 0x7f08007b - public const int design_bottom_navigation_text_size = 2131230843; + // aapt resource value: 0x7F06005B + public const int design_bottom_navigation_text_size = 2131099739; - // aapt resource value: 0x7f08007c - public const int design_bottom_sheet_modal_elevation = 2131230844; + // aapt resource value: 0x7F06005C + public const int design_bottom_sheet_modal_elevation = 2131099740; - // aapt resource value: 0x7f08007d - public const int design_bottom_sheet_peek_height_min = 2131230845; + // aapt resource value: 0x7F06005D + public const int design_bottom_sheet_peek_height_min = 2131099741; - // aapt resource value: 0x7f08007e - public const int design_fab_border_width = 2131230846; + // aapt resource value: 0x7F06005E + public const int design_fab_border_width = 2131099742; - // aapt resource value: 0x7f08007f - public const int design_fab_elevation = 2131230847; + // aapt resource value: 0x7F06005F + public const int design_fab_elevation = 2131099743; - // aapt resource value: 0x7f080080 - public const int design_fab_image_size = 2131230848; + // aapt resource value: 0x7F060060 + public const int design_fab_image_size = 2131099744; - // aapt resource value: 0x7f080081 - public const int design_fab_size_mini = 2131230849; + // aapt resource value: 0x7F060061 + public const int design_fab_size_mini = 2131099745; - // aapt resource value: 0x7f080082 - public const int design_fab_size_normal = 2131230850; + // aapt resource value: 0x7F060062 + public const int design_fab_size_normal = 2131099746; - // aapt resource value: 0x7f080083 - public const int design_fab_translation_z_pressed = 2131230851; + // aapt resource value: 0x7F060063 + public const int design_fab_translation_z_pressed = 2131099747; - // aapt resource value: 0x7f080084 - public const int design_navigation_elevation = 2131230852; + // aapt resource value: 0x7F060064 + public const int design_navigation_elevation = 2131099748; - // aapt resource value: 0x7f080085 - public const int design_navigation_icon_padding = 2131230853; + // aapt resource value: 0x7F060065 + public const int design_navigation_icon_padding = 2131099749; - // aapt resource value: 0x7f080086 - public const int design_navigation_icon_size = 2131230854; + // aapt resource value: 0x7F060066 + public const int design_navigation_icon_size = 2131099750; - // aapt resource value: 0x7f08006a - public const int design_navigation_max_width = 2131230826; + // aapt resource value: 0x7F060067 + public const int design_navigation_max_width = 2131099751; - // aapt resource value: 0x7f080087 - public const int design_navigation_padding_bottom = 2131230855; + // aapt resource value: 0x7F060068 + public const int design_navigation_padding_bottom = 2131099752; - // aapt resource value: 0x7f080088 - public const int design_navigation_separator_vertical_padding = 2131230856; + // aapt resource value: 0x7F060069 + public const int design_navigation_separator_vertical_padding = 2131099753; - // aapt resource value: 0x7f08006b - public const int design_snackbar_action_inline_max_width = 2131230827; + // aapt resource value: 0x7F06006A + public const int design_snackbar_action_inline_max_width = 2131099754; - // aapt resource value: 0x7f08006c - public const int design_snackbar_background_corner_radius = 2131230828; + // aapt resource value: 0x7F06006B + public const int design_snackbar_background_corner_radius = 2131099755; - // aapt resource value: 0x7f080089 - public const int design_snackbar_elevation = 2131230857; + // aapt resource value: 0x7F06006C + public const int design_snackbar_elevation = 2131099756; - // aapt resource value: 0x7f08006d - public const int design_snackbar_extra_spacing_horizontal = 2131230829; + // aapt resource value: 0x7F06006D + public const int design_snackbar_extra_spacing_horizontal = 2131099757; - // aapt resource value: 0x7f08006e - public const int design_snackbar_max_width = 2131230830; + // aapt resource value: 0x7F06006E + public const int design_snackbar_max_width = 2131099758; - // aapt resource value: 0x7f08006f - public const int design_snackbar_min_width = 2131230831; + // aapt resource value: 0x7F06006F + public const int design_snackbar_min_width = 2131099759; - // aapt resource value: 0x7f08008a - public const int design_snackbar_padding_horizontal = 2131230858; + // aapt resource value: 0x7F060070 + public const int design_snackbar_padding_horizontal = 2131099760; - // aapt resource value: 0x7f08008b - public const int design_snackbar_padding_vertical = 2131230859; + // aapt resource value: 0x7F060071 + public const int design_snackbar_padding_vertical = 2131099761; - // aapt resource value: 0x7f080070 - public const int design_snackbar_padding_vertical_2lines = 2131230832; + // aapt resource value: 0x7F060072 + public const int design_snackbar_padding_vertical_2lines = 2131099762; - // aapt resource value: 0x7f08008c - public const int design_snackbar_text_size = 2131230860; + // aapt resource value: 0x7F060073 + public const int design_snackbar_text_size = 2131099763; - // aapt resource value: 0x7f08008d - public const int design_tab_max_width = 2131230861; + // aapt resource value: 0x7F060074 + public const int design_tab_max_width = 2131099764; - // aapt resource value: 0x7f080071 - public const int design_tab_scrollable_min_width = 2131230833; + // aapt resource value: 0x7F060075 + public const int design_tab_scrollable_min_width = 2131099765; - // aapt resource value: 0x7f08008e - public const int design_tab_text_size = 2131230862; + // aapt resource value: 0x7F060076 + public const int design_tab_text_size = 2131099766; - // aapt resource value: 0x7f08008f - public const int design_tab_text_size_2line = 2131230863; + // aapt resource value: 0x7F060077 + public const int design_tab_text_size_2line = 2131099767; - // aapt resource value: 0x7f080059 - public const int disabled_alpha_material_dark = 2131230809; + // aapt resource value: 0x7F060078 + public const int disabled_alpha_material_dark = 2131099768; - // aapt resource value: 0x7f08005a - public const int disabled_alpha_material_light = 2131230810; + // aapt resource value: 0x7F060079 + public const int disabled_alpha_material_light = 2131099769; - // aapt resource value: 0x7f080000 - public const int fastscroll_default_thickness = 2131230720; + // aapt resource value: 0x7F06007A + public const int fastscroll_default_thickness = 2131099770; - // aapt resource value: 0x7f080001 - public const int fastscroll_margin = 2131230721; + // aapt resource value: 0x7F06007B + public const int fastscroll_margin = 2131099771; - // aapt resource value: 0x7f080002 - public const int fastscroll_minimum_range = 2131230722; + // aapt resource value: 0x7F06007C + public const int fastscroll_minimum_range = 2131099772; - // aapt resource value: 0x7f08005b - public const int highlight_alpha_material_colored = 2131230811; + // aapt resource value: 0x7F06007D + public const int highlight_alpha_material_colored = 2131099773; - // aapt resource value: 0x7f08005c - public const int highlight_alpha_material_dark = 2131230812; + // aapt resource value: 0x7F06007E + public const int highlight_alpha_material_dark = 2131099774; - // aapt resource value: 0x7f08005d - public const int highlight_alpha_material_light = 2131230813; + // aapt resource value: 0x7F06007F + public const int highlight_alpha_material_light = 2131099775; - // aapt resource value: 0x7f08005e - public const int hint_alpha_material_dark = 2131230814; + // aapt resource value: 0x7F060080 + public const int hint_alpha_material_dark = 2131099776; - // aapt resource value: 0x7f08005f - public const int hint_alpha_material_light = 2131230815; + // aapt resource value: 0x7F060081 + public const int hint_alpha_material_light = 2131099777; - // aapt resource value: 0x7f080060 - public const int hint_pressed_alpha_material_dark = 2131230816; + // aapt resource value: 0x7F060082 + public const int hint_pressed_alpha_material_dark = 2131099778; - // aapt resource value: 0x7f080061 - public const int hint_pressed_alpha_material_light = 2131230817; + // aapt resource value: 0x7F060083 + public const int hint_pressed_alpha_material_light = 2131099779; - // aapt resource value: 0x7f080003 - public const int item_touch_helper_max_drag_scroll_per_frame = 2131230723; + // aapt resource value: 0x7F060084 + public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - // aapt resource value: 0x7f080004 - public const int item_touch_helper_swipe_escape_max_velocity = 2131230724; + // aapt resource value: 0x7F060085 + public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - // aapt resource value: 0x7f080005 - public const int item_touch_helper_swipe_escape_velocity = 2131230725; + // aapt resource value: 0x7F060086 + public const int item_touch_helper_swipe_escape_velocity = 2131099782; - // aapt resource value: 0x7f080006 - public const int mr_controller_volume_group_list_item_height = 2131230726; + // aapt resource value: 0x7F060087 + public const int mr_controller_volume_group_list_item_height = 2131099783; - // aapt resource value: 0x7f080007 - public const int mr_controller_volume_group_list_item_icon_size = 2131230727; + // aapt resource value: 0x7F060088 + public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - // aapt resource value: 0x7f080008 - public const int mr_controller_volume_group_list_max_height = 2131230728; + // aapt resource value: 0x7F060089 + public const int mr_controller_volume_group_list_max_height = 2131099785; - // aapt resource value: 0x7f08000b - public const int mr_controller_volume_group_list_padding_top = 2131230731; + // aapt resource value: 0x7F06008A + public const int mr_controller_volume_group_list_padding_top = 2131099786; - // aapt resource value: 0x7f080009 - public const int mr_dialog_fixed_width_major = 2131230729; + // aapt resource value: 0x7F06008B + public const int mr_dialog_fixed_width_major = 2131099787; - // aapt resource value: 0x7f08000a - public const int mr_dialog_fixed_width_minor = 2131230730; + // aapt resource value: 0x7F06008C + public const int mr_dialog_fixed_width_minor = 2131099788; - // aapt resource value: 0x7f080099 - public const int notification_action_icon_size = 2131230873; + // aapt resource value: 0x7F06008D + public const int notification_action_icon_size = 2131099789; - // aapt resource value: 0x7f08009a - public const int notification_action_text_size = 2131230874; + // aapt resource value: 0x7F06008E + public const int notification_action_text_size = 2131099790; - // aapt resource value: 0x7f08009b - public const int notification_big_circle_margin = 2131230875; + // aapt resource value: 0x7F06008F + public const int notification_big_circle_margin = 2131099791; - // aapt resource value: 0x7f080091 - public const int notification_content_margin_start = 2131230865; + // aapt resource value: 0x7F060090 + public const int notification_content_margin_start = 2131099792; - // aapt resource value: 0x7f08009c - public const int notification_large_icon_height = 2131230876; + // aapt resource value: 0x7F060091 + public const int notification_large_icon_height = 2131099793; - // aapt resource value: 0x7f08009d - public const int notification_large_icon_width = 2131230877; + // aapt resource value: 0x7F060092 + public const int notification_large_icon_width = 2131099794; - // aapt resource value: 0x7f080092 - public const int notification_main_column_padding_top = 2131230866; + // aapt resource value: 0x7F060093 + public const int notification_main_column_padding_top = 2131099795; - // aapt resource value: 0x7f080093 - public const int notification_media_narrow_margin = 2131230867; + // aapt resource value: 0x7F060094 + public const int notification_media_narrow_margin = 2131099796; - // aapt resource value: 0x7f08009e - public const int notification_right_icon_size = 2131230878; + // aapt resource value: 0x7F060095 + public const int notification_right_icon_size = 2131099797; - // aapt resource value: 0x7f080090 - public const int notification_right_side_padding_top = 2131230864; + // aapt resource value: 0x7F060096 + public const int notification_right_side_padding_top = 2131099798; - // aapt resource value: 0x7f08009f - public const int notification_small_icon_background_padding = 2131230879; + // aapt resource value: 0x7F060097 + public const int notification_small_icon_background_padding = 2131099799; - // aapt resource value: 0x7f0800a0 - public const int notification_small_icon_size_as_large = 2131230880; + // aapt resource value: 0x7F060098 + public const int notification_small_icon_size_as_large = 2131099800; - // aapt resource value: 0x7f0800a1 - public const int notification_subtext_size = 2131230881; + // aapt resource value: 0x7F060099 + public const int notification_subtext_size = 2131099801; - // aapt resource value: 0x7f0800a2 - public const int notification_top_pad = 2131230882; + // aapt resource value: 0x7F06009A + public const int notification_top_pad = 2131099802; - // aapt resource value: 0x7f0800a3 - public const int notification_top_pad_large_text = 2131230883; + // aapt resource value: 0x7F06009B + public const int notification_top_pad_large_text = 2131099803; - // aapt resource value: 0x7f080062 - public const int tooltip_corner_radius = 2131230818; + // aapt resource value: 0x7F06009C + public const int tooltip_corner_radius = 2131099804; - // aapt resource value: 0x7f080063 - public const int tooltip_horizontal_padding = 2131230819; + // aapt resource value: 0x7F06009D + public const int tooltip_horizontal_padding = 2131099805; - // aapt resource value: 0x7f080064 - public const int tooltip_margin = 2131230820; + // aapt resource value: 0x7F06009E + public const int tooltip_margin = 2131099806; - // aapt resource value: 0x7f080065 - public const int tooltip_precise_anchor_extra_offset = 2131230821; + // aapt resource value: 0x7F06009F + public const int tooltip_precise_anchor_extra_offset = 2131099807; - // aapt resource value: 0x7f080066 - public const int tooltip_precise_anchor_threshold = 2131230822; + // aapt resource value: 0x7F0600A0 + public const int tooltip_precise_anchor_threshold = 2131099808; - // aapt resource value: 0x7f080067 - public const int tooltip_vertical_padding = 2131230823; + // aapt resource value: 0x7F0600A1 + public const int tooltip_vertical_padding = 2131099809; - // aapt resource value: 0x7f080068 - public const int tooltip_y_offset_non_touch = 2131230824; + // aapt resource value: 0x7F0600A2 + public const int tooltip_y_offset_non_touch = 2131099810; - // aapt resource value: 0x7f080069 - public const int tooltip_y_offset_touch = 2131230825; + // aapt resource value: 0x7F0600A3 + public const int tooltip_y_offset_touch = 2131099811; static Dimension() { @@ -4194,932 +4086,914 @@ private Dimension() public partial class Drawable { - // aapt resource value: 0x7f020000 - public const int abc_ab_share_pack_mtrl_alpha = 2130837504; + // aapt resource value: 0x7F070006 + public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - // aapt resource value: 0x7f020001 - public const int abc_action_bar_item_background_material = 2130837505; + // aapt resource value: 0x7F070007 + public const int abc_action_bar_item_background_material = 2131165191; - // aapt resource value: 0x7f020002 - public const int abc_btn_borderless_material = 2130837506; + // aapt resource value: 0x7F070008 + public const int abc_btn_borderless_material = 2131165192; - // aapt resource value: 0x7f020003 - public const int abc_btn_check_material = 2130837507; + // aapt resource value: 0x7F070009 + public const int abc_btn_check_material = 2131165193; - // aapt resource value: 0x7f020004 - public const int abc_btn_check_to_on_mtrl_000 = 2130837508; + // aapt resource value: 0x7F07000A + public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - // aapt resource value: 0x7f020005 - public const int abc_btn_check_to_on_mtrl_015 = 2130837509; + // aapt resource value: 0x7F07000B + public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - // aapt resource value: 0x7f020006 - public const int abc_btn_colored_material = 2130837510; + // aapt resource value: 0x7F07000C + public const int abc_btn_colored_material = 2131165196; - // aapt resource value: 0x7f020007 - public const int abc_btn_default_mtrl_shape = 2130837511; + // aapt resource value: 0x7F07000D + public const int abc_btn_default_mtrl_shape = 2131165197; - // aapt resource value: 0x7f020008 - public const int abc_btn_radio_material = 2130837512; + // aapt resource value: 0x7F07000E + public const int abc_btn_radio_material = 2131165198; - // aapt resource value: 0x7f020009 - public const int abc_btn_radio_to_on_mtrl_000 = 2130837513; + // aapt resource value: 0x7F07000F + public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - // aapt resource value: 0x7f02000a - public const int abc_btn_radio_to_on_mtrl_015 = 2130837514; + // aapt resource value: 0x7F070010 + public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - // aapt resource value: 0x7f02000b - public const int abc_btn_switch_to_on_mtrl_00001 = 2130837515; + // aapt resource value: 0x7F070011 + public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - // aapt resource value: 0x7f02000c - public const int abc_btn_switch_to_on_mtrl_00012 = 2130837516; + // aapt resource value: 0x7F070012 + public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - // aapt resource value: 0x7f02000d - public const int abc_cab_background_internal_bg = 2130837517; + // aapt resource value: 0x7F070013 + public const int abc_cab_background_internal_bg = 2131165203; - // aapt resource value: 0x7f02000e - public const int abc_cab_background_top_material = 2130837518; + // aapt resource value: 0x7F070014 + public const int abc_cab_background_top_material = 2131165204; - // aapt resource value: 0x7f02000f - public const int abc_cab_background_top_mtrl_alpha = 2130837519; + // aapt resource value: 0x7F070015 + public const int abc_cab_background_top_mtrl_alpha = 2131165205; - // aapt resource value: 0x7f020010 - public const int abc_control_background_material = 2130837520; + // aapt resource value: 0x7F070016 + public const int abc_control_background_material = 2131165206; - // aapt resource value: 0x7f020011 - public const int abc_dialog_material_background = 2130837521; + // aapt resource value: 0x7F070017 + public const int abc_dialog_material_background = 2131165207; - // aapt resource value: 0x7f020012 - public const int abc_edit_text_material = 2130837522; + // aapt resource value: 0x7F070018 + public const int abc_edit_text_material = 2131165208; - // aapt resource value: 0x7f020013 - public const int abc_ic_ab_back_material = 2130837523; + // aapt resource value: 0x7F070019 + public const int abc_ic_ab_back_material = 2131165209; - // aapt resource value: 0x7f020014 - public const int abc_ic_arrow_drop_right_black_24dp = 2130837524; + // aapt resource value: 0x7F07001A + public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - // aapt resource value: 0x7f020015 - public const int abc_ic_clear_material = 2130837525; + // aapt resource value: 0x7F07001B + public const int abc_ic_clear_material = 2131165211; - // aapt resource value: 0x7f020016 - public const int abc_ic_commit_search_api_mtrl_alpha = 2130837526; + // aapt resource value: 0x7F07001C + public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - // aapt resource value: 0x7f020017 - public const int abc_ic_go_search_api_material = 2130837527; + // aapt resource value: 0x7F07001D + public const int abc_ic_go_search_api_material = 2131165213; - // aapt resource value: 0x7f020018 - public const int abc_ic_menu_copy_mtrl_am_alpha = 2130837528; + // aapt resource value: 0x7F07001E + public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - // aapt resource value: 0x7f020019 - public const int abc_ic_menu_cut_mtrl_alpha = 2130837529; + // aapt resource value: 0x7F07001F + public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - // aapt resource value: 0x7f02001a - public const int abc_ic_menu_overflow_material = 2130837530; + // aapt resource value: 0x7F070020 + public const int abc_ic_menu_overflow_material = 2131165216; - // aapt resource value: 0x7f02001b - public const int abc_ic_menu_paste_mtrl_am_alpha = 2130837531; + // aapt resource value: 0x7F070021 + public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - // aapt resource value: 0x7f02001c - public const int abc_ic_menu_selectall_mtrl_alpha = 2130837532; + // aapt resource value: 0x7F070022 + public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - // aapt resource value: 0x7f02001d - public const int abc_ic_menu_share_mtrl_alpha = 2130837533; + // aapt resource value: 0x7F070023 + public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - // aapt resource value: 0x7f02001e - public const int abc_ic_search_api_material = 2130837534; + // aapt resource value: 0x7F070024 + public const int abc_ic_search_api_material = 2131165220; - // aapt resource value: 0x7f02001f - public const int abc_ic_star_black_16dp = 2130837535; + // aapt resource value: 0x7F070025 + public const int abc_ic_star_black_16dp = 2131165221; - // aapt resource value: 0x7f020020 - public const int abc_ic_star_black_36dp = 2130837536; + // aapt resource value: 0x7F070026 + public const int abc_ic_star_black_36dp = 2131165222; - // aapt resource value: 0x7f020021 - public const int abc_ic_star_black_48dp = 2130837537; + // aapt resource value: 0x7F070027 + public const int abc_ic_star_black_48dp = 2131165223; - // aapt resource value: 0x7f020022 - public const int abc_ic_star_half_black_16dp = 2130837538; + // aapt resource value: 0x7F070028 + public const int abc_ic_star_half_black_16dp = 2131165224; - // aapt resource value: 0x7f020023 - public const int abc_ic_star_half_black_36dp = 2130837539; + // aapt resource value: 0x7F070029 + public const int abc_ic_star_half_black_36dp = 2131165225; - // aapt resource value: 0x7f020024 - public const int abc_ic_star_half_black_48dp = 2130837540; + // aapt resource value: 0x7F07002A + public const int abc_ic_star_half_black_48dp = 2131165226; - // aapt resource value: 0x7f020025 - public const int abc_ic_voice_search_api_material = 2130837541; + // aapt resource value: 0x7F07002B + public const int abc_ic_voice_search_api_material = 2131165227; - // aapt resource value: 0x7f020026 - public const int abc_item_background_holo_dark = 2130837542; + // aapt resource value: 0x7F07002C + public const int abc_item_background_holo_dark = 2131165228; - // aapt resource value: 0x7f020027 - public const int abc_item_background_holo_light = 2130837543; + // aapt resource value: 0x7F07002D + public const int abc_item_background_holo_light = 2131165229; - // aapt resource value: 0x7f020028 - public const int abc_list_divider_mtrl_alpha = 2130837544; + // aapt resource value: 0x7F07002E + public const int abc_list_divider_mtrl_alpha = 2131165230; - // aapt resource value: 0x7f020029 - public const int abc_list_focused_holo = 2130837545; + // aapt resource value: 0x7F07002F + public const int abc_list_focused_holo = 2131165231; - // aapt resource value: 0x7f02002a - public const int abc_list_longpressed_holo = 2130837546; + // aapt resource value: 0x7F070030 + public const int abc_list_longpressed_holo = 2131165232; - // aapt resource value: 0x7f02002b - public const int abc_list_pressed_holo_dark = 2130837547; + // aapt resource value: 0x7F070031 + public const int abc_list_pressed_holo_dark = 2131165233; - // aapt resource value: 0x7f02002c - public const int abc_list_pressed_holo_light = 2130837548; + // aapt resource value: 0x7F070032 + public const int abc_list_pressed_holo_light = 2131165234; - // aapt resource value: 0x7f02002d - public const int abc_list_selector_background_transition_holo_dark = 2130837549; + // aapt resource value: 0x7F070033 + public const int abc_list_selector_background_transition_holo_dark = 2131165235; - // aapt resource value: 0x7f02002e - public const int abc_list_selector_background_transition_holo_light = 2130837550; + // aapt resource value: 0x7F070034 + public const int abc_list_selector_background_transition_holo_light = 2131165236; - // aapt resource value: 0x7f02002f - public const int abc_list_selector_disabled_holo_dark = 2130837551; + // aapt resource value: 0x7F070035 + public const int abc_list_selector_disabled_holo_dark = 2131165237; - // aapt resource value: 0x7f020030 - public const int abc_list_selector_disabled_holo_light = 2130837552; + // aapt resource value: 0x7F070036 + public const int abc_list_selector_disabled_holo_light = 2131165238; - // aapt resource value: 0x7f020031 - public const int abc_list_selector_holo_dark = 2130837553; + // aapt resource value: 0x7F070037 + public const int abc_list_selector_holo_dark = 2131165239; - // aapt resource value: 0x7f020032 - public const int abc_list_selector_holo_light = 2130837554; + // aapt resource value: 0x7F070038 + public const int abc_list_selector_holo_light = 2131165240; - // aapt resource value: 0x7f020033 - public const int abc_menu_hardkey_panel_mtrl_mult = 2130837555; + // aapt resource value: 0x7F070039 + public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - // aapt resource value: 0x7f020034 - public const int abc_popup_background_mtrl_mult = 2130837556; + // aapt resource value: 0x7F07003A + public const int abc_popup_background_mtrl_mult = 2131165242; - // aapt resource value: 0x7f020035 - public const int abc_ratingbar_indicator_material = 2130837557; + // aapt resource value: 0x7F07003B + public const int abc_ratingbar_indicator_material = 2131165243; - // aapt resource value: 0x7f020036 - public const int abc_ratingbar_material = 2130837558; + // aapt resource value: 0x7F07003C + public const int abc_ratingbar_material = 2131165244; - // aapt resource value: 0x7f020037 - public const int abc_ratingbar_small_material = 2130837559; + // aapt resource value: 0x7F07003D + public const int abc_ratingbar_small_material = 2131165245; - // aapt resource value: 0x7f020038 - public const int abc_scrubber_control_off_mtrl_alpha = 2130837560; + // aapt resource value: 0x7F07003E + public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - // aapt resource value: 0x7f020039 - public const int abc_scrubber_control_to_pressed_mtrl_000 = 2130837561; + // aapt resource value: 0x7F07003F + public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - // aapt resource value: 0x7f02003a - public const int abc_scrubber_control_to_pressed_mtrl_005 = 2130837562; + // aapt resource value: 0x7F070040 + public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - // aapt resource value: 0x7f02003b - public const int abc_scrubber_primary_mtrl_alpha = 2130837563; + // aapt resource value: 0x7F070041 + public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - // aapt resource value: 0x7f02003c - public const int abc_scrubber_track_mtrl_alpha = 2130837564; + // aapt resource value: 0x7F070042 + public const int abc_scrubber_track_mtrl_alpha = 2131165250; - // aapt resource value: 0x7f02003d - public const int abc_seekbar_thumb_material = 2130837565; + // aapt resource value: 0x7F070043 + public const int abc_seekbar_thumb_material = 2131165251; - // aapt resource value: 0x7f02003e - public const int abc_seekbar_tick_mark_material = 2130837566; + // aapt resource value: 0x7F070044 + public const int abc_seekbar_tick_mark_material = 2131165252; - // aapt resource value: 0x7f02003f - public const int abc_seekbar_track_material = 2130837567; + // aapt resource value: 0x7F070045 + public const int abc_seekbar_track_material = 2131165253; - // aapt resource value: 0x7f020040 - public const int abc_spinner_mtrl_am_alpha = 2130837568; + // aapt resource value: 0x7F070046 + public const int abc_spinner_mtrl_am_alpha = 2131165254; - // aapt resource value: 0x7f020041 - public const int abc_spinner_textfield_background_material = 2130837569; + // aapt resource value: 0x7F070047 + public const int abc_spinner_textfield_background_material = 2131165255; - // aapt resource value: 0x7f020042 - public const int abc_switch_thumb_material = 2130837570; + // aapt resource value: 0x7F070048 + public const int abc_switch_thumb_material = 2131165256; - // aapt resource value: 0x7f020043 - public const int abc_switch_track_mtrl_alpha = 2130837571; + // aapt resource value: 0x7F070049 + public const int abc_switch_track_mtrl_alpha = 2131165257; - // aapt resource value: 0x7f020044 - public const int abc_tab_indicator_material = 2130837572; + // aapt resource value: 0x7F07004A + public const int abc_tab_indicator_material = 2131165258; - // aapt resource value: 0x7f020045 - public const int abc_tab_indicator_mtrl_alpha = 2130837573; + // aapt resource value: 0x7F07004B + public const int abc_tab_indicator_mtrl_alpha = 2131165259; - // aapt resource value: 0x7f020046 - public const int abc_text_cursor_material = 2130837574; + // aapt resource value: 0x7F070053 + public const int abc_textfield_activated_mtrl_alpha = 2131165267; - // aapt resource value: 0x7f020047 - public const int abc_text_select_handle_left_mtrl_dark = 2130837575; + // aapt resource value: 0x7F070054 + public const int abc_textfield_default_mtrl_alpha = 2131165268; - // aapt resource value: 0x7f020048 - public const int abc_text_select_handle_left_mtrl_light = 2130837576; + // aapt resource value: 0x7F070055 + public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - // aapt resource value: 0x7f020049 - public const int abc_text_select_handle_middle_mtrl_dark = 2130837577; + // aapt resource value: 0x7F070056 + public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - // aapt resource value: 0x7f02004a - public const int abc_text_select_handle_middle_mtrl_light = 2130837578; + // aapt resource value: 0x7F070057 + public const int abc_textfield_search_material = 2131165271; - // aapt resource value: 0x7f02004b - public const int abc_text_select_handle_right_mtrl_dark = 2130837579; + // aapt resource value: 0x7F07004C + public const int abc_text_cursor_material = 2131165260; - // aapt resource value: 0x7f02004c - public const int abc_text_select_handle_right_mtrl_light = 2130837580; + // aapt resource value: 0x7F07004D + public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - // aapt resource value: 0x7f02004d - public const int abc_textfield_activated_mtrl_alpha = 2130837581; + // aapt resource value: 0x7F07004E + public const int abc_text_select_handle_left_mtrl_light = 2131165262; - // aapt resource value: 0x7f02004e - public const int abc_textfield_default_mtrl_alpha = 2130837582; + // aapt resource value: 0x7F07004F + public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - // aapt resource value: 0x7f02004f - public const int abc_textfield_search_activated_mtrl_alpha = 2130837583; + // aapt resource value: 0x7F070050 + public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - // aapt resource value: 0x7f020050 - public const int abc_textfield_search_default_mtrl_alpha = 2130837584; + // aapt resource value: 0x7F070051 + public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - // aapt resource value: 0x7f020051 - public const int abc_textfield_search_material = 2130837585; + // aapt resource value: 0x7F070052 + public const int abc_text_select_handle_right_mtrl_light = 2131165266; - // aapt resource value: 0x7f020052 - public const int abc_vector_test = 2130837586; + // aapt resource value: 0x7F070058 + public const int abc_vector_test = 2131165272; - // aapt resource value: 0x7f020053 - public const int avd_hide_password = 2130837587; + // aapt resource value: 0x7F070059 + public const int avd_hide_password = 2131165273; - // aapt resource value: 0x7f02012f - public const int avd_hide_password_1 = 2130837807; + // aapt resource value: 0x7F07005A + public const int avd_show_password = 2131165274; - // aapt resource value: 0x7f020130 - public const int avd_hide_password_2 = 2130837808; + // aapt resource value: 0x7F07005B + public const int design_bottom_navigation_item_background = 2131165275; - // aapt resource value: 0x7f020131 - public const int avd_hide_password_3 = 2130837809; + // aapt resource value: 0x7F07005C + public const int design_fab_background = 2131165276; - // aapt resource value: 0x7f020054 - public const int avd_show_password = 2130837588; + // aapt resource value: 0x7F07005D + public const int design_ic_visibility = 2131165277; - // aapt resource value: 0x7f020132 - public const int avd_show_password_1 = 2130837810; + // aapt resource value: 0x7F07005E + public const int design_ic_visibility_off = 2131165278; - // aapt resource value: 0x7f020133 - public const int avd_show_password_2 = 2130837811; + // aapt resource value: 0x7F07005F + public const int design_password_eye = 2131165279; - // aapt resource value: 0x7f020134 - public const int avd_show_password_3 = 2130837812; + // aapt resource value: 0x7F070060 + public const int design_snackbar_background = 2131165280; - // aapt resource value: 0x7f020055 - public const int design_bottom_navigation_item_background = 2130837589; + // aapt resource value: 0x7F070061 + public const int ic_audiotrack_dark = 2131165281; - // aapt resource value: 0x7f020056 - public const int design_fab_background = 2130837590; + // aapt resource value: 0x7F070062 + public const int ic_audiotrack_light = 2131165282; - // aapt resource value: 0x7f020057 - public const int design_ic_visibility = 2130837591; + // aapt resource value: 0x7F070063 + public const int ic_dialog_close_dark = 2131165283; - // aapt resource value: 0x7f020058 - public const int design_ic_visibility_off = 2130837592; + // aapt resource value: 0x7F070064 + public const int ic_dialog_close_light = 2131165284; - // aapt resource value: 0x7f020059 - public const int design_password_eye = 2130837593; + // aapt resource value: 0x7F070065 + public const int ic_group_collapse_00 = 2131165285; - // aapt resource value: 0x7f02005a - public const int design_snackbar_background = 2130837594; + // aapt resource value: 0x7F070066 + public const int ic_group_collapse_01 = 2131165286; - // aapt resource value: 0x7f02005b - public const int ic_audiotrack_dark = 2130837595; + // aapt resource value: 0x7F070067 + public const int ic_group_collapse_02 = 2131165287; - // aapt resource value: 0x7f02005c - public const int ic_audiotrack_light = 2130837596; + // aapt resource value: 0x7F070068 + public const int ic_group_collapse_03 = 2131165288; - // aapt resource value: 0x7f02005d - public const int ic_dialog_close_dark = 2130837597; + // aapt resource value: 0x7F070069 + public const int ic_group_collapse_04 = 2131165289; - // aapt resource value: 0x7f02005e - public const int ic_dialog_close_light = 2130837598; + // aapt resource value: 0x7F07006A + public const int ic_group_collapse_05 = 2131165290; - // aapt resource value: 0x7f02005f - public const int ic_group_collapse_00 = 2130837599; + // aapt resource value: 0x7F07006B + public const int ic_group_collapse_06 = 2131165291; - // aapt resource value: 0x7f020060 - public const int ic_group_collapse_01 = 2130837600; + // aapt resource value: 0x7F07006C + public const int ic_group_collapse_07 = 2131165292; - // aapt resource value: 0x7f020061 - public const int ic_group_collapse_02 = 2130837601; + // aapt resource value: 0x7F07006D + public const int ic_group_collapse_08 = 2131165293; - // aapt resource value: 0x7f020062 - public const int ic_group_collapse_03 = 2130837602; + // aapt resource value: 0x7F07006E + public const int ic_group_collapse_09 = 2131165294; - // aapt resource value: 0x7f020063 - public const int ic_group_collapse_04 = 2130837603; + // aapt resource value: 0x7F07006F + public const int ic_group_collapse_10 = 2131165295; - // aapt resource value: 0x7f020064 - public const int ic_group_collapse_05 = 2130837604; + // aapt resource value: 0x7F070070 + public const int ic_group_collapse_11 = 2131165296; - // aapt resource value: 0x7f020065 - public const int ic_group_collapse_06 = 2130837605; + // aapt resource value: 0x7F070071 + public const int ic_group_collapse_12 = 2131165297; - // aapt resource value: 0x7f020066 - public const int ic_group_collapse_07 = 2130837606; + // aapt resource value: 0x7F070072 + public const int ic_group_collapse_13 = 2131165298; - // aapt resource value: 0x7f020067 - public const int ic_group_collapse_08 = 2130837607; + // aapt resource value: 0x7F070073 + public const int ic_group_collapse_14 = 2131165299; - // aapt resource value: 0x7f020068 - public const int ic_group_collapse_09 = 2130837608; + // aapt resource value: 0x7F070074 + public const int ic_group_collapse_15 = 2131165300; - // aapt resource value: 0x7f020069 - public const int ic_group_collapse_10 = 2130837609; + // aapt resource value: 0x7F070075 + public const int ic_group_expand_00 = 2131165301; - // aapt resource value: 0x7f02006a - public const int ic_group_collapse_11 = 2130837610; + // aapt resource value: 0x7F070076 + public const int ic_group_expand_01 = 2131165302; - // aapt resource value: 0x7f02006b - public const int ic_group_collapse_12 = 2130837611; + // aapt resource value: 0x7F070077 + public const int ic_group_expand_02 = 2131165303; - // aapt resource value: 0x7f02006c - public const int ic_group_collapse_13 = 2130837612; + // aapt resource value: 0x7F070078 + public const int ic_group_expand_03 = 2131165304; - // aapt resource value: 0x7f02006d - public const int ic_group_collapse_14 = 2130837613; + // aapt resource value: 0x7F070079 + public const int ic_group_expand_04 = 2131165305; - // aapt resource value: 0x7f02006e - public const int ic_group_collapse_15 = 2130837614; + // aapt resource value: 0x7F07007A + public const int ic_group_expand_05 = 2131165306; - // aapt resource value: 0x7f02006f - public const int ic_group_expand_00 = 2130837615; + // aapt resource value: 0x7F07007B + public const int ic_group_expand_06 = 2131165307; - // aapt resource value: 0x7f020070 - public const int ic_group_expand_01 = 2130837616; + // aapt resource value: 0x7F07007C + public const int ic_group_expand_07 = 2131165308; - // aapt resource value: 0x7f020071 - public const int ic_group_expand_02 = 2130837617; + // aapt resource value: 0x7F07007D + public const int ic_group_expand_08 = 2131165309; - // aapt resource value: 0x7f020072 - public const int ic_group_expand_03 = 2130837618; + // aapt resource value: 0x7F07007E + public const int ic_group_expand_09 = 2131165310; - // aapt resource value: 0x7f020073 - public const int ic_group_expand_04 = 2130837619; + // aapt resource value: 0x7F07007F + public const int ic_group_expand_10 = 2131165311; - // aapt resource value: 0x7f020074 - public const int ic_group_expand_05 = 2130837620; + // aapt resource value: 0x7F070080 + public const int ic_group_expand_11 = 2131165312; - // aapt resource value: 0x7f020075 - public const int ic_group_expand_06 = 2130837621; + // aapt resource value: 0x7F070081 + public const int ic_group_expand_12 = 2131165313; - // aapt resource value: 0x7f020076 - public const int ic_group_expand_07 = 2130837622; + // aapt resource value: 0x7F070082 + public const int ic_group_expand_13 = 2131165314; - // aapt resource value: 0x7f020077 - public const int ic_group_expand_08 = 2130837623; + // aapt resource value: 0x7F070083 + public const int ic_group_expand_14 = 2131165315; - // aapt resource value: 0x7f020078 - public const int ic_group_expand_09 = 2130837624; + // aapt resource value: 0x7F070084 + public const int ic_group_expand_15 = 2131165316; - // aapt resource value: 0x7f020079 - public const int ic_group_expand_10 = 2130837625; + // aapt resource value: 0x7F070085 + public const int ic_media_pause_dark = 2131165317; - // aapt resource value: 0x7f02007a - public const int ic_group_expand_11 = 2130837626; + // aapt resource value: 0x7F070086 + public const int ic_media_pause_light = 2131165318; - // aapt resource value: 0x7f02007b - public const int ic_group_expand_12 = 2130837627; + // aapt resource value: 0x7F070087 + public const int ic_media_play_dark = 2131165319; - // aapt resource value: 0x7f02007c - public const int ic_group_expand_13 = 2130837628; + // aapt resource value: 0x7F070088 + public const int ic_media_play_light = 2131165320; - // aapt resource value: 0x7f02007d - public const int ic_group_expand_14 = 2130837629; + // aapt resource value: 0x7F070089 + public const int ic_media_stop_dark = 2131165321; - // aapt resource value: 0x7f02007e - public const int ic_group_expand_15 = 2130837630; + // aapt resource value: 0x7F07008A + public const int ic_media_stop_light = 2131165322; - // aapt resource value: 0x7f02007f - public const int ic_media_pause_dark = 2130837631; + // aapt resource value: 0x7F07008B + public const int ic_mr_button_connected_00_dark = 2131165323; - // aapt resource value: 0x7f020080 - public const int ic_media_pause_light = 2130837632; + // aapt resource value: 0x7F07008C + public const int ic_mr_button_connected_00_light = 2131165324; - // aapt resource value: 0x7f020081 - public const int ic_media_play_dark = 2130837633; + // aapt resource value: 0x7F07008D + public const int ic_mr_button_connected_01_dark = 2131165325; - // aapt resource value: 0x7f020082 - public const int ic_media_play_light = 2130837634; + // aapt resource value: 0x7F07008E + public const int ic_mr_button_connected_01_light = 2131165326; - // aapt resource value: 0x7f020083 - public const int ic_media_stop_dark = 2130837635; + // aapt resource value: 0x7F07008F + public const int ic_mr_button_connected_02_dark = 2131165327; - // aapt resource value: 0x7f020084 - public const int ic_media_stop_light = 2130837636; + // aapt resource value: 0x7F070090 + public const int ic_mr_button_connected_02_light = 2131165328; - // aapt resource value: 0x7f020085 - public const int ic_mr_button_connected_00_dark = 2130837637; + // aapt resource value: 0x7F070091 + public const int ic_mr_button_connected_03_dark = 2131165329; - // aapt resource value: 0x7f020086 - public const int ic_mr_button_connected_00_light = 2130837638; + // aapt resource value: 0x7F070092 + public const int ic_mr_button_connected_03_light = 2131165330; - // aapt resource value: 0x7f020087 - public const int ic_mr_button_connected_01_dark = 2130837639; + // aapt resource value: 0x7F070093 + public const int ic_mr_button_connected_04_dark = 2131165331; - // aapt resource value: 0x7f020088 - public const int ic_mr_button_connected_01_light = 2130837640; + // aapt resource value: 0x7F070094 + public const int ic_mr_button_connected_04_light = 2131165332; - // aapt resource value: 0x7f020089 - public const int ic_mr_button_connected_02_dark = 2130837641; + // aapt resource value: 0x7F070095 + public const int ic_mr_button_connected_05_dark = 2131165333; - // aapt resource value: 0x7f02008a - public const int ic_mr_button_connected_02_light = 2130837642; + // aapt resource value: 0x7F070096 + public const int ic_mr_button_connected_05_light = 2131165334; - // aapt resource value: 0x7f02008b - public const int ic_mr_button_connected_03_dark = 2130837643; + // aapt resource value: 0x7F070097 + public const int ic_mr_button_connected_06_dark = 2131165335; - // aapt resource value: 0x7f02008c - public const int ic_mr_button_connected_03_light = 2130837644; + // aapt resource value: 0x7F070098 + public const int ic_mr_button_connected_06_light = 2131165336; - // aapt resource value: 0x7f02008d - public const int ic_mr_button_connected_04_dark = 2130837645; + // aapt resource value: 0x7F070099 + public const int ic_mr_button_connected_07_dark = 2131165337; - // aapt resource value: 0x7f02008e - public const int ic_mr_button_connected_04_light = 2130837646; + // aapt resource value: 0x7F07009A + public const int ic_mr_button_connected_07_light = 2131165338; - // aapt resource value: 0x7f02008f - public const int ic_mr_button_connected_05_dark = 2130837647; + // aapt resource value: 0x7F07009B + public const int ic_mr_button_connected_08_dark = 2131165339; - // aapt resource value: 0x7f020090 - public const int ic_mr_button_connected_05_light = 2130837648; + // aapt resource value: 0x7F07009C + public const int ic_mr_button_connected_08_light = 2131165340; - // aapt resource value: 0x7f020091 - public const int ic_mr_button_connected_06_dark = 2130837649; + // aapt resource value: 0x7F07009D + public const int ic_mr_button_connected_09_dark = 2131165341; - // aapt resource value: 0x7f020092 - public const int ic_mr_button_connected_06_light = 2130837650; + // aapt resource value: 0x7F07009E + public const int ic_mr_button_connected_09_light = 2131165342; - // aapt resource value: 0x7f020093 - public const int ic_mr_button_connected_07_dark = 2130837651; + // aapt resource value: 0x7F07009F + public const int ic_mr_button_connected_10_dark = 2131165343; - // aapt resource value: 0x7f020094 - public const int ic_mr_button_connected_07_light = 2130837652; + // aapt resource value: 0x7F0700A0 + public const int ic_mr_button_connected_10_light = 2131165344; - // aapt resource value: 0x7f020095 - public const int ic_mr_button_connected_08_dark = 2130837653; + // aapt resource value: 0x7F0700A1 + public const int ic_mr_button_connected_11_dark = 2131165345; - // aapt resource value: 0x7f020096 - public const int ic_mr_button_connected_08_light = 2130837654; + // aapt resource value: 0x7F0700A2 + public const int ic_mr_button_connected_11_light = 2131165346; - // aapt resource value: 0x7f020097 - public const int ic_mr_button_connected_09_dark = 2130837655; + // aapt resource value: 0x7F0700A3 + public const int ic_mr_button_connected_12_dark = 2131165347; - // aapt resource value: 0x7f020098 - public const int ic_mr_button_connected_09_light = 2130837656; + // aapt resource value: 0x7F0700A4 + public const int ic_mr_button_connected_12_light = 2131165348; - // aapt resource value: 0x7f020099 - public const int ic_mr_button_connected_10_dark = 2130837657; + // aapt resource value: 0x7F0700A5 + public const int ic_mr_button_connected_13_dark = 2131165349; - // aapt resource value: 0x7f02009a - public const int ic_mr_button_connected_10_light = 2130837658; + // aapt resource value: 0x7F0700A6 + public const int ic_mr_button_connected_13_light = 2131165350; - // aapt resource value: 0x7f02009b - public const int ic_mr_button_connected_11_dark = 2130837659; + // aapt resource value: 0x7F0700A7 + public const int ic_mr_button_connected_14_dark = 2131165351; - // aapt resource value: 0x7f02009c - public const int ic_mr_button_connected_11_light = 2130837660; + // aapt resource value: 0x7F0700A8 + public const int ic_mr_button_connected_14_light = 2131165352; - // aapt resource value: 0x7f02009d - public const int ic_mr_button_connected_12_dark = 2130837661; + // aapt resource value: 0x7F0700A9 + public const int ic_mr_button_connected_15_dark = 2131165353; - // aapt resource value: 0x7f02009e - public const int ic_mr_button_connected_12_light = 2130837662; + // aapt resource value: 0x7F0700AA + public const int ic_mr_button_connected_15_light = 2131165354; - // aapt resource value: 0x7f02009f - public const int ic_mr_button_connected_13_dark = 2130837663; + // aapt resource value: 0x7F0700AB + public const int ic_mr_button_connected_16_dark = 2131165355; - // aapt resource value: 0x7f0200a0 - public const int ic_mr_button_connected_13_light = 2130837664; + // aapt resource value: 0x7F0700AC + public const int ic_mr_button_connected_16_light = 2131165356; - // aapt resource value: 0x7f0200a1 - public const int ic_mr_button_connected_14_dark = 2130837665; + // aapt resource value: 0x7F0700AD + public const int ic_mr_button_connected_17_dark = 2131165357; - // aapt resource value: 0x7f0200a2 - public const int ic_mr_button_connected_14_light = 2130837666; + // aapt resource value: 0x7F0700AE + public const int ic_mr_button_connected_17_light = 2131165358; - // aapt resource value: 0x7f0200a3 - public const int ic_mr_button_connected_15_dark = 2130837667; + // aapt resource value: 0x7F0700AF + public const int ic_mr_button_connected_18_dark = 2131165359; - // aapt resource value: 0x7f0200a4 - public const int ic_mr_button_connected_15_light = 2130837668; + // aapt resource value: 0x7F0700B0 + public const int ic_mr_button_connected_18_light = 2131165360; - // aapt resource value: 0x7f0200a5 - public const int ic_mr_button_connected_16_dark = 2130837669; + // aapt resource value: 0x7F0700B1 + public const int ic_mr_button_connected_19_dark = 2131165361; - // aapt resource value: 0x7f0200a6 - public const int ic_mr_button_connected_16_light = 2130837670; + // aapt resource value: 0x7F0700B2 + public const int ic_mr_button_connected_19_light = 2131165362; - // aapt resource value: 0x7f0200a7 - public const int ic_mr_button_connected_17_dark = 2130837671; + // aapt resource value: 0x7F0700B3 + public const int ic_mr_button_connected_20_dark = 2131165363; - // aapt resource value: 0x7f0200a8 - public const int ic_mr_button_connected_17_light = 2130837672; + // aapt resource value: 0x7F0700B4 + public const int ic_mr_button_connected_20_light = 2131165364; - // aapt resource value: 0x7f0200a9 - public const int ic_mr_button_connected_18_dark = 2130837673; + // aapt resource value: 0x7F0700B5 + public const int ic_mr_button_connected_21_dark = 2131165365; - // aapt resource value: 0x7f0200aa - public const int ic_mr_button_connected_18_light = 2130837674; + // aapt resource value: 0x7F0700B6 + public const int ic_mr_button_connected_21_light = 2131165366; - // aapt resource value: 0x7f0200ab - public const int ic_mr_button_connected_19_dark = 2130837675; + // aapt resource value: 0x7F0700B7 + public const int ic_mr_button_connected_22_dark = 2131165367; - // aapt resource value: 0x7f0200ac - public const int ic_mr_button_connected_19_light = 2130837676; + // aapt resource value: 0x7F0700B8 + public const int ic_mr_button_connected_22_light = 2131165368; - // aapt resource value: 0x7f0200ad - public const int ic_mr_button_connected_20_dark = 2130837677; + // aapt resource value: 0x7F0700B9 + public const int ic_mr_button_connected_23_dark = 2131165369; - // aapt resource value: 0x7f0200ae - public const int ic_mr_button_connected_20_light = 2130837678; + // aapt resource value: 0x7F0700BA + public const int ic_mr_button_connected_23_light = 2131165370; - // aapt resource value: 0x7f0200af - public const int ic_mr_button_connected_21_dark = 2130837679; + // aapt resource value: 0x7F0700BB + public const int ic_mr_button_connected_24_dark = 2131165371; - // aapt resource value: 0x7f0200b0 - public const int ic_mr_button_connected_21_light = 2130837680; + // aapt resource value: 0x7F0700BC + public const int ic_mr_button_connected_24_light = 2131165372; - // aapt resource value: 0x7f0200b1 - public const int ic_mr_button_connected_22_dark = 2130837681; + // aapt resource value: 0x7F0700BD + public const int ic_mr_button_connected_25_dark = 2131165373; - // aapt resource value: 0x7f0200b2 - public const int ic_mr_button_connected_22_light = 2130837682; + // aapt resource value: 0x7F0700BE + public const int ic_mr_button_connected_25_light = 2131165374; - // aapt resource value: 0x7f0200b3 - public const int ic_mr_button_connected_23_dark = 2130837683; + // aapt resource value: 0x7F0700BF + public const int ic_mr_button_connected_26_dark = 2131165375; - // aapt resource value: 0x7f0200b4 - public const int ic_mr_button_connected_23_light = 2130837684; + // aapt resource value: 0x7F0700C0 + public const int ic_mr_button_connected_26_light = 2131165376; - // aapt resource value: 0x7f0200b5 - public const int ic_mr_button_connected_24_dark = 2130837685; + // aapt resource value: 0x7F0700C1 + public const int ic_mr_button_connected_27_dark = 2131165377; - // aapt resource value: 0x7f0200b6 - public const int ic_mr_button_connected_24_light = 2130837686; + // aapt resource value: 0x7F0700C2 + public const int ic_mr_button_connected_27_light = 2131165378; - // aapt resource value: 0x7f0200b7 - public const int ic_mr_button_connected_25_dark = 2130837687; + // aapt resource value: 0x7F0700C3 + public const int ic_mr_button_connected_28_dark = 2131165379; - // aapt resource value: 0x7f0200b8 - public const int ic_mr_button_connected_25_light = 2130837688; + // aapt resource value: 0x7F0700C4 + public const int ic_mr_button_connected_28_light = 2131165380; - // aapt resource value: 0x7f0200b9 - public const int ic_mr_button_connected_26_dark = 2130837689; + // aapt resource value: 0x7F0700C5 + public const int ic_mr_button_connected_29_dark = 2131165381; - // aapt resource value: 0x7f0200ba - public const int ic_mr_button_connected_26_light = 2130837690; + // aapt resource value: 0x7F0700C6 + public const int ic_mr_button_connected_29_light = 2131165382; - // aapt resource value: 0x7f0200bb - public const int ic_mr_button_connected_27_dark = 2130837691; + // aapt resource value: 0x7F0700C7 + public const int ic_mr_button_connected_30_dark = 2131165383; - // aapt resource value: 0x7f0200bc - public const int ic_mr_button_connected_27_light = 2130837692; + // aapt resource value: 0x7F0700C8 + public const int ic_mr_button_connected_30_light = 2131165384; - // aapt resource value: 0x7f0200bd - public const int ic_mr_button_connected_28_dark = 2130837693; + // aapt resource value: 0x7F0700C9 + public const int ic_mr_button_connecting_00_dark = 2131165385; - // aapt resource value: 0x7f0200be - public const int ic_mr_button_connected_28_light = 2130837694; + // aapt resource value: 0x7F0700CA + public const int ic_mr_button_connecting_00_light = 2131165386; - // aapt resource value: 0x7f0200bf - public const int ic_mr_button_connected_29_dark = 2130837695; + // aapt resource value: 0x7F0700CB + public const int ic_mr_button_connecting_01_dark = 2131165387; - // aapt resource value: 0x7f0200c0 - public const int ic_mr_button_connected_29_light = 2130837696; + // aapt resource value: 0x7F0700CC + public const int ic_mr_button_connecting_01_light = 2131165388; - // aapt resource value: 0x7f0200c1 - public const int ic_mr_button_connected_30_dark = 2130837697; + // aapt resource value: 0x7F0700CD + public const int ic_mr_button_connecting_02_dark = 2131165389; - // aapt resource value: 0x7f0200c2 - public const int ic_mr_button_connected_30_light = 2130837698; + // aapt resource value: 0x7F0700CE + public const int ic_mr_button_connecting_02_light = 2131165390; - // aapt resource value: 0x7f0200c3 - public const int ic_mr_button_connecting_00_dark = 2130837699; + // aapt resource value: 0x7F0700CF + public const int ic_mr_button_connecting_03_dark = 2131165391; - // aapt resource value: 0x7f0200c4 - public const int ic_mr_button_connecting_00_light = 2130837700; + // aapt resource value: 0x7F0700D0 + public const int ic_mr_button_connecting_03_light = 2131165392; - // aapt resource value: 0x7f0200c5 - public const int ic_mr_button_connecting_01_dark = 2130837701; + // aapt resource value: 0x7F0700D1 + public const int ic_mr_button_connecting_04_dark = 2131165393; - // aapt resource value: 0x7f0200c6 - public const int ic_mr_button_connecting_01_light = 2130837702; + // aapt resource value: 0x7F0700D2 + public const int ic_mr_button_connecting_04_light = 2131165394; - // aapt resource value: 0x7f0200c7 - public const int ic_mr_button_connecting_02_dark = 2130837703; + // aapt resource value: 0x7F0700D3 + public const int ic_mr_button_connecting_05_dark = 2131165395; - // aapt resource value: 0x7f0200c8 - public const int ic_mr_button_connecting_02_light = 2130837704; + // aapt resource value: 0x7F0700D4 + public const int ic_mr_button_connecting_05_light = 2131165396; - // aapt resource value: 0x7f0200c9 - public const int ic_mr_button_connecting_03_dark = 2130837705; + // aapt resource value: 0x7F0700D5 + public const int ic_mr_button_connecting_06_dark = 2131165397; - // aapt resource value: 0x7f0200ca - public const int ic_mr_button_connecting_03_light = 2130837706; + // aapt resource value: 0x7F0700D6 + public const int ic_mr_button_connecting_06_light = 2131165398; - // aapt resource value: 0x7f0200cb - public const int ic_mr_button_connecting_04_dark = 2130837707; + // aapt resource value: 0x7F0700D7 + public const int ic_mr_button_connecting_07_dark = 2131165399; - // aapt resource value: 0x7f0200cc - public const int ic_mr_button_connecting_04_light = 2130837708; + // aapt resource value: 0x7F0700D8 + public const int ic_mr_button_connecting_07_light = 2131165400; - // aapt resource value: 0x7f0200cd - public const int ic_mr_button_connecting_05_dark = 2130837709; + // aapt resource value: 0x7F0700D9 + public const int ic_mr_button_connecting_08_dark = 2131165401; - // aapt resource value: 0x7f0200ce - public const int ic_mr_button_connecting_05_light = 2130837710; + // aapt resource value: 0x7F0700DA + public const int ic_mr_button_connecting_08_light = 2131165402; - // aapt resource value: 0x7f0200cf - public const int ic_mr_button_connecting_06_dark = 2130837711; + // aapt resource value: 0x7F0700DB + public const int ic_mr_button_connecting_09_dark = 2131165403; - // aapt resource value: 0x7f0200d0 - public const int ic_mr_button_connecting_06_light = 2130837712; + // aapt resource value: 0x7F0700DC + public const int ic_mr_button_connecting_09_light = 2131165404; - // aapt resource value: 0x7f0200d1 - public const int ic_mr_button_connecting_07_dark = 2130837713; + // aapt resource value: 0x7F0700DD + public const int ic_mr_button_connecting_10_dark = 2131165405; - // aapt resource value: 0x7f0200d2 - public const int ic_mr_button_connecting_07_light = 2130837714; + // aapt resource value: 0x7F0700DE + public const int ic_mr_button_connecting_10_light = 2131165406; - // aapt resource value: 0x7f0200d3 - public const int ic_mr_button_connecting_08_dark = 2130837715; + // aapt resource value: 0x7F0700DF + public const int ic_mr_button_connecting_11_dark = 2131165407; - // aapt resource value: 0x7f0200d4 - public const int ic_mr_button_connecting_08_light = 2130837716; + // aapt resource value: 0x7F0700E0 + public const int ic_mr_button_connecting_11_light = 2131165408; - // aapt resource value: 0x7f0200d5 - public const int ic_mr_button_connecting_09_dark = 2130837717; + // aapt resource value: 0x7F0700E1 + public const int ic_mr_button_connecting_12_dark = 2131165409; - // aapt resource value: 0x7f0200d6 - public const int ic_mr_button_connecting_09_light = 2130837718; + // aapt resource value: 0x7F0700E2 + public const int ic_mr_button_connecting_12_light = 2131165410; - // aapt resource value: 0x7f0200d7 - public const int ic_mr_button_connecting_10_dark = 2130837719; + // aapt resource value: 0x7F0700E3 + public const int ic_mr_button_connecting_13_dark = 2131165411; - // aapt resource value: 0x7f0200d8 - public const int ic_mr_button_connecting_10_light = 2130837720; + // aapt resource value: 0x7F0700E4 + public const int ic_mr_button_connecting_13_light = 2131165412; - // aapt resource value: 0x7f0200d9 - public const int ic_mr_button_connecting_11_dark = 2130837721; + // aapt resource value: 0x7F0700E5 + public const int ic_mr_button_connecting_14_dark = 2131165413; - // aapt resource value: 0x7f0200da - public const int ic_mr_button_connecting_11_light = 2130837722; + // aapt resource value: 0x7F0700E6 + public const int ic_mr_button_connecting_14_light = 2131165414; - // aapt resource value: 0x7f0200db - public const int ic_mr_button_connecting_12_dark = 2130837723; + // aapt resource value: 0x7F0700E7 + public const int ic_mr_button_connecting_15_dark = 2131165415; - // aapt resource value: 0x7f0200dc - public const int ic_mr_button_connecting_12_light = 2130837724; + // aapt resource value: 0x7F0700E8 + public const int ic_mr_button_connecting_15_light = 2131165416; - // aapt resource value: 0x7f0200dd - public const int ic_mr_button_connecting_13_dark = 2130837725; + // aapt resource value: 0x7F0700E9 + public const int ic_mr_button_connecting_16_dark = 2131165417; - // aapt resource value: 0x7f0200de - public const int ic_mr_button_connecting_13_light = 2130837726; + // aapt resource value: 0x7F0700EA + public const int ic_mr_button_connecting_16_light = 2131165418; - // aapt resource value: 0x7f0200df - public const int ic_mr_button_connecting_14_dark = 2130837727; + // aapt resource value: 0x7F0700EB + public const int ic_mr_button_connecting_17_dark = 2131165419; - // aapt resource value: 0x7f0200e0 - public const int ic_mr_button_connecting_14_light = 2130837728; + // aapt resource value: 0x7F0700EC + public const int ic_mr_button_connecting_17_light = 2131165420; - // aapt resource value: 0x7f0200e1 - public const int ic_mr_button_connecting_15_dark = 2130837729; + // aapt resource value: 0x7F0700ED + public const int ic_mr_button_connecting_18_dark = 2131165421; - // aapt resource value: 0x7f0200e2 - public const int ic_mr_button_connecting_15_light = 2130837730; + // aapt resource value: 0x7F0700EE + public const int ic_mr_button_connecting_18_light = 2131165422; - // aapt resource value: 0x7f0200e3 - public const int ic_mr_button_connecting_16_dark = 2130837731; + // aapt resource value: 0x7F0700EF + public const int ic_mr_button_connecting_19_dark = 2131165423; - // aapt resource value: 0x7f0200e4 - public const int ic_mr_button_connecting_16_light = 2130837732; + // aapt resource value: 0x7F0700F0 + public const int ic_mr_button_connecting_19_light = 2131165424; - // aapt resource value: 0x7f0200e5 - public const int ic_mr_button_connecting_17_dark = 2130837733; + // aapt resource value: 0x7F0700F1 + public const int ic_mr_button_connecting_20_dark = 2131165425; - // aapt resource value: 0x7f0200e6 - public const int ic_mr_button_connecting_17_light = 2130837734; + // aapt resource value: 0x7F0700F2 + public const int ic_mr_button_connecting_20_light = 2131165426; - // aapt resource value: 0x7f0200e7 - public const int ic_mr_button_connecting_18_dark = 2130837735; + // aapt resource value: 0x7F0700F3 + public const int ic_mr_button_connecting_21_dark = 2131165427; - // aapt resource value: 0x7f0200e8 - public const int ic_mr_button_connecting_18_light = 2130837736; + // aapt resource value: 0x7F0700F4 + public const int ic_mr_button_connecting_21_light = 2131165428; - // aapt resource value: 0x7f0200e9 - public const int ic_mr_button_connecting_19_dark = 2130837737; + // aapt resource value: 0x7F0700F5 + public const int ic_mr_button_connecting_22_dark = 2131165429; - // aapt resource value: 0x7f0200ea - public const int ic_mr_button_connecting_19_light = 2130837738; + // aapt resource value: 0x7F0700F6 + public const int ic_mr_button_connecting_22_light = 2131165430; - // aapt resource value: 0x7f0200eb - public const int ic_mr_button_connecting_20_dark = 2130837739; + // aapt resource value: 0x7F0700F7 + public const int ic_mr_button_connecting_23_dark = 2131165431; - // aapt resource value: 0x7f0200ec - public const int ic_mr_button_connecting_20_light = 2130837740; + // aapt resource value: 0x7F0700F8 + public const int ic_mr_button_connecting_23_light = 2131165432; - // aapt resource value: 0x7f0200ed - public const int ic_mr_button_connecting_21_dark = 2130837741; + // aapt resource value: 0x7F0700F9 + public const int ic_mr_button_connecting_24_dark = 2131165433; - // aapt resource value: 0x7f0200ee - public const int ic_mr_button_connecting_21_light = 2130837742; + // aapt resource value: 0x7F0700FA + public const int ic_mr_button_connecting_24_light = 2131165434; - // aapt resource value: 0x7f0200ef - public const int ic_mr_button_connecting_22_dark = 2130837743; + // aapt resource value: 0x7F0700FB + public const int ic_mr_button_connecting_25_dark = 2131165435; - // aapt resource value: 0x7f0200f0 - public const int ic_mr_button_connecting_22_light = 2130837744; + // aapt resource value: 0x7F0700FC + public const int ic_mr_button_connecting_25_light = 2131165436; - // aapt resource value: 0x7f0200f1 - public const int ic_mr_button_connecting_23_dark = 2130837745; + // aapt resource value: 0x7F0700FD + public const int ic_mr_button_connecting_26_dark = 2131165437; - // aapt resource value: 0x7f0200f2 - public const int ic_mr_button_connecting_23_light = 2130837746; + // aapt resource value: 0x7F0700FE + public const int ic_mr_button_connecting_26_light = 2131165438; - // aapt resource value: 0x7f0200f3 - public const int ic_mr_button_connecting_24_dark = 2130837747; + // aapt resource value: 0x7F0700FF + public const int ic_mr_button_connecting_27_dark = 2131165439; - // aapt resource value: 0x7f0200f4 - public const int ic_mr_button_connecting_24_light = 2130837748; + // aapt resource value: 0x7F070100 + public const int ic_mr_button_connecting_27_light = 2131165440; - // aapt resource value: 0x7f0200f5 - public const int ic_mr_button_connecting_25_dark = 2130837749; + // aapt resource value: 0x7F070101 + public const int ic_mr_button_connecting_28_dark = 2131165441; - // aapt resource value: 0x7f0200f6 - public const int ic_mr_button_connecting_25_light = 2130837750; + // aapt resource value: 0x7F070102 + public const int ic_mr_button_connecting_28_light = 2131165442; - // aapt resource value: 0x7f0200f7 - public const int ic_mr_button_connecting_26_dark = 2130837751; + // aapt resource value: 0x7F070103 + public const int ic_mr_button_connecting_29_dark = 2131165443; - // aapt resource value: 0x7f0200f8 - public const int ic_mr_button_connecting_26_light = 2130837752; + // aapt resource value: 0x7F070104 + public const int ic_mr_button_connecting_29_light = 2131165444; - // aapt resource value: 0x7f0200f9 - public const int ic_mr_button_connecting_27_dark = 2130837753; + // aapt resource value: 0x7F070105 + public const int ic_mr_button_connecting_30_dark = 2131165445; - // aapt resource value: 0x7f0200fa - public const int ic_mr_button_connecting_27_light = 2130837754; + // aapt resource value: 0x7F070106 + public const int ic_mr_button_connecting_30_light = 2131165446; - // aapt resource value: 0x7f0200fb - public const int ic_mr_button_connecting_28_dark = 2130837755; + // aapt resource value: 0x7F070107 + public const int ic_mr_button_disabled_dark = 2131165447; - // aapt resource value: 0x7f0200fc - public const int ic_mr_button_connecting_28_light = 2130837756; + // aapt resource value: 0x7F070108 + public const int ic_mr_button_disabled_light = 2131165448; - // aapt resource value: 0x7f0200fd - public const int ic_mr_button_connecting_29_dark = 2130837757; + // aapt resource value: 0x7F070109 + public const int ic_mr_button_disconnected_dark = 2131165449; - // aapt resource value: 0x7f0200fe - public const int ic_mr_button_connecting_29_light = 2130837758; + // aapt resource value: 0x7F07010A + public const int ic_mr_button_disconnected_light = 2131165450; - // aapt resource value: 0x7f0200ff - public const int ic_mr_button_connecting_30_dark = 2130837759; + // aapt resource value: 0x7F07010B + public const int ic_mr_button_grey = 2131165451; - // aapt resource value: 0x7f020100 - public const int ic_mr_button_connecting_30_light = 2130837760; + // aapt resource value: 0x7F07010C + public const int ic_vol_type_speaker_dark = 2131165452; - // aapt resource value: 0x7f020101 - public const int ic_mr_button_disabled_dark = 2130837761; + // aapt resource value: 0x7F07010D + public const int ic_vol_type_speaker_group_dark = 2131165453; - // aapt resource value: 0x7f020102 - public const int ic_mr_button_disabled_light = 2130837762; + // aapt resource value: 0x7F07010E + public const int ic_vol_type_speaker_group_light = 2131165454; - // aapt resource value: 0x7f020103 - public const int ic_mr_button_disconnected_dark = 2130837763; + // aapt resource value: 0x7F07010F + public const int ic_vol_type_speaker_light = 2131165455; - // aapt resource value: 0x7f020104 - public const int ic_mr_button_disconnected_light = 2130837764; + // aapt resource value: 0x7F070110 + public const int ic_vol_type_tv_dark = 2131165456; - // aapt resource value: 0x7f020105 - public const int ic_mr_button_grey = 2130837765; + // aapt resource value: 0x7F070111 + public const int ic_vol_type_tv_light = 2131165457; - // aapt resource value: 0x7f020106 - public const int ic_vol_type_speaker_dark = 2130837766; + // aapt resource value: 0x7F070112 + public const int mr_button_connected_dark = 2131165458; - // aapt resource value: 0x7f020107 - public const int ic_vol_type_speaker_group_dark = 2130837767; + // aapt resource value: 0x7F070113 + public const int mr_button_connected_light = 2131165459; - // aapt resource value: 0x7f020108 - public const int ic_vol_type_speaker_group_light = 2130837768; + // aapt resource value: 0x7F070114 + public const int mr_button_connecting_dark = 2131165460; - // aapt resource value: 0x7f020109 - public const int ic_vol_type_speaker_light = 2130837769; + // aapt resource value: 0x7F070115 + public const int mr_button_connecting_light = 2131165461; - // aapt resource value: 0x7f02010a - public const int ic_vol_type_tv_dark = 2130837770; + // aapt resource value: 0x7F070116 + public const int mr_button_dark = 2131165462; - // aapt resource value: 0x7f02010b - public const int ic_vol_type_tv_light = 2130837771; + // aapt resource value: 0x7F070117 + public const int mr_button_light = 2131165463; - // aapt resource value: 0x7f02010c - public const int mr_button_connected_dark = 2130837772; + // aapt resource value: 0x7F070118 + public const int mr_dialog_close_dark = 2131165464; - // aapt resource value: 0x7f02010d - public const int mr_button_connected_light = 2130837773; + // aapt resource value: 0x7F070119 + public const int mr_dialog_close_light = 2131165465; - // aapt resource value: 0x7f02010e - public const int mr_button_connecting_dark = 2130837774; + // aapt resource value: 0x7F07011A + public const int mr_dialog_material_background_dark = 2131165466; - // aapt resource value: 0x7f02010f - public const int mr_button_connecting_light = 2130837775; + // aapt resource value: 0x7F07011B + public const int mr_dialog_material_background_light = 2131165467; - // aapt resource value: 0x7f020110 - public const int mr_button_dark = 2130837776; + // aapt resource value: 0x7F07011C + public const int mr_group_collapse = 2131165468; - // aapt resource value: 0x7f020111 - public const int mr_button_light = 2130837777; + // aapt resource value: 0x7F07011D + public const int mr_group_expand = 2131165469; - // aapt resource value: 0x7f020112 - public const int mr_dialog_close_dark = 2130837778; + // aapt resource value: 0x7F07011E + public const int mr_media_pause_dark = 2131165470; - // aapt resource value: 0x7f020113 - public const int mr_dialog_close_light = 2130837779; + // aapt resource value: 0x7F07011F + public const int mr_media_pause_light = 2131165471; - // aapt resource value: 0x7f020114 - public const int mr_dialog_material_background_dark = 2130837780; + // aapt resource value: 0x7F070120 + public const int mr_media_play_dark = 2131165472; - // aapt resource value: 0x7f020115 - public const int mr_dialog_material_background_light = 2130837781; + // aapt resource value: 0x7F070121 + public const int mr_media_play_light = 2131165473; - // aapt resource value: 0x7f020116 - public const int mr_group_collapse = 2130837782; + // aapt resource value: 0x7F070122 + public const int mr_media_stop_dark = 2131165474; - // aapt resource value: 0x7f020117 - public const int mr_group_expand = 2130837783; + // aapt resource value: 0x7F070123 + public const int mr_media_stop_light = 2131165475; - // aapt resource value: 0x7f020118 - public const int mr_media_pause_dark = 2130837784; + // aapt resource value: 0x7F070124 + public const int mr_vol_type_audiotrack_dark = 2131165476; - // aapt resource value: 0x7f020119 - public const int mr_media_pause_light = 2130837785; + // aapt resource value: 0x7F070125 + public const int mr_vol_type_audiotrack_light = 2131165477; - // aapt resource value: 0x7f02011a - public const int mr_media_play_dark = 2130837786; + // aapt resource value: 0x7F070126 + public const int navigation_empty_icon = 2131165478; - // aapt resource value: 0x7f02011b - public const int mr_media_play_light = 2130837787; + // aapt resource value: 0x7F070127 + public const int notification_action_background = 2131165479; - // aapt resource value: 0x7f02011c - public const int mr_media_stop_dark = 2130837788; + // aapt resource value: 0x7F070128 + public const int notification_bg = 2131165480; - // aapt resource value: 0x7f02011d - public const int mr_media_stop_light = 2130837789; + // aapt resource value: 0x7F070129 + public const int notification_bg_low = 2131165481; - // aapt resource value: 0x7f02011e - public const int mr_vol_type_audiotrack_dark = 2130837790; + // aapt resource value: 0x7F07012A + public const int notification_bg_low_normal = 2131165482; - // aapt resource value: 0x7f02011f - public const int mr_vol_type_audiotrack_light = 2130837791; + // aapt resource value: 0x7F07012B + public const int notification_bg_low_pressed = 2131165483; - // aapt resource value: 0x7f020120 - public const int navigation_empty_icon = 2130837792; + // aapt resource value: 0x7F07012C + public const int notification_bg_normal = 2131165484; - // aapt resource value: 0x7f020121 - public const int notification_action_background = 2130837793; + // aapt resource value: 0x7F07012D + public const int notification_bg_normal_pressed = 2131165485; - // aapt resource value: 0x7f020122 - public const int notification_bg = 2130837794; + // aapt resource value: 0x7F07012E + public const int notification_icon_background = 2131165486; - // aapt resource value: 0x7f020123 - public const int notification_bg_low = 2130837795; + // aapt resource value: 0x7F07012F + public const int notification_template_icon_bg = 2131165487; - // aapt resource value: 0x7f020124 - public const int notification_bg_low_normal = 2130837796; + // aapt resource value: 0x7F070130 + public const int notification_template_icon_low_bg = 2131165488; - // aapt resource value: 0x7f020125 - public const int notification_bg_low_pressed = 2130837797; + // aapt resource value: 0x7F070131 + public const int notification_tile_bg = 2131165489; - // aapt resource value: 0x7f020126 - public const int notification_bg_normal = 2130837798; + // aapt resource value: 0x7F070132 + public const int notify_panel_notification_icon_bg = 2131165490; - // aapt resource value: 0x7f020127 - public const int notification_bg_normal_pressed = 2130837799; + // aapt resource value: 0x7F070133 + public const int tooltip_frame_dark = 2131165491; - // aapt resource value: 0x7f020128 - public const int notification_icon_background = 2130837800; - - // aapt resource value: 0x7f02012d - public const int notification_template_icon_bg = 2130837805; - - // aapt resource value: 0x7f02012e - public const int notification_template_icon_low_bg = 2130837806; - - // aapt resource value: 0x7f020129 - public const int notification_tile_bg = 2130837801; - - // aapt resource value: 0x7f02012a - public const int notify_panel_notification_icon_bg = 2130837802; - - // aapt resource value: 0x7f02012b - public const int tooltip_frame_dark = 2130837803; - - // aapt resource value: 0x7f02012c - public const int tooltip_frame_light = 2130837804; + // aapt resource value: 0x7F070134 + public const int tooltip_frame_light = 2131165492; static Drawable() { @@ -5134,641 +5008,641 @@ private Drawable() public partial class Id { - // aapt resource value: 0x7f090032 - public const int ALT = 2131296306; + // aapt resource value: 0x7F080006 + public const int action0 = 2131230726; - // aapt resource value: 0x7f090033 - public const int CTRL = 2131296307; + // aapt resource value: 0x7F080018 + public const int actions = 2131230744; - // aapt resource value: 0x7f090034 - public const int FUNCTION = 2131296308; + // aapt resource value: 0x7F080007 + public const int action_bar = 2131230727; - // aapt resource value: 0x7f090035 - public const int META = 2131296309; + // aapt resource value: 0x7F080008 + public const int action_bar_activity_content = 2131230728; - // aapt resource value: 0x7f090036 - public const int SHIFT = 2131296310; + // aapt resource value: 0x7F080009 + public const int action_bar_container = 2131230729; - // aapt resource value: 0x7f090037 - public const int SYM = 2131296311; + // aapt resource value: 0x7F08000A + public const int action_bar_root = 2131230730; - // aapt resource value: 0x7f0900ba - public const int action0 = 2131296442; + // aapt resource value: 0x7F08000B + public const int action_bar_spinner = 2131230731; - // aapt resource value: 0x7f09007c - public const int action_bar = 2131296380; + // aapt resource value: 0x7F08000C + public const int action_bar_subtitle = 2131230732; - // aapt resource value: 0x7f090001 - public const int action_bar_activity_content = 2131296257; + // aapt resource value: 0x7F08000D + public const int action_bar_title = 2131230733; - // aapt resource value: 0x7f09007b - public const int action_bar_container = 2131296379; + // aapt resource value: 0x7F08000E + public const int action_container = 2131230734; - // aapt resource value: 0x7f090077 - public const int action_bar_root = 2131296375; + // aapt resource value: 0x7F08000F + public const int action_context_bar = 2131230735; - // aapt resource value: 0x7f090002 - public const int action_bar_spinner = 2131296258; + // aapt resource value: 0x7F080010 + public const int action_divider = 2131230736; - // aapt resource value: 0x7f09005b - public const int action_bar_subtitle = 2131296347; + // aapt resource value: 0x7F080011 + public const int action_image = 2131230737; - // aapt resource value: 0x7f09005a - public const int action_bar_title = 2131296346; + // aapt resource value: 0x7F080012 + public const int action_menu_divider = 2131230738; - // aapt resource value: 0x7f0900b7 - public const int action_container = 2131296439; + // aapt resource value: 0x7F080013 + public const int action_menu_presenter = 2131230739; - // aapt resource value: 0x7f09007d - public const int action_context_bar = 2131296381; + // aapt resource value: 0x7F080014 + public const int action_mode_bar = 2131230740; - // aapt resource value: 0x7f0900be - public const int action_divider = 2131296446; + // aapt resource value: 0x7F080015 + public const int action_mode_bar_stub = 2131230741; - // aapt resource value: 0x7f0900b8 - public const int action_image = 2131296440; + // aapt resource value: 0x7F080016 + public const int action_mode_close_button = 2131230742; - // aapt resource value: 0x7f090003 - public const int action_menu_divider = 2131296259; + // aapt resource value: 0x7F080017 + public const int action_text = 2131230743; - // aapt resource value: 0x7f090004 - public const int action_menu_presenter = 2131296260; + // aapt resource value: 0x7F080019 + public const int activity_chooser_view_content = 2131230745; - // aapt resource value: 0x7f090079 - public const int action_mode_bar = 2131296377; + // aapt resource value: 0x7F08001A + public const int add = 2131230746; - // aapt resource value: 0x7f090078 - public const int action_mode_bar_stub = 2131296376; + // aapt resource value: 0x7F08001B + public const int alertTitle = 2131230747; - // aapt resource value: 0x7f09005c - public const int action_mode_close_button = 2131296348; + // aapt resource value: 0x7F08001C + public const int all = 2131230748; - // aapt resource value: 0x7f0900b9 - public const int action_text = 2131296441; + // aapt resource value: 0x7F080000 + public const int ALT = 2131230720; - // aapt resource value: 0x7f0900c7 - public const int actions = 2131296455; + // aapt resource value: 0x7F08001D + public const int always = 2131230749; - // aapt resource value: 0x7f09005d - public const int activity_chooser_view_content = 2131296349; + // aapt resource value: 0x7F08001E + public const int async = 2131230750; - // aapt resource value: 0x7f090027 - public const int add = 2131296295; + // aapt resource value: 0x7F08001F + public const int auto = 2131230751; - // aapt resource value: 0x7f090070 - public const int alertTitle = 2131296368; + // aapt resource value: 0x7F080020 + public const int beginning = 2131230752; - // aapt resource value: 0x7f090052 - public const int all = 2131296338; + // aapt resource value: 0x7F080021 + public const int blocking = 2131230753; - // aapt resource value: 0x7f090038 - public const int always = 2131296312; + // aapt resource value: 0x7F080022 + public const int bottom = 2131230754; - // aapt resource value: 0x7f090056 - public const int async = 2131296342; + // aapt resource value: 0x7F080023 + public const int bottomtab_navarea = 2131230755; - // aapt resource value: 0x7f090044 - public const int auto = 2131296324; + // aapt resource value: 0x7F080024 + public const int bottomtab_tabbar = 2131230756; - // aapt resource value: 0x7f09002f - public const int beginning = 2131296303; + // aapt resource value: 0x7F080025 + public const int buttonPanel = 2131230757; - // aapt resource value: 0x7f090057 - public const int blocking = 2131296343; + // aapt resource value: 0x7F080026 + public const int cancel_action = 2131230758; - // aapt resource value: 0x7f09003d - public const int bottom = 2131296317; + // aapt resource value: 0x7F080027 + public const int center = 2131230759; - // aapt resource value: 0x7f09008b - public const int bottomtab_navarea = 2131296395; + // aapt resource value: 0x7F080028 + public const int center_horizontal = 2131230760; - // aapt resource value: 0x7f09008c - public const int bottomtab_tabbar = 2131296396; + // aapt resource value: 0x7F080029 + public const int center_vertical = 2131230761; - // aapt resource value: 0x7f090063 - public const int buttonPanel = 2131296355; + // aapt resource value: 0x7F08002A + public const int checkbox = 2131230762; - // aapt resource value: 0x7f0900bb - public const int cancel_action = 2131296443; + // aapt resource value: 0x7F08002B + public const int chronometer = 2131230763; - // aapt resource value: 0x7f090045 - public const int center = 2131296325; + // aapt resource value: 0x7F08002C + public const int clip_horizontal = 2131230764; - // aapt resource value: 0x7f090046 - public const int center_horizontal = 2131296326; + // aapt resource value: 0x7F08002D + public const int clip_vertical = 2131230765; - // aapt resource value: 0x7f090047 - public const int center_vertical = 2131296327; + // aapt resource value: 0x7F08002E + public const int collapseActionView = 2131230766; - // aapt resource value: 0x7f090073 - public const int checkbox = 2131296371; + // aapt resource value: 0x7F08002F + public const int container = 2131230767; - // aapt resource value: 0x7f0900c3 - public const int chronometer = 2131296451; + // aapt resource value: 0x7F080030 + public const int contentPanel = 2131230768; - // aapt resource value: 0x7f09004e - public const int clip_horizontal = 2131296334; + // aapt resource value: 0x7F080031 + public const int coordinator = 2131230769; - // aapt resource value: 0x7f09004f - public const int clip_vertical = 2131296335; + // aapt resource value: 0x7F080001 + public const int CTRL = 2131230721; - // aapt resource value: 0x7f090039 - public const int collapseActionView = 2131296313; + // aapt resource value: 0x7F080032 + public const int custom = 2131230770; - // aapt resource value: 0x7f09008f - public const int container = 2131296399; + // aapt resource value: 0x7F080033 + public const int customPanel = 2131230771; - // aapt resource value: 0x7f090066 - public const int contentPanel = 2131296358; + // aapt resource value: 0x7F080034 + public const int decor_content_parent = 2131230772; - // aapt resource value: 0x7f090090 - public const int coordinator = 2131296400; + // aapt resource value: 0x7F080035 + public const int default_activity_button = 2131230773; - // aapt resource value: 0x7f09006d - public const int custom = 2131296365; + // aapt resource value: 0x7F080036 + public const int design_bottom_sheet = 2131230774; - // aapt resource value: 0x7f09006c - public const int customPanel = 2131296364; + // aapt resource value: 0x7F080037 + public const int design_menu_item_action_area = 2131230775; - // aapt resource value: 0x7f09007a - public const int decor_content_parent = 2131296378; + // aapt resource value: 0x7F080038 + public const int design_menu_item_action_area_stub = 2131230776; - // aapt resource value: 0x7f090060 - public const int default_activity_button = 2131296352; + // aapt resource value: 0x7F080039 + public const int design_menu_item_text = 2131230777; - // aapt resource value: 0x7f090092 - public const int design_bottom_sheet = 2131296402; + // aapt resource value: 0x7F08003A + public const int design_navigation_view = 2131230778; - // aapt resource value: 0x7f090099 - public const int design_menu_item_action_area = 2131296409; + // aapt resource value: 0x7F08003B + public const int disableHome = 2131230779; - // aapt resource value: 0x7f090098 - public const int design_menu_item_action_area_stub = 2131296408; + // aapt resource value: 0x7F08003C + public const int edit_query = 2131230780; - // aapt resource value: 0x7f090097 - public const int design_menu_item_text = 2131296407; + // aapt resource value: 0x7F08003D + public const int end = 2131230781; - // aapt resource value: 0x7f090096 - public const int design_navigation_view = 2131296406; + // aapt resource value: 0x7F08003E + public const int end_padder = 2131230782; - // aapt resource value: 0x7f090020 - public const int disableHome = 2131296288; + // aapt resource value: 0x7F08003F + public const int enterAlways = 2131230783; - // aapt resource value: 0x7f09007e - public const int edit_query = 2131296382; + // aapt resource value: 0x7F080040 + public const int enterAlwaysCollapsed = 2131230784; - // aapt resource value: 0x7f090030 - public const int end = 2131296304; + // aapt resource value: 0x7F080041 + public const int exitUntilCollapsed = 2131230785; - // aapt resource value: 0x7f0900c9 - public const int end_padder = 2131296457; + // aapt resource value: 0x7F080043 + public const int expanded_menu = 2131230787; - // aapt resource value: 0x7f09003f - public const int enterAlways = 2131296319; + // aapt resource value: 0x7F080042 + public const int expand_activities_button = 2131230786; - // aapt resource value: 0x7f090040 - public const int enterAlwaysCollapsed = 2131296320; + // aapt resource value: 0x7F080044 + public const int fill = 2131230788; - // aapt resource value: 0x7f090041 - public const int exitUntilCollapsed = 2131296321; + // aapt resource value: 0x7F080045 + public const int fill_horizontal = 2131230789; - // aapt resource value: 0x7f09005e - public const int expand_activities_button = 2131296350; + // aapt resource value: 0x7F080046 + public const int fill_vertical = 2131230790; - // aapt resource value: 0x7f090072 - public const int expanded_menu = 2131296370; + // aapt resource value: 0x7F080047 + public const int @fixed = 2131230791; - // aapt resource value: 0x7f090050 - public const int fill = 2131296336; + // aapt resource value: 0x7F080048 + public const int flyoutcontent_appbar = 2131230792; - // aapt resource value: 0x7f090051 - public const int fill_horizontal = 2131296337; + // aapt resource value: 0x7F080049 + public const int flyoutcontent_recycler = 2131230793; - // aapt resource value: 0x7f090048 - public const int fill_vertical = 2131296328; + // aapt resource value: 0x7F08004A + public const int forever = 2131230794; - // aapt resource value: 0x7f090054 - public const int @fixed = 2131296340; + // aapt resource value: 0x7F080002 + public const int FUNCTION = 2131230722; - // aapt resource value: 0x7f09009b - public const int flyoutcontent_appbar = 2131296411; + // aapt resource value: 0x7F08004B + public const int ghost_view = 2131230795; - // aapt resource value: 0x7f09009c - public const int flyoutcontent_recycler = 2131296412; + // aapt resource value: 0x7F08004C + public const int home = 2131230796; - // aapt resource value: 0x7f090058 - public const int forever = 2131296344; + // aapt resource value: 0x7F08004D + public const int homeAsUp = 2131230797; - // aapt resource value: 0x7f09000a - public const int ghost_view = 2131296266; + // aapt resource value: 0x7F08004E + public const int icon = 2131230798; - // aapt resource value: 0x7f090005 - public const int home = 2131296261; + // aapt resource value: 0x7F08004F + public const int icon_group = 2131230799; - // aapt resource value: 0x7f090021 - public const int homeAsUp = 2131296289; + // aapt resource value: 0x7F080050 + public const int ifRoom = 2131230800; - // aapt resource value: 0x7f090062 - public const int icon = 2131296354; + // aapt resource value: 0x7F080051 + public const int image = 2131230801; - // aapt resource value: 0x7f0900c8 - public const int icon_group = 2131296456; + // aapt resource value: 0x7F080052 + public const int info = 2131230802; - // aapt resource value: 0x7f09003a - public const int ifRoom = 2131296314; + // aapt resource value: 0x7F080053 + public const int italic = 2131230803; - // aapt resource value: 0x7f09005f - public const int image = 2131296351; + // aapt resource value: 0x7F080054 + public const int item_touch_helper_previous_elevation = 2131230804; - // aapt resource value: 0x7f0900c4 - public const int info = 2131296452; + // aapt resource value: 0x7F080055 + public const int largeLabel = 2131230805; - // aapt resource value: 0x7f090059 - public const int italic = 2131296345; + // aapt resource value: 0x7F080056 + public const int left = 2131230806; - // aapt resource value: 0x7f090000 - public const int item_touch_helper_previous_elevation = 2131296256; + // aapt resource value: 0x7F080057 + public const int line1 = 2131230807; - // aapt resource value: 0x7f09008e - public const int largeLabel = 2131296398; + // aapt resource value: 0x7F080058 + public const int line3 = 2131230808; - // aapt resource value: 0x7f090049 - public const int left = 2131296329; + // aapt resource value: 0x7F080059 + public const int listMode = 2131230809; - // aapt resource value: 0x7f090017 - public const int line1 = 2131296279; + // aapt resource value: 0x7F08005A + public const int list_item = 2131230810; - // aapt resource value: 0x7f090018 - public const int line3 = 2131296280; + // aapt resource value: 0x7F08005B + public const int main_appbar = 2131230811; - // aapt resource value: 0x7f09001d - public const int listMode = 2131296285; + // aapt resource value: 0x7F08005C + public const int main_scrollview = 2131230812; - // aapt resource value: 0x7f090061 - public const int list_item = 2131296353; + // aapt resource value: 0x7F08005D + public const int main_tablayout = 2131230813; - // aapt resource value: 0x7f0900ca - public const int main_appbar = 2131296458; + // aapt resource value: 0x7F08005E + public const int main_toolbar = 2131230814; - // aapt resource value: 0x7f0900cd - public const int main_scrollview = 2131296461; + // aapt resource value: 0x7F08005F + public const int masked = 2131230815; - // aapt resource value: 0x7f0900cc - public const int main_tablayout = 2131296460; + // aapt resource value: 0x7F080060 + public const int media_actions = 2131230816; - // aapt resource value: 0x7f0900cb - public const int main_toolbar = 2131296459; + // aapt resource value: 0x7F080061 + public const int message = 2131230817; - // aapt resource value: 0x7f0900d3 - public const int masked = 2131296467; + // aapt resource value: 0x7F080003 + public const int META = 2131230723; - // aapt resource value: 0x7f0900bd - public const int media_actions = 2131296445; + // aapt resource value: 0x7F080062 + public const int middle = 2131230818; - // aapt resource value: 0x7f0900d1 - public const int message = 2131296465; + // aapt resource value: 0x7F080063 + public const int mini = 2131230819; - // aapt resource value: 0x7f090031 - public const int middle = 2131296305; + // aapt resource value: 0x7F080064 + public const int mr_art = 2131230820; - // aapt resource value: 0x7f090053 - public const int mini = 2131296339; + // aapt resource value: 0x7F080065 + public const int mr_chooser_list = 2131230821; - // aapt resource value: 0x7f0900a9 - public const int mr_art = 2131296425; + // aapt resource value: 0x7F080066 + public const int mr_chooser_route_desc = 2131230822; - // aapt resource value: 0x7f09009e - public const int mr_chooser_list = 2131296414; + // aapt resource value: 0x7F080067 + public const int mr_chooser_route_icon = 2131230823; - // aapt resource value: 0x7f0900a1 - public const int mr_chooser_route_desc = 2131296417; + // aapt resource value: 0x7F080068 + public const int mr_chooser_route_name = 2131230824; - // aapt resource value: 0x7f09009f - public const int mr_chooser_route_icon = 2131296415; + // aapt resource value: 0x7F080069 + public const int mr_chooser_title = 2131230825; - // aapt resource value: 0x7f0900a0 - public const int mr_chooser_route_name = 2131296416; + // aapt resource value: 0x7F08006A + public const int mr_close = 2131230826; - // aapt resource value: 0x7f09009d - public const int mr_chooser_title = 2131296413; + // aapt resource value: 0x7F08006B + public const int mr_control_divider = 2131230827; - // aapt resource value: 0x7f0900a6 - public const int mr_close = 2131296422; + // aapt resource value: 0x7F08006C + public const int mr_control_playback_ctrl = 2131230828; - // aapt resource value: 0x7f0900ac - public const int mr_control_divider = 2131296428; + // aapt resource value: 0x7F08006D + public const int mr_control_subtitle = 2131230829; - // aapt resource value: 0x7f0900b2 - public const int mr_control_playback_ctrl = 2131296434; + // aapt resource value: 0x7F08006E + public const int mr_control_title = 2131230830; - // aapt resource value: 0x7f0900b5 - public const int mr_control_subtitle = 2131296437; + // aapt resource value: 0x7F08006F + public const int mr_control_title_container = 2131230831; - // aapt resource value: 0x7f0900b4 - public const int mr_control_title = 2131296436; + // aapt resource value: 0x7F080070 + public const int mr_custom_control = 2131230832; - // aapt resource value: 0x7f0900b3 - public const int mr_control_title_container = 2131296435; + // aapt resource value: 0x7F080071 + public const int mr_default_control = 2131230833; - // aapt resource value: 0x7f0900a7 - public const int mr_custom_control = 2131296423; + // aapt resource value: 0x7F080072 + public const int mr_dialog_area = 2131230834; - // aapt resource value: 0x7f0900a8 - public const int mr_default_control = 2131296424; + // aapt resource value: 0x7F080073 + public const int mr_expandable_area = 2131230835; - // aapt resource value: 0x7f0900a3 - public const int mr_dialog_area = 2131296419; + // aapt resource value: 0x7F080074 + public const int mr_group_expand_collapse = 2131230836; - // aapt resource value: 0x7f0900a2 - public const int mr_expandable_area = 2131296418; + // aapt resource value: 0x7F080075 + public const int mr_media_main_control = 2131230837; - // aapt resource value: 0x7f0900b6 - public const int mr_group_expand_collapse = 2131296438; + // aapt resource value: 0x7F080076 + public const int mr_name = 2131230838; - // aapt resource value: 0x7f0900aa - public const int mr_media_main_control = 2131296426; + // aapt resource value: 0x7F080077 + public const int mr_playback_control = 2131230839; - // aapt resource value: 0x7f0900a5 - public const int mr_name = 2131296421; + // aapt resource value: 0x7F080078 + public const int mr_title_bar = 2131230840; - // aapt resource value: 0x7f0900ab - public const int mr_playback_control = 2131296427; + // aapt resource value: 0x7F080079 + public const int mr_volume_control = 2131230841; - // aapt resource value: 0x7f0900a4 - public const int mr_title_bar = 2131296420; + // aapt resource value: 0x7F08007A + public const int mr_volume_group_list = 2131230842; - // aapt resource value: 0x7f0900ad - public const int mr_volume_control = 2131296429; + // aapt resource value: 0x7F08007B + public const int mr_volume_item_icon = 2131230843; - // aapt resource value: 0x7f0900ae - public const int mr_volume_group_list = 2131296430; + // aapt resource value: 0x7F08007C + public const int mr_volume_slider = 2131230844; - // aapt resource value: 0x7f0900b0 - public const int mr_volume_item_icon = 2131296432; + // aapt resource value: 0x7F08007D + public const int multiply = 2131230845; - // aapt resource value: 0x7f0900b1 - public const int mr_volume_slider = 2131296433; + // aapt resource value: 0x7F08007E + public const int navigation_header_container = 2131230846; - // aapt resource value: 0x7f090028 - public const int multiply = 2131296296; + // aapt resource value: 0x7F08007F + public const int never = 2131230847; - // aapt resource value: 0x7f090095 - public const int navigation_header_container = 2131296405; + // aapt resource value: 0x7F080080 + public const int none = 2131230848; - // aapt resource value: 0x7f09003b - public const int never = 2131296315; + // aapt resource value: 0x7F080081 + public const int normal = 2131230849; - // aapt resource value: 0x7f090022 - public const int none = 2131296290; + // aapt resource value: 0x7F080082 + public const int notification_background = 2131230850; - // aapt resource value: 0x7f09001e - public const int normal = 2131296286; + // aapt resource value: 0x7F080083 + public const int notification_main_column = 2131230851; - // aapt resource value: 0x7f0900c6 - public const int notification_background = 2131296454; + // aapt resource value: 0x7F080084 + public const int notification_main_column_container = 2131230852; - // aapt resource value: 0x7f0900c0 - public const int notification_main_column = 2131296448; + // aapt resource value: 0x7F080085 + public const int parallax = 2131230853; - // aapt resource value: 0x7f0900bf - public const int notification_main_column_container = 2131296447; + // aapt resource value: 0x7F080086 + public const int parentPanel = 2131230854; - // aapt resource value: 0x7f09004c - public const int parallax = 2131296332; + // aapt resource value: 0x7F080087 + public const int parent_matrix = 2131230855; - // aapt resource value: 0x7f090065 - public const int parentPanel = 2131296357; + // aapt resource value: 0x7F080088 + public const int pin = 2131230856; - // aapt resource value: 0x7f09000b - public const int parent_matrix = 2131296267; + // aapt resource value: 0x7F080089 + public const int progress_circular = 2131230857; - // aapt resource value: 0x7f09004d - public const int pin = 2131296333; + // aapt resource value: 0x7F08008A + public const int progress_horizontal = 2131230858; - // aapt resource value: 0x7f090006 - public const int progress_circular = 2131296262; + // aapt resource value: 0x7F08008B + public const int radio = 2131230859; - // aapt resource value: 0x7f090007 - public const int progress_horizontal = 2131296263; + // aapt resource value: 0x7F08008C + public const int right = 2131230860; - // aapt resource value: 0x7f090075 - public const int radio = 2131296373; + // aapt resource value: 0x7F08008D + public const int right_icon = 2131230861; - // aapt resource value: 0x7f09004a - public const int right = 2131296330; + // aapt resource value: 0x7F08008E + public const int right_side = 2131230862; - // aapt resource value: 0x7f0900c5 - public const int right_icon = 2131296453; + // aapt resource value: 0x7F08008F + public const int save_image_matrix = 2131230863; - // aapt resource value: 0x7f0900c1 - public const int right_side = 2131296449; + // aapt resource value: 0x7F080090 + public const int save_non_transition_alpha = 2131230864; - // aapt resource value: 0x7f09000c - public const int save_image_matrix = 2131296268; + // aapt resource value: 0x7F080091 + public const int save_scale_type = 2131230865; - // aapt resource value: 0x7f09000d - public const int save_non_transition_alpha = 2131296269; + // aapt resource value: 0x7F080092 + public const int screen = 2131230866; - // aapt resource value: 0x7f09000e - public const int save_scale_type = 2131296270; + // aapt resource value: 0x7F080093 + public const int scroll = 2131230867; - // aapt resource value: 0x7f090029 - public const int screen = 2131296297; + // aapt resource value: 0x7F080097 + public const int scrollable = 2131230871; - // aapt resource value: 0x7f090042 - public const int scroll = 2131296322; + // aapt resource value: 0x7F080094 + public const int scrollIndicatorDown = 2131230868; - // aapt resource value: 0x7f09006b - public const int scrollIndicatorDown = 2131296363; + // aapt resource value: 0x7F080095 + public const int scrollIndicatorUp = 2131230869; - // aapt resource value: 0x7f090067 - public const int scrollIndicatorUp = 2131296359; + // aapt resource value: 0x7F080096 + public const int scrollView = 2131230870; - // aapt resource value: 0x7f090068 - public const int scrollView = 2131296360; + // aapt resource value: 0x7F080098 + public const int search_badge = 2131230872; - // aapt resource value: 0x7f090055 - public const int scrollable = 2131296341; + // aapt resource value: 0x7F080099 + public const int search_bar = 2131230873; - // aapt resource value: 0x7f090080 - public const int search_badge = 2131296384; + // aapt resource value: 0x7F08009A + public const int search_button = 2131230874; - // aapt resource value: 0x7f09007f - public const int search_bar = 2131296383; + // aapt resource value: 0x7F08009B + public const int search_close_btn = 2131230875; - // aapt resource value: 0x7f090081 - public const int search_button = 2131296385; + // aapt resource value: 0x7F08009C + public const int search_edit_frame = 2131230876; - // aapt resource value: 0x7f090086 - public const int search_close_btn = 2131296390; + // aapt resource value: 0x7F08009D + public const int search_go_btn = 2131230877; - // aapt resource value: 0x7f090082 - public const int search_edit_frame = 2131296386; + // aapt resource value: 0x7F08009E + public const int search_mag_icon = 2131230878; - // aapt resource value: 0x7f090088 - public const int search_go_btn = 2131296392; + // aapt resource value: 0x7F08009F + public const int search_plate = 2131230879; - // aapt resource value: 0x7f090083 - public const int search_mag_icon = 2131296387; + // aapt resource value: 0x7F0800A0 + public const int search_src_text = 2131230880; - // aapt resource value: 0x7f090084 - public const int search_plate = 2131296388; + // aapt resource value: 0x7F0800A1 + public const int search_voice_btn = 2131230881; - // aapt resource value: 0x7f090085 - public const int search_src_text = 2131296389; + // aapt resource value: 0x7F0800A2 + public const int select_dialog_listview = 2131230882; - // aapt resource value: 0x7f090089 - public const int search_voice_btn = 2131296393; + // aapt resource value: 0x7F0800A3 + public const int shellcontent_appbar = 2131230883; - // aapt resource value: 0x7f09008a - public const int select_dialog_listview = 2131296394; + // aapt resource value: 0x7F0800A4 + public const int shellcontent_scrollview = 2131230884; - // aapt resource value: 0x7f0900ce - public const int shellcontent_appbar = 2131296462; + // aapt resource value: 0x7F0800A5 + public const int shellcontent_toolbar = 2131230885; - // aapt resource value: 0x7f0900d0 - public const int shellcontent_scrollview = 2131296464; + // aapt resource value: 0x7F080004 + public const int SHIFT = 2131230724; - // aapt resource value: 0x7f0900cf - public const int shellcontent_toolbar = 2131296463; + // aapt resource value: 0x7F0800A6 + public const int shortcut = 2131230886; - // aapt resource value: 0x7f090074 - public const int shortcut = 2131296372; + // aapt resource value: 0x7F0800A7 + public const int showCustom = 2131230887; - // aapt resource value: 0x7f090023 - public const int showCustom = 2131296291; + // aapt resource value: 0x7F0800A8 + public const int showHome = 2131230888; - // aapt resource value: 0x7f090024 - public const int showHome = 2131296292; + // aapt resource value: 0x7F0800A9 + public const int showTitle = 2131230889; - // aapt resource value: 0x7f090025 - public const int showTitle = 2131296293; + // aapt resource value: 0x7F0800AA + public const int smallLabel = 2131230890; - // aapt resource value: 0x7f09008d - public const int smallLabel = 2131296397; + // aapt resource value: 0x7F0800AB + public const int snackbar_action = 2131230891; - // aapt resource value: 0x7f090094 - public const int snackbar_action = 2131296404; + // aapt resource value: 0x7F0800AC + public const int snackbar_text = 2131230892; - // aapt resource value: 0x7f090093 - public const int snackbar_text = 2131296403; + // aapt resource value: 0x7F0800AD + public const int snap = 2131230893; - // aapt resource value: 0x7f090043 - public const int snap = 2131296323; + // aapt resource value: 0x7F0800AE + public const int spacer = 2131230894; - // aapt resource value: 0x7f090064 - public const int spacer = 2131296356; + // aapt resource value: 0x7F0800AF + public const int split_action_bar = 2131230895; - // aapt resource value: 0x7f090008 - public const int split_action_bar = 2131296264; + // aapt resource value: 0x7F0800B0 + public const int src_atop = 2131230896; - // aapt resource value: 0x7f09002a - public const int src_atop = 2131296298; + // aapt resource value: 0x7F0800B1 + public const int src_in = 2131230897; - // aapt resource value: 0x7f09002b - public const int src_in = 2131296299; + // aapt resource value: 0x7F0800B2 + public const int src_over = 2131230898; - // aapt resource value: 0x7f09002c - public const int src_over = 2131296300; + // aapt resource value: 0x7F0800B3 + public const int start = 2131230899; - // aapt resource value: 0x7f09004b - public const int start = 2131296331; + // aapt resource value: 0x7F0800B4 + public const int status_bar_latest_event_content = 2131230900; - // aapt resource value: 0x7f0900bc - public const int status_bar_latest_event_content = 2131296444; + // aapt resource value: 0x7F0800B5 + public const int submenuarrow = 2131230901; - // aapt resource value: 0x7f090076 - public const int submenuarrow = 2131296374; + // aapt resource value: 0x7F0800B6 + public const int submit_area = 2131230902; - // aapt resource value: 0x7f090087 - public const int submit_area = 2131296391; + // aapt resource value: 0x7F080005 + public const int SYM = 2131230725; - // aapt resource value: 0x7f09001f - public const int tabMode = 2131296287; + // aapt resource value: 0x7F0800B7 + public const int tabMode = 2131230903; - // aapt resource value: 0x7f090019 - public const int tag_transition_group = 2131296281; + // aapt resource value: 0x7F0800B8 + public const int tag_transition_group = 2131230904; - // aapt resource value: 0x7f09001a - public const int text = 2131296282; + // aapt resource value: 0x7F0800B9 + public const int text = 2131230905; - // aapt resource value: 0x7f09001b - public const int text2 = 2131296283; + // aapt resource value: 0x7F0800BA + public const int text2 = 2131230906; - // aapt resource value: 0x7f09006a - public const int textSpacerNoButtons = 2131296362; + // aapt resource value: 0x7F0800BE + public const int textinput_counter = 2131230910; - // aapt resource value: 0x7f090069 - public const int textSpacerNoTitle = 2131296361; + // aapt resource value: 0x7F0800BF + public const int textinput_error = 2131230911; - // aapt resource value: 0x7f09009a - public const int text_input_password_toggle = 2131296410; + // aapt resource value: 0x7F0800BB + public const int textSpacerNoButtons = 2131230907; - // aapt resource value: 0x7f090014 - public const int textinput_counter = 2131296276; + // aapt resource value: 0x7F0800BC + public const int textSpacerNoTitle = 2131230908; - // aapt resource value: 0x7f090015 - public const int textinput_error = 2131296277; + // aapt resource value: 0x7F0800BD + public const int text_input_password_toggle = 2131230909; - // aapt resource value: 0x7f0900c2 - public const int time = 2131296450; + // aapt resource value: 0x7F0800C0 + public const int time = 2131230912; - // aapt resource value: 0x7f09001c - public const int title = 2131296284; + // aapt resource value: 0x7F0800C1 + public const int title = 2131230913; - // aapt resource value: 0x7f090071 - public const int titleDividerNoCustom = 2131296369; + // aapt resource value: 0x7F0800C2 + public const int titleDividerNoCustom = 2131230914; - // aapt resource value: 0x7f09006f - public const int title_template = 2131296367; + // aapt resource value: 0x7F0800C3 + public const int title_template = 2131230915; - // aapt resource value: 0x7f09003e - public const int top = 2131296318; + // aapt resource value: 0x7F0800C4 + public const int top = 2131230916; - // aapt resource value: 0x7f09006e - public const int topPanel = 2131296366; + // aapt resource value: 0x7F0800C5 + public const int topPanel = 2131230917; - // aapt resource value: 0x7f090091 - public const int touch_outside = 2131296401; + // aapt resource value: 0x7F0800C6 + public const int touch_outside = 2131230918; - // aapt resource value: 0x7f09000f - public const int transition_current_scene = 2131296271; + // aapt resource value: 0x7F0800C7 + public const int transition_current_scene = 2131230919; - // aapt resource value: 0x7f090010 - public const int transition_layout_save = 2131296272; + // aapt resource value: 0x7F0800C8 + public const int transition_layout_save = 2131230920; - // aapt resource value: 0x7f090011 - public const int transition_position = 2131296273; + // aapt resource value: 0x7F0800C9 + public const int transition_position = 2131230921; - // aapt resource value: 0x7f090012 - public const int transition_scene_layoutid_cache = 2131296274; + // aapt resource value: 0x7F0800CA + public const int transition_scene_layoutid_cache = 2131230922; - // aapt resource value: 0x7f090013 - public const int transition_transform = 2131296275; + // aapt resource value: 0x7F0800CB + public const int transition_transform = 2131230923; - // aapt resource value: 0x7f09002d - public const int uniform = 2131296301; + // aapt resource value: 0x7F0800CC + public const int uniform = 2131230924; - // aapt resource value: 0x7f090009 - public const int up = 2131296265; + // aapt resource value: 0x7F0800CD + public const int up = 2131230925; - // aapt resource value: 0x7f090026 - public const int useLogo = 2131296294; + // aapt resource value: 0x7F0800CE + public const int useLogo = 2131230926; - // aapt resource value: 0x7f090016 - public const int view_offset_helper = 2131296278; + // aapt resource value: 0x7F0800CF + public const int view_offset_helper = 2131230927; - // aapt resource value: 0x7f0900d2 - public const int visible = 2131296466; + // aapt resource value: 0x7F0800D0 + public const int visible = 2131230928; - // aapt resource value: 0x7f0900af - public const int volume_item_container = 2131296431; + // aapt resource value: 0x7F0800D1 + public const int volume_item_container = 2131230929; - // aapt resource value: 0x7f09003c - public const int withText = 2131296316; + // aapt resource value: 0x7F0800D2 + public const int withText = 2131230930; - // aapt resource value: 0x7f09002e - public const int wrap_content = 2131296302; + // aapt resource value: 0x7F0800D3 + public const int wrap_content = 2131230931; static Id() { @@ -5783,44 +5657,44 @@ private Id() public partial class Integer { - // aapt resource value: 0x7f0b0003 - public const int abc_config_activityDefaultDur = 2131427331; + // aapt resource value: 0x7F090000 + public const int abc_config_activityDefaultDur = 2131296256; - // aapt resource value: 0x7f0b0004 - public const int abc_config_activityShortDur = 2131427332; + // aapt resource value: 0x7F090001 + public const int abc_config_activityShortDur = 2131296257; - // aapt resource value: 0x7f0b0008 - public const int app_bar_elevation_anim_duration = 2131427336; + // aapt resource value: 0x7F090002 + public const int app_bar_elevation_anim_duration = 2131296258; - // aapt resource value: 0x7f0b0009 - public const int bottom_sheet_slide_duration = 2131427337; + // aapt resource value: 0x7F090003 + public const int bottom_sheet_slide_duration = 2131296259; - // aapt resource value: 0x7f0b0005 - public const int cancel_button_image_alpha = 2131427333; + // aapt resource value: 0x7F090004 + public const int cancel_button_image_alpha = 2131296260; - // aapt resource value: 0x7f0b0006 - public const int config_tooltipAnimTime = 2131427334; + // aapt resource value: 0x7F090005 + public const int config_tooltipAnimTime = 2131296261; - // aapt resource value: 0x7f0b0007 - public const int design_snackbar_text_max_lines = 2131427335; + // aapt resource value: 0x7F090006 + public const int design_snackbar_text_max_lines = 2131296262; - // aapt resource value: 0x7f0b000a - public const int hide_password_duration = 2131427338; + // aapt resource value: 0x7F090007 + public const int hide_password_duration = 2131296263; - // aapt resource value: 0x7f0b0000 - public const int mr_controller_volume_group_list_animation_duration_ms = 2131427328; + // aapt resource value: 0x7F090008 + public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - // aapt resource value: 0x7f0b0001 - public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131427329; + // aapt resource value: 0x7F090009 + public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - // aapt resource value: 0x7f0b0002 - public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131427330; + // aapt resource value: 0x7F09000A + public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - // aapt resource value: 0x7f0b000b - public const int show_password_duration = 2131427339; + // aapt resource value: 0x7F09000B + public const int show_password_duration = 2131296267; - // aapt resource value: 0x7f0b000c - public const int status_bar_notification_info_maxnum = 2131427340; + // aapt resource value: 0x7F09000C + public const int status_bar_notification_info_maxnum = 2131296268; static Integer() { @@ -5835,11 +5709,11 @@ private Integer() public partial class Interpolator { - // aapt resource value: 0x7f070000 - public const int mr_fast_out_slow_in = 2131165184; + // aapt resource value: 0x7F0A0000 + public const int mr_fast_out_slow_in = 2131361792; - // aapt resource value: 0x7f070001 - public const int mr_linear_out_slow_in = 2131165185; + // aapt resource value: 0x7F0A0001 + public const int mr_linear_out_slow_in = 2131361793; static Interpolator() { @@ -5854,218 +5728,218 @@ private Interpolator() public partial class Layout { - // aapt resource value: 0x7f040000 - public const int abc_action_bar_title_item = 2130968576; + // aapt resource value: 0x7F0B0000 + public const int abc_action_bar_title_item = 2131427328; - // aapt resource value: 0x7f040001 - public const int abc_action_bar_up_container = 2130968577; + // aapt resource value: 0x7F0B0001 + public const int abc_action_bar_up_container = 2131427329; - // aapt resource value: 0x7f040002 - public const int abc_action_menu_item_layout = 2130968578; + // aapt resource value: 0x7F0B0002 + public const int abc_action_menu_item_layout = 2131427330; - // aapt resource value: 0x7f040003 - public const int abc_action_menu_layout = 2130968579; + // aapt resource value: 0x7F0B0003 + public const int abc_action_menu_layout = 2131427331; - // aapt resource value: 0x7f040004 - public const int abc_action_mode_bar = 2130968580; + // aapt resource value: 0x7F0B0004 + public const int abc_action_mode_bar = 2131427332; - // aapt resource value: 0x7f040005 - public const int abc_action_mode_close_item_material = 2130968581; + // aapt resource value: 0x7F0B0005 + public const int abc_action_mode_close_item_material = 2131427333; - // aapt resource value: 0x7f040006 - public const int abc_activity_chooser_view = 2130968582; + // aapt resource value: 0x7F0B0006 + public const int abc_activity_chooser_view = 2131427334; - // aapt resource value: 0x7f040007 - public const int abc_activity_chooser_view_list_item = 2130968583; + // aapt resource value: 0x7F0B0007 + public const int abc_activity_chooser_view_list_item = 2131427335; - // aapt resource value: 0x7f040008 - public const int abc_alert_dialog_button_bar_material = 2130968584; + // aapt resource value: 0x7F0B0008 + public const int abc_alert_dialog_button_bar_material = 2131427336; - // aapt resource value: 0x7f040009 - public const int abc_alert_dialog_material = 2130968585; + // aapt resource value: 0x7F0B0009 + public const int abc_alert_dialog_material = 2131427337; - // aapt resource value: 0x7f04000a - public const int abc_alert_dialog_title_material = 2130968586; + // aapt resource value: 0x7F0B000A + public const int abc_alert_dialog_title_material = 2131427338; - // aapt resource value: 0x7f04000b - public const int abc_dialog_title_material = 2130968587; + // aapt resource value: 0x7F0B000B + public const int abc_dialog_title_material = 2131427339; - // aapt resource value: 0x7f04000c - public const int abc_expanded_menu_layout = 2130968588; + // aapt resource value: 0x7F0B000C + public const int abc_expanded_menu_layout = 2131427340; - // aapt resource value: 0x7f04000d - public const int abc_list_menu_item_checkbox = 2130968589; + // aapt resource value: 0x7F0B000D + public const int abc_list_menu_item_checkbox = 2131427341; - // aapt resource value: 0x7f04000e - public const int abc_list_menu_item_icon = 2130968590; + // aapt resource value: 0x7F0B000E + public const int abc_list_menu_item_icon = 2131427342; - // aapt resource value: 0x7f04000f - public const int abc_list_menu_item_layout = 2130968591; + // aapt resource value: 0x7F0B000F + public const int abc_list_menu_item_layout = 2131427343; - // aapt resource value: 0x7f040010 - public const int abc_list_menu_item_radio = 2130968592; + // aapt resource value: 0x7F0B0010 + public const int abc_list_menu_item_radio = 2131427344; - // aapt resource value: 0x7f040011 - public const int abc_popup_menu_header_item_layout = 2130968593; + // aapt resource value: 0x7F0B0011 + public const int abc_popup_menu_header_item_layout = 2131427345; - // aapt resource value: 0x7f040012 - public const int abc_popup_menu_item_layout = 2130968594; + // aapt resource value: 0x7F0B0012 + public const int abc_popup_menu_item_layout = 2131427346; - // aapt resource value: 0x7f040013 - public const int abc_screen_content_include = 2130968595; + // aapt resource value: 0x7F0B0013 + public const int abc_screen_content_include = 2131427347; - // aapt resource value: 0x7f040014 - public const int abc_screen_simple = 2130968596; + // aapt resource value: 0x7F0B0014 + public const int abc_screen_simple = 2131427348; - // aapt resource value: 0x7f040015 - public const int abc_screen_simple_overlay_action_mode = 2130968597; + // aapt resource value: 0x7F0B0015 + public const int abc_screen_simple_overlay_action_mode = 2131427349; - // aapt resource value: 0x7f040016 - public const int abc_screen_toolbar = 2130968598; + // aapt resource value: 0x7F0B0016 + public const int abc_screen_toolbar = 2131427350; - // aapt resource value: 0x7f040017 - public const int abc_search_dropdown_item_icons_2line = 2130968599; + // aapt resource value: 0x7F0B0017 + public const int abc_search_dropdown_item_icons_2line = 2131427351; - // aapt resource value: 0x7f040018 - public const int abc_search_view = 2130968600; + // aapt resource value: 0x7F0B0018 + public const int abc_search_view = 2131427352; - // aapt resource value: 0x7f040019 - public const int abc_select_dialog_material = 2130968601; + // aapt resource value: 0x7F0B0019 + public const int abc_select_dialog_material = 2131427353; - // aapt resource value: 0x7f04001a - public const int activity_main = 2130968602; + // aapt resource value: 0x7F0B001A + public const int activity_main = 2131427354; - // aapt resource value: 0x7f04001b - public const int BottomTabLayout = 2130968603; + // aapt resource value: 0x7F0B001B + public const int BottomTabLayout = 2131427355; - // aapt resource value: 0x7f04001c - public const int design_bottom_navigation_item = 2130968604; + // aapt resource value: 0x7F0B001C + public const int design_bottom_navigation_item = 2131427356; - // aapt resource value: 0x7f04001d - public const int design_bottom_sheet_dialog = 2130968605; + // aapt resource value: 0x7F0B001D + public const int design_bottom_sheet_dialog = 2131427357; - // aapt resource value: 0x7f04001e - public const int design_layout_snackbar = 2130968606; + // aapt resource value: 0x7F0B001E + public const int design_layout_snackbar = 2131427358; - // aapt resource value: 0x7f04001f - public const int design_layout_snackbar_include = 2130968607; + // aapt resource value: 0x7F0B001F + public const int design_layout_snackbar_include = 2131427359; - // aapt resource value: 0x7f040020 - public const int design_layout_tab_icon = 2130968608; + // aapt resource value: 0x7F0B0020 + public const int design_layout_tab_icon = 2131427360; - // aapt resource value: 0x7f040021 - public const int design_layout_tab_text = 2130968609; + // aapt resource value: 0x7F0B0021 + public const int design_layout_tab_text = 2131427361; - // aapt resource value: 0x7f040022 - public const int design_menu_item_action_area = 2130968610; + // aapt resource value: 0x7F0B0022 + public const int design_menu_item_action_area = 2131427362; - // aapt resource value: 0x7f040023 - public const int design_navigation_item = 2130968611; + // aapt resource value: 0x7F0B0023 + public const int design_navigation_item = 2131427363; - // aapt resource value: 0x7f040024 - public const int design_navigation_item_header = 2130968612; + // aapt resource value: 0x7F0B0024 + public const int design_navigation_item_header = 2131427364; - // aapt resource value: 0x7f040025 - public const int design_navigation_item_separator = 2130968613; + // aapt resource value: 0x7F0B0025 + public const int design_navigation_item_separator = 2131427365; - // aapt resource value: 0x7f040026 - public const int design_navigation_item_subheader = 2130968614; + // aapt resource value: 0x7F0B0026 + public const int design_navigation_item_subheader = 2131427366; - // aapt resource value: 0x7f040027 - public const int design_navigation_menu = 2130968615; + // aapt resource value: 0x7F0B0027 + public const int design_navigation_menu = 2131427367; - // aapt resource value: 0x7f040028 - public const int design_navigation_menu_item = 2130968616; + // aapt resource value: 0x7F0B0028 + public const int design_navigation_menu_item = 2131427368; - // aapt resource value: 0x7f040029 - public const int design_text_input_password_icon = 2130968617; + // aapt resource value: 0x7F0B0029 + public const int design_text_input_password_icon = 2131427369; - // aapt resource value: 0x7f04002a - public const int FlyoutContent = 2130968618; + // aapt resource value: 0x7F0B002A + public const int FlyoutContent = 2131427370; - // aapt resource value: 0x7f04002b - public const int mr_chooser_dialog = 2130968619; + // aapt resource value: 0x7F0B002B + public const int mr_chooser_dialog = 2131427371; - // aapt resource value: 0x7f04002c - public const int mr_chooser_list_item = 2130968620; + // aapt resource value: 0x7F0B002C + public const int mr_chooser_list_item = 2131427372; - // aapt resource value: 0x7f04002d - public const int mr_controller_material_dialog_b = 2130968621; + // aapt resource value: 0x7F0B002D + public const int mr_controller_material_dialog_b = 2131427373; - // aapt resource value: 0x7f04002e - public const int mr_controller_volume_item = 2130968622; + // aapt resource value: 0x7F0B002E + public const int mr_controller_volume_item = 2131427374; - // aapt resource value: 0x7f04002f - public const int mr_playback_control = 2130968623; + // aapt resource value: 0x7F0B002F + public const int mr_playback_control = 2131427375; - // aapt resource value: 0x7f040030 - public const int mr_volume_control = 2130968624; + // aapt resource value: 0x7F0B0030 + public const int mr_volume_control = 2131427376; - // aapt resource value: 0x7f040031 - public const int notification_action = 2130968625; + // aapt resource value: 0x7F0B0031 + public const int notification_action = 2131427377; - // aapt resource value: 0x7f040032 - public const int notification_action_tombstone = 2130968626; + // aapt resource value: 0x7F0B0032 + public const int notification_action_tombstone = 2131427378; - // aapt resource value: 0x7f040033 - public const int notification_media_action = 2130968627; + // aapt resource value: 0x7F0B0033 + public const int notification_media_action = 2131427379; - // aapt resource value: 0x7f040034 - public const int notification_media_cancel_action = 2130968628; + // aapt resource value: 0x7F0B0034 + public const int notification_media_cancel_action = 2131427380; - // aapt resource value: 0x7f040035 - public const int notification_template_big_media = 2130968629; + // aapt resource value: 0x7F0B0035 + public const int notification_template_big_media = 2131427381; - // aapt resource value: 0x7f040036 - public const int notification_template_big_media_custom = 2130968630; + // aapt resource value: 0x7F0B0036 + public const int notification_template_big_media_custom = 2131427382; - // aapt resource value: 0x7f040037 - public const int notification_template_big_media_narrow = 2130968631; + // aapt resource value: 0x7F0B0037 + public const int notification_template_big_media_narrow = 2131427383; - // aapt resource value: 0x7f040038 - public const int notification_template_big_media_narrow_custom = 2130968632; + // aapt resource value: 0x7F0B0038 + public const int notification_template_big_media_narrow_custom = 2131427384; - // aapt resource value: 0x7f040039 - public const int notification_template_custom_big = 2130968633; + // aapt resource value: 0x7F0B0039 + public const int notification_template_custom_big = 2131427385; - // aapt resource value: 0x7f04003a - public const int notification_template_icon_group = 2130968634; + // aapt resource value: 0x7F0B003A + public const int notification_template_icon_group = 2131427386; - // aapt resource value: 0x7f04003b - public const int notification_template_lines_media = 2130968635; + // aapt resource value: 0x7F0B003B + public const int notification_template_lines_media = 2131427387; - // aapt resource value: 0x7f04003c - public const int notification_template_media = 2130968636; + // aapt resource value: 0x7F0B003C + public const int notification_template_media = 2131427388; - // aapt resource value: 0x7f04003d - public const int notification_template_media_custom = 2130968637; + // aapt resource value: 0x7F0B003D + public const int notification_template_media_custom = 2131427389; - // aapt resource value: 0x7f04003e - public const int notification_template_part_chronometer = 2130968638; + // aapt resource value: 0x7F0B003E + public const int notification_template_part_chronometer = 2131427390; - // aapt resource value: 0x7f04003f - public const int notification_template_part_time = 2130968639; + // aapt resource value: 0x7F0B003F + public const int notification_template_part_time = 2131427391; - // aapt resource value: 0x7f040040 - public const int RootLayout = 2130968640; + // aapt resource value: 0x7F0B0040 + public const int RootLayout = 2131427392; - // aapt resource value: 0x7f040041 - public const int select_dialog_item_material = 2130968641; + // aapt resource value: 0x7F0B0041 + public const int select_dialog_item_material = 2131427393; - // aapt resource value: 0x7f040042 - public const int select_dialog_multichoice_material = 2130968642; + // aapt resource value: 0x7F0B0042 + public const int select_dialog_multichoice_material = 2131427394; - // aapt resource value: 0x7f040043 - public const int select_dialog_singlechoice_material = 2130968643; + // aapt resource value: 0x7F0B0043 + public const int select_dialog_singlechoice_material = 2131427395; - // aapt resource value: 0x7f040044 - public const int ShellContent = 2130968644; + // aapt resource value: 0x7F0B0044 + public const int ShellContent = 2131427396; - // aapt resource value: 0x7f040045 - public const int support_simple_spinner_dropdown_item = 2130968645; + // aapt resource value: 0x7F0B0045 + public const int support_simple_spinner_dropdown_item = 2131427397; - // aapt resource value: 0x7f040046 - public const int tooltip = 2130968646; + // aapt resource value: 0x7F0B0046 + public const int tooltip = 2131427398; static Layout() { @@ -6080,14 +5954,14 @@ private Layout() public partial class Mipmap { - // aapt resource value: 0x7f030000 - public const int ic_launcher = 2130903040; + // aapt resource value: 0x7F0C0000 + public const int ic_launcher = 2131492864; - // aapt resource value: 0x7f030001 - public const int ic_launcher_foreground = 2130903041; + // aapt resource value: 0x7F0C0001 + public const int ic_launcher_foreground = 2131492865; - // aapt resource value: 0x7f030002 - public const int ic_launcher_round = 2130903042; + // aapt resource value: 0x7F0C0002 + public const int ic_launcher_round = 2131492866; static Mipmap() { @@ -6102,191 +5976,191 @@ private Mipmap() public partial class String { - // aapt resource value: 0x7f0a0015 - public const int abc_action_bar_home_description = 2131361813; + // aapt resource value: 0x7F0D0000 + public const int abc_action_bar_home_description = 2131558400; - // aapt resource value: 0x7f0a0016 - public const int abc_action_bar_up_description = 2131361814; + // aapt resource value: 0x7F0D0001 + public const int abc_action_bar_up_description = 2131558401; - // aapt resource value: 0x7f0a0017 - public const int abc_action_menu_overflow_description = 2131361815; + // aapt resource value: 0x7F0D0002 + public const int abc_action_menu_overflow_description = 2131558402; - // aapt resource value: 0x7f0a0018 - public const int abc_action_mode_done = 2131361816; + // aapt resource value: 0x7F0D0003 + public const int abc_action_mode_done = 2131558403; - // aapt resource value: 0x7f0a0019 - public const int abc_activity_chooser_view_see_all = 2131361817; + // aapt resource value: 0x7F0D0005 + public const int abc_activitychooserview_choose_application = 2131558405; - // aapt resource value: 0x7f0a001a - public const int abc_activitychooserview_choose_application = 2131361818; + // aapt resource value: 0x7F0D0004 + public const int abc_activity_chooser_view_see_all = 2131558404; - // aapt resource value: 0x7f0a001b - public const int abc_capital_off = 2131361819; + // aapt resource value: 0x7F0D0006 + public const int abc_capital_off = 2131558406; - // aapt resource value: 0x7f0a001c - public const int abc_capital_on = 2131361820; + // aapt resource value: 0x7F0D0007 + public const int abc_capital_on = 2131558407; - // aapt resource value: 0x7f0a0027 - public const int abc_font_family_body_1_material = 2131361831; + // aapt resource value: 0x7F0D0008 + public const int abc_font_family_body_1_material = 2131558408; - // aapt resource value: 0x7f0a0028 - public const int abc_font_family_body_2_material = 2131361832; + // aapt resource value: 0x7F0D0009 + public const int abc_font_family_body_2_material = 2131558409; - // aapt resource value: 0x7f0a0029 - public const int abc_font_family_button_material = 2131361833; + // aapt resource value: 0x7F0D000A + public const int abc_font_family_button_material = 2131558410; - // aapt resource value: 0x7f0a002a - public const int abc_font_family_caption_material = 2131361834; + // aapt resource value: 0x7F0D000B + public const int abc_font_family_caption_material = 2131558411; - // aapt resource value: 0x7f0a002b - public const int abc_font_family_display_1_material = 2131361835; + // aapt resource value: 0x7F0D000C + public const int abc_font_family_display_1_material = 2131558412; - // aapt resource value: 0x7f0a002c - public const int abc_font_family_display_2_material = 2131361836; + // aapt resource value: 0x7F0D000D + public const int abc_font_family_display_2_material = 2131558413; - // aapt resource value: 0x7f0a002d - public const int abc_font_family_display_3_material = 2131361837; + // aapt resource value: 0x7F0D000E + public const int abc_font_family_display_3_material = 2131558414; - // aapt resource value: 0x7f0a002e - public const int abc_font_family_display_4_material = 2131361838; + // aapt resource value: 0x7F0D000F + public const int abc_font_family_display_4_material = 2131558415; - // aapt resource value: 0x7f0a002f - public const int abc_font_family_headline_material = 2131361839; + // aapt resource value: 0x7F0D0010 + public const int abc_font_family_headline_material = 2131558416; - // aapt resource value: 0x7f0a0030 - public const int abc_font_family_menu_material = 2131361840; + // aapt resource value: 0x7F0D0011 + public const int abc_font_family_menu_material = 2131558417; - // aapt resource value: 0x7f0a0031 - public const int abc_font_family_subhead_material = 2131361841; + // aapt resource value: 0x7F0D0012 + public const int abc_font_family_subhead_material = 2131558418; - // aapt resource value: 0x7f0a0032 - public const int abc_font_family_title_material = 2131361842; + // aapt resource value: 0x7F0D0013 + public const int abc_font_family_title_material = 2131558419; - // aapt resource value: 0x7f0a001d - public const int abc_search_hint = 2131361821; + // aapt resource value: 0x7F0D0015 + public const int abc_searchview_description_clear = 2131558421; - // aapt resource value: 0x7f0a001e - public const int abc_searchview_description_clear = 2131361822; + // aapt resource value: 0x7F0D0016 + public const int abc_searchview_description_query = 2131558422; - // aapt resource value: 0x7f0a001f - public const int abc_searchview_description_query = 2131361823; + // aapt resource value: 0x7F0D0017 + public const int abc_searchview_description_search = 2131558423; - // aapt resource value: 0x7f0a0020 - public const int abc_searchview_description_search = 2131361824; + // aapt resource value: 0x7F0D0018 + public const int abc_searchview_description_submit = 2131558424; - // aapt resource value: 0x7f0a0021 - public const int abc_searchview_description_submit = 2131361825; + // aapt resource value: 0x7F0D0019 + public const int abc_searchview_description_voice = 2131558425; - // aapt resource value: 0x7f0a0022 - public const int abc_searchview_description_voice = 2131361826; + // aapt resource value: 0x7F0D0014 + public const int abc_search_hint = 2131558420; - // aapt resource value: 0x7f0a0023 - public const int abc_shareactionprovider_share_with = 2131361827; + // aapt resource value: 0x7F0D001A + public const int abc_shareactionprovider_share_with = 2131558426; - // aapt resource value: 0x7f0a0024 - public const int abc_shareactionprovider_share_with_application = 2131361828; + // aapt resource value: 0x7F0D001B + public const int abc_shareactionprovider_share_with_application = 2131558427; - // aapt resource value: 0x7f0a0025 - public const int abc_toolbar_collapse_description = 2131361829; + // aapt resource value: 0x7F0D001C + public const int abc_toolbar_collapse_description = 2131558428; - // aapt resource value: 0x7f0a003d - public const int action_settings = 2131361853; + // aapt resource value: 0x7F0D001D + public const int action_settings = 2131558429; - // aapt resource value: 0x7f0a003c - public const int app_name = 2131361852; + // aapt resource value: 0x7F0D001F + public const int appbar_scrolling_view_behavior = 2131558431; - // aapt resource value: 0x7f0a0033 - public const int appbar_scrolling_view_behavior = 2131361843; + // aapt resource value: 0x7F0D001E + public const int app_name = 2131558430; - // aapt resource value: 0x7f0a0034 - public const int bottom_sheet_behavior = 2131361844; + // aapt resource value: 0x7F0D0020 + public const int bottom_sheet_behavior = 2131558432; - // aapt resource value: 0x7f0a0035 - public const int character_counter_pattern = 2131361845; + // aapt resource value: 0x7F0D0021 + public const int character_counter_pattern = 2131558433; - // aapt resource value: 0x7f0a0000 - public const int mr_button_content_description = 2131361792; + // aapt resource value: 0x7F0D0022 + public const int mr_button_content_description = 2131558434; - // aapt resource value: 0x7f0a0001 - public const int mr_cast_button_connected = 2131361793; + // aapt resource value: 0x7F0D0023 + public const int mr_cast_button_connected = 2131558435; - // aapt resource value: 0x7f0a0002 - public const int mr_cast_button_connecting = 2131361794; + // aapt resource value: 0x7F0D0024 + public const int mr_cast_button_connecting = 2131558436; - // aapt resource value: 0x7f0a0003 - public const int mr_cast_button_disconnected = 2131361795; + // aapt resource value: 0x7F0D0025 + public const int mr_cast_button_disconnected = 2131558437; - // aapt resource value: 0x7f0a0004 - public const int mr_chooser_searching = 2131361796; + // aapt resource value: 0x7F0D0026 + public const int mr_chooser_searching = 2131558438; - // aapt resource value: 0x7f0a0005 - public const int mr_chooser_title = 2131361797; + // aapt resource value: 0x7F0D0027 + public const int mr_chooser_title = 2131558439; - // aapt resource value: 0x7f0a0006 - public const int mr_controller_album_art = 2131361798; + // aapt resource value: 0x7F0D0028 + public const int mr_controller_album_art = 2131558440; - // aapt resource value: 0x7f0a0007 - public const int mr_controller_casting_screen = 2131361799; + // aapt resource value: 0x7F0D0029 + public const int mr_controller_casting_screen = 2131558441; - // aapt resource value: 0x7f0a0008 - public const int mr_controller_close_description = 2131361800; + // aapt resource value: 0x7F0D002A + public const int mr_controller_close_description = 2131558442; - // aapt resource value: 0x7f0a0009 - public const int mr_controller_collapse_group = 2131361801; + // aapt resource value: 0x7F0D002B + public const int mr_controller_collapse_group = 2131558443; - // aapt resource value: 0x7f0a000a - public const int mr_controller_disconnect = 2131361802; + // aapt resource value: 0x7F0D002C + public const int mr_controller_disconnect = 2131558444; - // aapt resource value: 0x7f0a000b - public const int mr_controller_expand_group = 2131361803; + // aapt resource value: 0x7F0D002D + public const int mr_controller_expand_group = 2131558445; - // aapt resource value: 0x7f0a000c - public const int mr_controller_no_info_available = 2131361804; + // aapt resource value: 0x7F0D002E + public const int mr_controller_no_info_available = 2131558446; - // aapt resource value: 0x7f0a000d - public const int mr_controller_no_media_selected = 2131361805; + // aapt resource value: 0x7F0D002F + public const int mr_controller_no_media_selected = 2131558447; - // aapt resource value: 0x7f0a000e - public const int mr_controller_pause = 2131361806; + // aapt resource value: 0x7F0D0030 + public const int mr_controller_pause = 2131558448; - // aapt resource value: 0x7f0a000f - public const int mr_controller_play = 2131361807; + // aapt resource value: 0x7F0D0031 + public const int mr_controller_play = 2131558449; - // aapt resource value: 0x7f0a0010 - public const int mr_controller_stop = 2131361808; + // aapt resource value: 0x7F0D0032 + public const int mr_controller_stop = 2131558450; - // aapt resource value: 0x7f0a0011 - public const int mr_controller_stop_casting = 2131361809; + // aapt resource value: 0x7F0D0033 + public const int mr_controller_stop_casting = 2131558451; - // aapt resource value: 0x7f0a0012 - public const int mr_controller_volume_slider = 2131361810; + // aapt resource value: 0x7F0D0034 + public const int mr_controller_volume_slider = 2131558452; - // aapt resource value: 0x7f0a0013 - public const int mr_system_route_name = 2131361811; + // aapt resource value: 0x7F0D0035 + public const int mr_system_route_name = 2131558453; - // aapt resource value: 0x7f0a0014 - public const int mr_user_route_category_name = 2131361812; + // aapt resource value: 0x7F0D0036 + public const int mr_user_route_category_name = 2131558454; - // aapt resource value: 0x7f0a0036 - public const int password_toggle_content_description = 2131361846; + // aapt resource value: 0x7F0D0037 + public const int password_toggle_content_description = 2131558455; - // aapt resource value: 0x7f0a0037 - public const int path_password_eye = 2131361847; + // aapt resource value: 0x7F0D0038 + public const int path_password_eye = 2131558456; - // aapt resource value: 0x7f0a0038 - public const int path_password_eye_mask_strike_through = 2131361848; + // aapt resource value: 0x7F0D0039 + public const int path_password_eye_mask_strike_through = 2131558457; - // aapt resource value: 0x7f0a0039 - public const int path_password_eye_mask_visible = 2131361849; + // aapt resource value: 0x7F0D003A + public const int path_password_eye_mask_visible = 2131558458; - // aapt resource value: 0x7f0a003a - public const int path_password_strike_through = 2131361850; + // aapt resource value: 0x7F0D003B + public const int path_password_strike_through = 2131558459; - // aapt resource value: 0x7f0a0026 - public const int search_menu_title = 2131361830; + // aapt resource value: 0x7F0D003C + public const int search_menu_title = 2131558460; - // aapt resource value: 0x7f0a003b - public const int status_bar_notification_info_overflow = 2131361851; + // aapt resource value: 0x7F0D003D + public const int status_bar_notification_info_overflow = 2131558461; static String() { @@ -6301,1202 +6175,1202 @@ private String() public partial class Style { - // aapt resource value: 0x7f0c00a4 - public const int AlertDialog_AppCompat = 2131493028; + // aapt resource value: 0x7F0E0000 + public const int AlertDialog_AppCompat = 2131623936; - // aapt resource value: 0x7f0c00a5 - public const int AlertDialog_AppCompat_Light = 2131493029; + // aapt resource value: 0x7F0E0001 + public const int AlertDialog_AppCompat_Light = 2131623937; - // aapt resource value: 0x7f0c00a6 - public const int Animation_AppCompat_Dialog = 2131493030; + // aapt resource value: 0x7F0E0002 + public const int Animation_AppCompat_Dialog = 2131623938; - // aapt resource value: 0x7f0c00a7 - public const int Animation_AppCompat_DropDownUp = 2131493031; + // aapt resource value: 0x7F0E0003 + public const int Animation_AppCompat_DropDownUp = 2131623939; - // aapt resource value: 0x7f0c00a8 - public const int Animation_AppCompat_Tooltip = 2131493032; + // aapt resource value: 0x7F0E0004 + public const int Animation_AppCompat_Tooltip = 2131623940; - // aapt resource value: 0x7f0c016e - public const int Animation_Design_BottomSheetDialog = 2131493230; + // aapt resource value: 0x7F0E0005 + public const int Animation_Design_BottomSheetDialog = 2131623941; - // aapt resource value: 0x7f0c00a9 - public const int Base_AlertDialog_AppCompat = 2131493033; + // aapt resource value: 0x7F0E0006 + public const int Base_AlertDialog_AppCompat = 2131623942; - // aapt resource value: 0x7f0c00aa - public const int Base_AlertDialog_AppCompat_Light = 2131493034; + // aapt resource value: 0x7F0E0007 + public const int Base_AlertDialog_AppCompat_Light = 2131623943; - // aapt resource value: 0x7f0c00ab - public const int Base_Animation_AppCompat_Dialog = 2131493035; + // aapt resource value: 0x7F0E0008 + public const int Base_Animation_AppCompat_Dialog = 2131623944; - // aapt resource value: 0x7f0c00ac - public const int Base_Animation_AppCompat_DropDownUp = 2131493036; + // aapt resource value: 0x7F0E0009 + public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - // aapt resource value: 0x7f0c00ad - public const int Base_Animation_AppCompat_Tooltip = 2131493037; + // aapt resource value: 0x7F0E000A + public const int Base_Animation_AppCompat_Tooltip = 2131623946; - // aapt resource value: 0x7f0c000c - public const int Base_CardView = 2131492876; + // aapt resource value: 0x7F0E000B + public const int Base_CardView = 2131623947; - // aapt resource value: 0x7f0c00ae - public const int Base_DialogWindowTitle_AppCompat = 2131493038; + // aapt resource value: 0x7F0E000D + public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - // aapt resource value: 0x7f0c00af - public const int Base_DialogWindowTitleBackground_AppCompat = 2131493039; + // aapt resource value: 0x7F0E000C + public const int Base_DialogWindowTitle_AppCompat = 2131623948; - // aapt resource value: 0x7f0c0048 - public const int Base_TextAppearance_AppCompat = 2131492936; + // aapt resource value: 0x7F0E000E + public const int Base_TextAppearance_AppCompat = 2131623950; - // aapt resource value: 0x7f0c0049 - public const int Base_TextAppearance_AppCompat_Body1 = 2131492937; + // aapt resource value: 0x7F0E000F + public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - // aapt resource value: 0x7f0c004a - public const int Base_TextAppearance_AppCompat_Body2 = 2131492938; + // aapt resource value: 0x7F0E0010 + public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - // aapt resource value: 0x7f0c0036 - public const int Base_TextAppearance_AppCompat_Button = 2131492918; + // aapt resource value: 0x7F0E0011 + public const int Base_TextAppearance_AppCompat_Button = 2131623953; - // aapt resource value: 0x7f0c004b - public const int Base_TextAppearance_AppCompat_Caption = 2131492939; + // aapt resource value: 0x7F0E0012 + public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - // aapt resource value: 0x7f0c004c - public const int Base_TextAppearance_AppCompat_Display1 = 2131492940; + // aapt resource value: 0x7F0E0013 + public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - // aapt resource value: 0x7f0c004d - public const int Base_TextAppearance_AppCompat_Display2 = 2131492941; + // aapt resource value: 0x7F0E0014 + public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - // aapt resource value: 0x7f0c004e - public const int Base_TextAppearance_AppCompat_Display3 = 2131492942; + // aapt resource value: 0x7F0E0015 + public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - // aapt resource value: 0x7f0c004f - public const int Base_TextAppearance_AppCompat_Display4 = 2131492943; + // aapt resource value: 0x7F0E0016 + public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - // aapt resource value: 0x7f0c0050 - public const int Base_TextAppearance_AppCompat_Headline = 2131492944; + // aapt resource value: 0x7F0E0017 + public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - // aapt resource value: 0x7f0c001a - public const int Base_TextAppearance_AppCompat_Inverse = 2131492890; + // aapt resource value: 0x7F0E0018 + public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - // aapt resource value: 0x7f0c0051 - public const int Base_TextAppearance_AppCompat_Large = 2131492945; + // aapt resource value: 0x7F0E0019 + public const int Base_TextAppearance_AppCompat_Large = 2131623961; - // aapt resource value: 0x7f0c001b - public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131492891; + // aapt resource value: 0x7F0E001A + public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - // aapt resource value: 0x7f0c0052 - public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131492946; + // aapt resource value: 0x7F0E001B + public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - // aapt resource value: 0x7f0c0053 - public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131492947; + // aapt resource value: 0x7F0E001C + public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - // aapt resource value: 0x7f0c0054 - public const int Base_TextAppearance_AppCompat_Medium = 2131492948; + // aapt resource value: 0x7F0E001D + public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - // aapt resource value: 0x7f0c001c - public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131492892; + // aapt resource value: 0x7F0E001E + public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - // aapt resource value: 0x7f0c0055 - public const int Base_TextAppearance_AppCompat_Menu = 2131492949; + // aapt resource value: 0x7F0E001F + public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - // aapt resource value: 0x7f0c00b0 - public const int Base_TextAppearance_AppCompat_SearchResult = 2131493040; + // aapt resource value: 0x7F0E0020 + public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - // aapt resource value: 0x7f0c0056 - public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131492950; + // aapt resource value: 0x7F0E0021 + public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - // aapt resource value: 0x7f0c0057 - public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131492951; + // aapt resource value: 0x7F0E0022 + public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - // aapt resource value: 0x7f0c0058 - public const int Base_TextAppearance_AppCompat_Small = 2131492952; + // aapt resource value: 0x7F0E0023 + public const int Base_TextAppearance_AppCompat_Small = 2131623971; - // aapt resource value: 0x7f0c001d - public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131492893; + // aapt resource value: 0x7F0E0024 + public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - // aapt resource value: 0x7f0c0059 - public const int Base_TextAppearance_AppCompat_Subhead = 2131492953; + // aapt resource value: 0x7F0E0025 + public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - // aapt resource value: 0x7f0c001e - public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131492894; + // aapt resource value: 0x7F0E0026 + public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - // aapt resource value: 0x7f0c005a - public const int Base_TextAppearance_AppCompat_Title = 2131492954; + // aapt resource value: 0x7F0E0027 + public const int Base_TextAppearance_AppCompat_Title = 2131623975; - // aapt resource value: 0x7f0c001f - public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131492895; + // aapt resource value: 0x7F0E0028 + public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - // aapt resource value: 0x7f0c00b1 - public const int Base_TextAppearance_AppCompat_Tooltip = 2131493041; + // aapt resource value: 0x7F0E0029 + public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - // aapt resource value: 0x7f0c0095 - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131493013; + // aapt resource value: 0x7F0E002A + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - // aapt resource value: 0x7f0c005b - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131492955; + // aapt resource value: 0x7F0E002B + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - // aapt resource value: 0x7f0c005c - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131492956; + // aapt resource value: 0x7F0E002C + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - // aapt resource value: 0x7f0c005d - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131492957; + // aapt resource value: 0x7F0E002D + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - // aapt resource value: 0x7f0c005e - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131492958; + // aapt resource value: 0x7F0E002E + public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - // aapt resource value: 0x7f0c005f - public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131492959; + // aapt resource value: 0x7F0E002F + public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - // aapt resource value: 0x7f0c0060 - public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131492960; + // aapt resource value: 0x7F0E0030 + public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - // aapt resource value: 0x7f0c0061 - public const int Base_TextAppearance_AppCompat_Widget_Button = 2131492961; + // aapt resource value: 0x7F0E0031 + public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - // aapt resource value: 0x7f0c009c - public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131493020; + // aapt resource value: 0x7F0E0032 + public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - // aapt resource value: 0x7f0c009d - public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131493021; + // aapt resource value: 0x7F0E0033 + public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - // aapt resource value: 0x7f0c0096 - public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131493014; + // aapt resource value: 0x7F0E0034 + public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - // aapt resource value: 0x7f0c00b2 - public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131493042; + // aapt resource value: 0x7F0E0035 + public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - // aapt resource value: 0x7f0c0062 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131492962; + // aapt resource value: 0x7F0E0036 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - // aapt resource value: 0x7f0c0063 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131492963; + // aapt resource value: 0x7F0E0037 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - // aapt resource value: 0x7f0c0064 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131492964; + // aapt resource value: 0x7F0E0038 + public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - // aapt resource value: 0x7f0c0065 - public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131492965; + // aapt resource value: 0x7F0E0039 + public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - // aapt resource value: 0x7f0c0066 - public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131492966; + // aapt resource value: 0x7F0E003A + public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - // aapt resource value: 0x7f0c00b3 - public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131493043; + // aapt resource value: 0x7F0E003B + public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - // aapt resource value: 0x7f0c0067 - public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131492967; + // aapt resource value: 0x7F0E003C + public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - // aapt resource value: 0x7f0c0068 - public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131492968; + // aapt resource value: 0x7F0E003D + public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - // aapt resource value: 0x7f0c0069 - public const int Base_Theme_AppCompat = 2131492969; + // aapt resource value: 0x7F0E004C + public const int Base_ThemeOverlay_AppCompat = 2131624012; - // aapt resource value: 0x7f0c00b4 - public const int Base_Theme_AppCompat_CompactMenu = 2131493044; + // aapt resource value: 0x7F0E004D + public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - // aapt resource value: 0x7f0c0020 - public const int Base_Theme_AppCompat_Dialog = 2131492896; + // aapt resource value: 0x7F0E004E + public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - // aapt resource value: 0x7f0c0021 - public const int Base_Theme_AppCompat_Dialog_Alert = 2131492897; + // aapt resource value: 0x7F0E004F + public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - // aapt resource value: 0x7f0c00b5 - public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131493045; + // aapt resource value: 0x7F0E0050 + public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - // aapt resource value: 0x7f0c0022 - public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131492898; + // aapt resource value: 0x7F0E0051 + public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - // aapt resource value: 0x7f0c0010 - public const int Base_Theme_AppCompat_DialogWhenLarge = 2131492880; + // aapt resource value: 0x7F0E0052 + public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - // aapt resource value: 0x7f0c006a - public const int Base_Theme_AppCompat_Light = 2131492970; + // aapt resource value: 0x7F0E003E + public const int Base_Theme_AppCompat = 2131623998; - // aapt resource value: 0x7f0c00b6 - public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131493046; + // aapt resource value: 0x7F0E003F + public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - // aapt resource value: 0x7f0c0023 - public const int Base_Theme_AppCompat_Light_Dialog = 2131492899; + // aapt resource value: 0x7F0E0040 + public const int Base_Theme_AppCompat_Dialog = 2131624000; - // aapt resource value: 0x7f0c0024 - public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131492900; + // aapt resource value: 0x7F0E0044 + public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - // aapt resource value: 0x7f0c00b7 - public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131493047; + // aapt resource value: 0x7F0E0041 + public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - // aapt resource value: 0x7f0c0025 - public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131492901; + // aapt resource value: 0x7F0E0042 + public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - // aapt resource value: 0x7f0c0011 - public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131492881; + // aapt resource value: 0x7F0E0043 + public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - // aapt resource value: 0x7f0c00b8 - public const int Base_ThemeOverlay_AppCompat = 2131493048; + // aapt resource value: 0x7F0E0045 + public const int Base_Theme_AppCompat_Light = 2131624005; - // aapt resource value: 0x7f0c00b9 - public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131493049; + // aapt resource value: 0x7F0E0046 + public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - // aapt resource value: 0x7f0c00ba - public const int Base_ThemeOverlay_AppCompat_Dark = 2131493050; + // aapt resource value: 0x7F0E0047 + public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - // aapt resource value: 0x7f0c00bb - public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131493051; + // aapt resource value: 0x7F0E004B + public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - // aapt resource value: 0x7f0c0026 - public const int Base_ThemeOverlay_AppCompat_Dialog = 2131492902; + // aapt resource value: 0x7F0E0048 + public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - // aapt resource value: 0x7f0c0027 - public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131492903; + // aapt resource value: 0x7F0E0049 + public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - // aapt resource value: 0x7f0c00bc - public const int Base_ThemeOverlay_AppCompat_Light = 2131493052; + // aapt resource value: 0x7F0E004A + public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - // aapt resource value: 0x7f0c0028 - public const int Base_V11_Theme_AppCompat_Dialog = 2131492904; + // aapt resource value: 0x7F0E0055 + public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - // aapt resource value: 0x7f0c0029 - public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131492905; + // aapt resource value: 0x7F0E0053 + public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - // aapt resource value: 0x7f0c002a - public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131492906; + // aapt resource value: 0x7F0E0054 + public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - // aapt resource value: 0x7f0c0032 - public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131492914; + // aapt resource value: 0x7F0E0056 + public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - // aapt resource value: 0x7f0c0033 - public const int Base_V12_Widget_AppCompat_EditText = 2131492915; + // aapt resource value: 0x7F0E0057 + public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - // aapt resource value: 0x7f0c016f - public const int Base_V14_Widget_Design_AppBarLayout = 2131493231; + // aapt resource value: 0x7F0E0058 + public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - // aapt resource value: 0x7f0c006b - public const int Base_V21_Theme_AppCompat = 2131492971; + // aapt resource value: 0x7F0E005D + public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - // aapt resource value: 0x7f0c006c - public const int Base_V21_Theme_AppCompat_Dialog = 2131492972; + // aapt resource value: 0x7F0E0059 + public const int Base_V21_Theme_AppCompat = 2131624025; - // aapt resource value: 0x7f0c006d - public const int Base_V21_Theme_AppCompat_Light = 2131492973; + // aapt resource value: 0x7F0E005A + public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - // aapt resource value: 0x7f0c006e - public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131492974; + // aapt resource value: 0x7F0E005B + public const int Base_V21_Theme_AppCompat_Light = 2131624027; - // aapt resource value: 0x7f0c006f - public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131492975; + // aapt resource value: 0x7F0E005C + public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - // aapt resource value: 0x7f0c016b - public const int Base_V21_Widget_Design_AppBarLayout = 2131493227; + // aapt resource value: 0x7F0E005E + public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - // aapt resource value: 0x7f0c0093 - public const int Base_V22_Theme_AppCompat = 2131493011; + // aapt resource value: 0x7F0E005F + public const int Base_V22_Theme_AppCompat = 2131624031; - // aapt resource value: 0x7f0c0094 - public const int Base_V22_Theme_AppCompat_Light = 2131493012; + // aapt resource value: 0x7F0E0060 + public const int Base_V22_Theme_AppCompat_Light = 2131624032; - // aapt resource value: 0x7f0c0097 - public const int Base_V23_Theme_AppCompat = 2131493015; + // aapt resource value: 0x7F0E0061 + public const int Base_V23_Theme_AppCompat = 2131624033; - // aapt resource value: 0x7f0c0098 - public const int Base_V23_Theme_AppCompat_Light = 2131493016; + // aapt resource value: 0x7F0E0062 + public const int Base_V23_Theme_AppCompat_Light = 2131624034; - // aapt resource value: 0x7f0c00a0 - public const int Base_V26_Theme_AppCompat = 2131493024; + // aapt resource value: 0x7F0E0063 + public const int Base_V26_Theme_AppCompat = 2131624035; - // aapt resource value: 0x7f0c00a1 - public const int Base_V26_Theme_AppCompat_Light = 2131493025; + // aapt resource value: 0x7F0E0064 + public const int Base_V26_Theme_AppCompat_Light = 2131624036; - // aapt resource value: 0x7f0c00a2 - public const int Base_V26_Widget_AppCompat_Toolbar = 2131493026; + // aapt resource value: 0x7F0E0065 + public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - // aapt resource value: 0x7f0c016d - public const int Base_V26_Widget_Design_AppBarLayout = 2131493229; + // aapt resource value: 0x7F0E0066 + public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - // aapt resource value: 0x7f0c00bd - public const int Base_V7_Theme_AppCompat = 2131493053; + // aapt resource value: 0x7F0E006B + public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - // aapt resource value: 0x7f0c00be - public const int Base_V7_Theme_AppCompat_Dialog = 2131493054; + // aapt resource value: 0x7F0E0067 + public const int Base_V7_Theme_AppCompat = 2131624039; - // aapt resource value: 0x7f0c00bf - public const int Base_V7_Theme_AppCompat_Light = 2131493055; + // aapt resource value: 0x7F0E0068 + public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - // aapt resource value: 0x7f0c00c0 - public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131493056; + // aapt resource value: 0x7F0E0069 + public const int Base_V7_Theme_AppCompat_Light = 2131624041; - // aapt resource value: 0x7f0c00c1 - public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131493057; + // aapt resource value: 0x7F0E006A + public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - // aapt resource value: 0x7f0c00c2 - public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131493058; + // aapt resource value: 0x7F0E006C + public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - // aapt resource value: 0x7f0c00c3 - public const int Base_V7_Widget_AppCompat_EditText = 2131493059; + // aapt resource value: 0x7F0E006D + public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - // aapt resource value: 0x7f0c00c4 - public const int Base_V7_Widget_AppCompat_Toolbar = 2131493060; + // aapt resource value: 0x7F0E006E + public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - // aapt resource value: 0x7f0c00c5 - public const int Base_Widget_AppCompat_ActionBar = 2131493061; + // aapt resource value: 0x7F0E006F + public const int Base_Widget_AppCompat_ActionBar = 2131624047; - // aapt resource value: 0x7f0c00c6 - public const int Base_Widget_AppCompat_ActionBar_Solid = 2131493062; + // aapt resource value: 0x7F0E0070 + public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - // aapt resource value: 0x7f0c00c7 - public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131493063; + // aapt resource value: 0x7F0E0071 + public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - // aapt resource value: 0x7f0c0070 - public const int Base_Widget_AppCompat_ActionBar_TabText = 2131492976; + // aapt resource value: 0x7F0E0072 + public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - // aapt resource value: 0x7f0c0071 - public const int Base_Widget_AppCompat_ActionBar_TabView = 2131492977; + // aapt resource value: 0x7F0E0073 + public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - // aapt resource value: 0x7f0c0072 - public const int Base_Widget_AppCompat_ActionButton = 2131492978; + // aapt resource value: 0x7F0E0074 + public const int Base_Widget_AppCompat_ActionButton = 2131624052; - // aapt resource value: 0x7f0c0073 - public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131492979; + // aapt resource value: 0x7F0E0075 + public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - // aapt resource value: 0x7f0c0074 - public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131492980; + // aapt resource value: 0x7F0E0076 + public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - // aapt resource value: 0x7f0c00c8 - public const int Base_Widget_AppCompat_ActionMode = 2131493064; + // aapt resource value: 0x7F0E0077 + public const int Base_Widget_AppCompat_ActionMode = 2131624055; - // aapt resource value: 0x7f0c00c9 - public const int Base_Widget_AppCompat_ActivityChooserView = 2131493065; + // aapt resource value: 0x7F0E0078 + public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - // aapt resource value: 0x7f0c0034 - public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131492916; + // aapt resource value: 0x7F0E0079 + public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - // aapt resource value: 0x7f0c0075 - public const int Base_Widget_AppCompat_Button = 2131492981; + // aapt resource value: 0x7F0E007A + public const int Base_Widget_AppCompat_Button = 2131624058; - // aapt resource value: 0x7f0c0076 - public const int Base_Widget_AppCompat_Button_Borderless = 2131492982; + // aapt resource value: 0x7F0E0080 + public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - // aapt resource value: 0x7f0c0077 - public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131492983; + // aapt resource value: 0x7F0E0081 + public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - // aapt resource value: 0x7f0c00ca - public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131493066; + // aapt resource value: 0x7F0E007B + public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - // aapt resource value: 0x7f0c0099 - public const int Base_Widget_AppCompat_Button_Colored = 2131493017; + // aapt resource value: 0x7F0E007C + public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - // aapt resource value: 0x7f0c0078 - public const int Base_Widget_AppCompat_Button_Small = 2131492984; + // aapt resource value: 0x7F0E007D + public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - // aapt resource value: 0x7f0c0079 - public const int Base_Widget_AppCompat_ButtonBar = 2131492985; + // aapt resource value: 0x7F0E007E + public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - // aapt resource value: 0x7f0c00cb - public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131493067; + // aapt resource value: 0x7F0E007F + public const int Base_Widget_AppCompat_Button_Small = 2131624063; - // aapt resource value: 0x7f0c007a - public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131492986; + // aapt resource value: 0x7F0E0082 + public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - // aapt resource value: 0x7f0c007b - public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131492987; + // aapt resource value: 0x7F0E0083 + public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - // aapt resource value: 0x7f0c00cc - public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131493068; + // aapt resource value: 0x7F0E0084 + public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - // aapt resource value: 0x7f0c000f - public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131492879; + // aapt resource value: 0x7F0E0085 + public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - // aapt resource value: 0x7f0c00cd - public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131493069; + // aapt resource value: 0x7F0E0086 + public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - // aapt resource value: 0x7f0c007c - public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131492988; + // aapt resource value: 0x7F0E0087 + public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - // aapt resource value: 0x7f0c0035 - public const int Base_Widget_AppCompat_EditText = 2131492917; + // aapt resource value: 0x7F0E0088 + public const int Base_Widget_AppCompat_EditText = 2131624072; - // aapt resource value: 0x7f0c007d - public const int Base_Widget_AppCompat_ImageButton = 2131492989; + // aapt resource value: 0x7F0E0089 + public const int Base_Widget_AppCompat_ImageButton = 2131624073; - // aapt resource value: 0x7f0c00ce - public const int Base_Widget_AppCompat_Light_ActionBar = 2131493070; + // aapt resource value: 0x7F0E008A + public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - // aapt resource value: 0x7f0c00cf - public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131493071; + // aapt resource value: 0x7F0E008B + public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - // aapt resource value: 0x7f0c00d0 - public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131493072; + // aapt resource value: 0x7F0E008C + public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - // aapt resource value: 0x7f0c007e - public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131492990; + // aapt resource value: 0x7F0E008D + public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - // aapt resource value: 0x7f0c007f - public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131492991; + // aapt resource value: 0x7F0E008E + public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - // aapt resource value: 0x7f0c0080 - public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131492992; + // aapt resource value: 0x7F0E008F + public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - // aapt resource value: 0x7f0c0081 - public const int Base_Widget_AppCompat_Light_PopupMenu = 2131492993; + // aapt resource value: 0x7F0E0090 + public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - // aapt resource value: 0x7f0c0082 - public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131492994; + // aapt resource value: 0x7F0E0091 + public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - // aapt resource value: 0x7f0c00d1 - public const int Base_Widget_AppCompat_ListMenuView = 2131493073; + // aapt resource value: 0x7F0E0092 + public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - // aapt resource value: 0x7f0c0083 - public const int Base_Widget_AppCompat_ListPopupWindow = 2131492995; + // aapt resource value: 0x7F0E0093 + public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - // aapt resource value: 0x7f0c0084 - public const int Base_Widget_AppCompat_ListView = 2131492996; + // aapt resource value: 0x7F0E0094 + public const int Base_Widget_AppCompat_ListView = 2131624084; - // aapt resource value: 0x7f0c0085 - public const int Base_Widget_AppCompat_ListView_DropDown = 2131492997; + // aapt resource value: 0x7F0E0095 + public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - // aapt resource value: 0x7f0c0086 - public const int Base_Widget_AppCompat_ListView_Menu = 2131492998; + // aapt resource value: 0x7F0E0096 + public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - // aapt resource value: 0x7f0c0087 - public const int Base_Widget_AppCompat_PopupMenu = 2131492999; + // aapt resource value: 0x7F0E0097 + public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - // aapt resource value: 0x7f0c0088 - public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131493000; + // aapt resource value: 0x7F0E0098 + public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - // aapt resource value: 0x7f0c00d2 - public const int Base_Widget_AppCompat_PopupWindow = 2131493074; + // aapt resource value: 0x7F0E0099 + public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - // aapt resource value: 0x7f0c002b - public const int Base_Widget_AppCompat_ProgressBar = 2131492907; + // aapt resource value: 0x7F0E009A + public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - // aapt resource value: 0x7f0c002c - public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131492908; + // aapt resource value: 0x7F0E009B + public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - // aapt resource value: 0x7f0c0089 - public const int Base_Widget_AppCompat_RatingBar = 2131493001; + // aapt resource value: 0x7F0E009C + public const int Base_Widget_AppCompat_RatingBar = 2131624092; - // aapt resource value: 0x7f0c009a - public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131493018; + // aapt resource value: 0x7F0E009D + public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - // aapt resource value: 0x7f0c009b - public const int Base_Widget_AppCompat_RatingBar_Small = 2131493019; + // aapt resource value: 0x7F0E009E + public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - // aapt resource value: 0x7f0c00d3 - public const int Base_Widget_AppCompat_SearchView = 2131493075; + // aapt resource value: 0x7F0E009F + public const int Base_Widget_AppCompat_SearchView = 2131624095; - // aapt resource value: 0x7f0c00d4 - public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131493076; + // aapt resource value: 0x7F0E00A0 + public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - // aapt resource value: 0x7f0c008a - public const int Base_Widget_AppCompat_SeekBar = 2131493002; + // aapt resource value: 0x7F0E00A1 + public const int Base_Widget_AppCompat_SeekBar = 2131624097; - // aapt resource value: 0x7f0c00d5 - public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131493077; + // aapt resource value: 0x7F0E00A2 + public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - // aapt resource value: 0x7f0c008b - public const int Base_Widget_AppCompat_Spinner = 2131493003; + // aapt resource value: 0x7F0E00A3 + public const int Base_Widget_AppCompat_Spinner = 2131624099; - // aapt resource value: 0x7f0c0012 - public const int Base_Widget_AppCompat_Spinner_Underlined = 2131492882; + // aapt resource value: 0x7F0E00A4 + public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - // aapt resource value: 0x7f0c008c - public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131493004; + // aapt resource value: 0x7F0E00A5 + public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - // aapt resource value: 0x7f0c00a3 - public const int Base_Widget_AppCompat_Toolbar = 2131493027; + // aapt resource value: 0x7F0E00A6 + public const int Base_Widget_AppCompat_Toolbar = 2131624102; - // aapt resource value: 0x7f0c008d - public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131493005; + // aapt resource value: 0x7F0E00A7 + public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - // aapt resource value: 0x7f0c016c - public const int Base_Widget_Design_AppBarLayout = 2131493228; + // aapt resource value: 0x7F0E00A8 + public const int Base_Widget_Design_AppBarLayout = 2131624104; - // aapt resource value: 0x7f0c0170 - public const int Base_Widget_Design_TabLayout = 2131493232; + // aapt resource value: 0x7F0E00A9 + public const int Base_Widget_Design_TabLayout = 2131624105; - // aapt resource value: 0x7f0c000b - public const int CardView = 2131492875; + // aapt resource value: 0x7F0E00AA + public const int CardView = 2131624106; - // aapt resource value: 0x7f0c000d - public const int CardView_Dark = 2131492877; + // aapt resource value: 0x7F0E00AB + public const int CardView_Dark = 2131624107; - // aapt resource value: 0x7f0c000e - public const int CardView_Light = 2131492878; + // aapt resource value: 0x7F0E00AC + public const int CardView_Light = 2131624108; - // aapt resource value: 0x7f0c002d - public const int Platform_AppCompat = 2131492909; + // aapt resource value: 0x7F0E00AD + public const int Platform_AppCompat = 2131624109; - // aapt resource value: 0x7f0c002e - public const int Platform_AppCompat_Light = 2131492910; + // aapt resource value: 0x7F0E00AE + public const int Platform_AppCompat_Light = 2131624110; - // aapt resource value: 0x7f0c008e - public const int Platform_ThemeOverlay_AppCompat = 2131493006; + // aapt resource value: 0x7F0E00AF + public const int Platform_ThemeOverlay_AppCompat = 2131624111; - // aapt resource value: 0x7f0c008f - public const int Platform_ThemeOverlay_AppCompat_Dark = 2131493007; + // aapt resource value: 0x7F0E00B0 + public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - // aapt resource value: 0x7f0c0090 - public const int Platform_ThemeOverlay_AppCompat_Light = 2131493008; + // aapt resource value: 0x7F0E00B1 + public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - // aapt resource value: 0x7f0c002f - public const int Platform_V11_AppCompat = 2131492911; + // aapt resource value: 0x7F0E00B2 + public const int Platform_V11_AppCompat = 2131624114; - // aapt resource value: 0x7f0c0030 - public const int Platform_V11_AppCompat_Light = 2131492912; + // aapt resource value: 0x7F0E00B3 + public const int Platform_V11_AppCompat_Light = 2131624115; - // aapt resource value: 0x7f0c0037 - public const int Platform_V14_AppCompat = 2131492919; + // aapt resource value: 0x7F0E00B4 + public const int Platform_V14_AppCompat = 2131624116; - // aapt resource value: 0x7f0c0038 - public const int Platform_V14_AppCompat_Light = 2131492920; + // aapt resource value: 0x7F0E00B5 + public const int Platform_V14_AppCompat_Light = 2131624117; - // aapt resource value: 0x7f0c0091 - public const int Platform_V21_AppCompat = 2131493009; + // aapt resource value: 0x7F0E00B6 + public const int Platform_V21_AppCompat = 2131624118; - // aapt resource value: 0x7f0c0092 - public const int Platform_V21_AppCompat_Light = 2131493010; + // aapt resource value: 0x7F0E00B7 + public const int Platform_V21_AppCompat_Light = 2131624119; - // aapt resource value: 0x7f0c009e - public const int Platform_V25_AppCompat = 2131493022; + // aapt resource value: 0x7F0E00B8 + public const int Platform_V25_AppCompat = 2131624120; - // aapt resource value: 0x7f0c009f - public const int Platform_V25_AppCompat_Light = 2131493023; + // aapt resource value: 0x7F0E00B9 + public const int Platform_V25_AppCompat_Light = 2131624121; - // aapt resource value: 0x7f0c0031 - public const int Platform_Widget_AppCompat_Spinner = 2131492913; + // aapt resource value: 0x7F0E00BA + public const int Platform_Widget_AppCompat_Spinner = 2131624122; - // aapt resource value: 0x7f0c003a - public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131492922; + // aapt resource value: 0x7F0E00BB + public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - // aapt resource value: 0x7f0c003b - public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131492923; + // aapt resource value: 0x7F0E00BC + public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - // aapt resource value: 0x7f0c003c - public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131492924; + // aapt resource value: 0x7F0E00BD + public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - // aapt resource value: 0x7f0c003d - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131492925; + // aapt resource value: 0x7F0E00BE + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - // aapt resource value: 0x7f0c003e - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131492926; + // aapt resource value: 0x7F0E00BF + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - // aapt resource value: 0x7f0c003f - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131492927; + // aapt resource value: 0x7F0E00C0 + public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - // aapt resource value: 0x7f0c0040 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131492928; + // aapt resource value: 0x7F0E00C6 + public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - // aapt resource value: 0x7f0c0041 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131492929; + // aapt resource value: 0x7F0E00C1 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - // aapt resource value: 0x7f0c0042 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131492930; + // aapt resource value: 0x7F0E00C2 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - // aapt resource value: 0x7f0c0043 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131492931; + // aapt resource value: 0x7F0E00C3 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - // aapt resource value: 0x7f0c0044 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131492932; + // aapt resource value: 0x7F0E00C4 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - // aapt resource value: 0x7f0c0045 - public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131492933; + // aapt resource value: 0x7F0E00C5 + public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - // aapt resource value: 0x7f0c0046 - public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131492934; + // aapt resource value: 0x7F0E00C7 + public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - // aapt resource value: 0x7f0c0047 - public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131492935; + // aapt resource value: 0x7F0E00C8 + public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - // aapt resource value: 0x7f0c00d6 - public const int TextAppearance_AppCompat = 2131493078; + // aapt resource value: 0x7F0E00C9 + public const int TextAppearance_AppCompat = 2131624137; - // aapt resource value: 0x7f0c00d7 - public const int TextAppearance_AppCompat_Body1 = 2131493079; + // aapt resource value: 0x7F0E00CA + public const int TextAppearance_AppCompat_Body1 = 2131624138; - // aapt resource value: 0x7f0c00d8 - public const int TextAppearance_AppCompat_Body2 = 2131493080; + // aapt resource value: 0x7F0E00CB + public const int TextAppearance_AppCompat_Body2 = 2131624139; - // aapt resource value: 0x7f0c00d9 - public const int TextAppearance_AppCompat_Button = 2131493081; + // aapt resource value: 0x7F0E00CC + public const int TextAppearance_AppCompat_Button = 2131624140; - // aapt resource value: 0x7f0c00da - public const int TextAppearance_AppCompat_Caption = 2131493082; + // aapt resource value: 0x7F0E00CD + public const int TextAppearance_AppCompat_Caption = 2131624141; - // aapt resource value: 0x7f0c00db - public const int TextAppearance_AppCompat_Display1 = 2131493083; + // aapt resource value: 0x7F0E00CE + public const int TextAppearance_AppCompat_Display1 = 2131624142; - // aapt resource value: 0x7f0c00dc - public const int TextAppearance_AppCompat_Display2 = 2131493084; + // aapt resource value: 0x7F0E00CF + public const int TextAppearance_AppCompat_Display2 = 2131624143; - // aapt resource value: 0x7f0c00dd - public const int TextAppearance_AppCompat_Display3 = 2131493085; + // aapt resource value: 0x7F0E00D0 + public const int TextAppearance_AppCompat_Display3 = 2131624144; - // aapt resource value: 0x7f0c00de - public const int TextAppearance_AppCompat_Display4 = 2131493086; + // aapt resource value: 0x7F0E00D1 + public const int TextAppearance_AppCompat_Display4 = 2131624145; - // aapt resource value: 0x7f0c00df - public const int TextAppearance_AppCompat_Headline = 2131493087; + // aapt resource value: 0x7F0E00D2 + public const int TextAppearance_AppCompat_Headline = 2131624146; - // aapt resource value: 0x7f0c00e0 - public const int TextAppearance_AppCompat_Inverse = 2131493088; + // aapt resource value: 0x7F0E00D3 + public const int TextAppearance_AppCompat_Inverse = 2131624147; - // aapt resource value: 0x7f0c00e1 - public const int TextAppearance_AppCompat_Large = 2131493089; + // aapt resource value: 0x7F0E00D4 + public const int TextAppearance_AppCompat_Large = 2131624148; - // aapt resource value: 0x7f0c00e2 - public const int TextAppearance_AppCompat_Large_Inverse = 2131493090; + // aapt resource value: 0x7F0E00D5 + public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - // aapt resource value: 0x7f0c00e3 - public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131493091; + // aapt resource value: 0x7F0E00D6 + public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - // aapt resource value: 0x7f0c00e4 - public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131493092; + // aapt resource value: 0x7F0E00D7 + public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - // aapt resource value: 0x7f0c00e5 - public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131493093; + // aapt resource value: 0x7F0E00D8 + public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - // aapt resource value: 0x7f0c00e6 - public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131493094; + // aapt resource value: 0x7F0E00D9 + public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - // aapt resource value: 0x7f0c00e7 - public const int TextAppearance_AppCompat_Medium = 2131493095; + // aapt resource value: 0x7F0E00DA + public const int TextAppearance_AppCompat_Medium = 2131624154; - // aapt resource value: 0x7f0c00e8 - public const int TextAppearance_AppCompat_Medium_Inverse = 2131493096; + // aapt resource value: 0x7F0E00DB + public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - // aapt resource value: 0x7f0c00e9 - public const int TextAppearance_AppCompat_Menu = 2131493097; + // aapt resource value: 0x7F0E00DC + public const int TextAppearance_AppCompat_Menu = 2131624156; - // aapt resource value: 0x7f0c00ea - public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131493098; + // aapt resource value: 0x7F0E00DD + public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - // aapt resource value: 0x7f0c00eb - public const int TextAppearance_AppCompat_SearchResult_Title = 2131493099; + // aapt resource value: 0x7F0E00DE + public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - // aapt resource value: 0x7f0c00ec - public const int TextAppearance_AppCompat_Small = 2131493100; + // aapt resource value: 0x7F0E00DF + public const int TextAppearance_AppCompat_Small = 2131624159; - // aapt resource value: 0x7f0c00ed - public const int TextAppearance_AppCompat_Small_Inverse = 2131493101; + // aapt resource value: 0x7F0E00E0 + public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - // aapt resource value: 0x7f0c00ee - public const int TextAppearance_AppCompat_Subhead = 2131493102; + // aapt resource value: 0x7F0E00E1 + public const int TextAppearance_AppCompat_Subhead = 2131624161; - // aapt resource value: 0x7f0c00ef - public const int TextAppearance_AppCompat_Subhead_Inverse = 2131493103; + // aapt resource value: 0x7F0E00E2 + public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - // aapt resource value: 0x7f0c00f0 - public const int TextAppearance_AppCompat_Title = 2131493104; + // aapt resource value: 0x7F0E00E3 + public const int TextAppearance_AppCompat_Title = 2131624163; - // aapt resource value: 0x7f0c00f1 - public const int TextAppearance_AppCompat_Title_Inverse = 2131493105; + // aapt resource value: 0x7F0E00E4 + public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - // aapt resource value: 0x7f0c0039 - public const int TextAppearance_AppCompat_Tooltip = 2131492921; + // aapt resource value: 0x7F0E00E5 + public const int TextAppearance_AppCompat_Tooltip = 2131624165; - // aapt resource value: 0x7f0c00f2 - public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131493106; + // aapt resource value: 0x7F0E00E6 + public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - // aapt resource value: 0x7f0c00f3 - public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131493107; + // aapt resource value: 0x7F0E00E7 + public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - // aapt resource value: 0x7f0c00f4 - public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131493108; + // aapt resource value: 0x7F0E00E8 + public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - // aapt resource value: 0x7f0c00f5 - public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131493109; + // aapt resource value: 0x7F0E00E9 + public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - // aapt resource value: 0x7f0c00f6 - public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131493110; + // aapt resource value: 0x7F0E00EA + public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - // aapt resource value: 0x7f0c00f7 - public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131493111; + // aapt resource value: 0x7F0E00EB + public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - // aapt resource value: 0x7f0c00f8 - public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131493112; + // aapt resource value: 0x7F0E00EC + public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - // aapt resource value: 0x7f0c00f9 - public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131493113; + // aapt resource value: 0x7F0E00ED + public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - // aapt resource value: 0x7f0c00fa - public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131493114; + // aapt resource value: 0x7F0E00EE + public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - // aapt resource value: 0x7f0c00fb - public const int TextAppearance_AppCompat_Widget_Button = 2131493115; + // aapt resource value: 0x7F0E00EF + public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - // aapt resource value: 0x7f0c00fc - public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131493116; + // aapt resource value: 0x7F0E00F0 + public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - // aapt resource value: 0x7f0c00fd - public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131493117; + // aapt resource value: 0x7F0E00F1 + public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - // aapt resource value: 0x7f0c00fe - public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131493118; + // aapt resource value: 0x7F0E00F2 + public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - // aapt resource value: 0x7f0c00ff - public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131493119; + // aapt resource value: 0x7F0E00F3 + public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - // aapt resource value: 0x7f0c0100 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131493120; + // aapt resource value: 0x7F0E00F4 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - // aapt resource value: 0x7f0c0101 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131493121; + // aapt resource value: 0x7F0E00F5 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - // aapt resource value: 0x7f0c0102 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131493122; + // aapt resource value: 0x7F0E00F6 + public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - // aapt resource value: 0x7f0c0103 - public const int TextAppearance_AppCompat_Widget_Switch = 2131493123; + // aapt resource value: 0x7F0E00F7 + public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - // aapt resource value: 0x7f0c0104 - public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131493124; + // aapt resource value: 0x7F0E00F8 + public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - // aapt resource value: 0x7f0c0188 - public const int TextAppearance_Compat_Notification = 2131493256; + // aapt resource value: 0x7F0E00F9 + public const int TextAppearance_Compat_Notification = 2131624185; - // aapt resource value: 0x7f0c0189 - public const int TextAppearance_Compat_Notification_Info = 2131493257; + // aapt resource value: 0x7F0E00FA + public const int TextAppearance_Compat_Notification_Info = 2131624186; - // aapt resource value: 0x7f0c0165 - public const int TextAppearance_Compat_Notification_Info_Media = 2131493221; + // aapt resource value: 0x7F0E00FB + public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - // aapt resource value: 0x7f0c018e - public const int TextAppearance_Compat_Notification_Line2 = 2131493262; + // aapt resource value: 0x7F0E00FC + public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - // aapt resource value: 0x7f0c0169 - public const int TextAppearance_Compat_Notification_Line2_Media = 2131493225; + // aapt resource value: 0x7F0E00FD + public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - // aapt resource value: 0x7f0c0166 - public const int TextAppearance_Compat_Notification_Media = 2131493222; + // aapt resource value: 0x7F0E00FE + public const int TextAppearance_Compat_Notification_Media = 2131624190; - // aapt resource value: 0x7f0c018a - public const int TextAppearance_Compat_Notification_Time = 2131493258; + // aapt resource value: 0x7F0E00FF + public const int TextAppearance_Compat_Notification_Time = 2131624191; - // aapt resource value: 0x7f0c0167 - public const int TextAppearance_Compat_Notification_Time_Media = 2131493223; + // aapt resource value: 0x7F0E0100 + public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - // aapt resource value: 0x7f0c018b - public const int TextAppearance_Compat_Notification_Title = 2131493259; + // aapt resource value: 0x7F0E0101 + public const int TextAppearance_Compat_Notification_Title = 2131624193; - // aapt resource value: 0x7f0c0168 - public const int TextAppearance_Compat_Notification_Title_Media = 2131493224; + // aapt resource value: 0x7F0E0102 + public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - // aapt resource value: 0x7f0c0171 - public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131493233; + // aapt resource value: 0x7F0E0103 + public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - // aapt resource value: 0x7f0c0172 - public const int TextAppearance_Design_Counter = 2131493234; + // aapt resource value: 0x7F0E0104 + public const int TextAppearance_Design_Counter = 2131624196; - // aapt resource value: 0x7f0c0173 - public const int TextAppearance_Design_Counter_Overflow = 2131493235; + // aapt resource value: 0x7F0E0105 + public const int TextAppearance_Design_Counter_Overflow = 2131624197; - // aapt resource value: 0x7f0c0174 - public const int TextAppearance_Design_Error = 2131493236; + // aapt resource value: 0x7F0E0106 + public const int TextAppearance_Design_Error = 2131624198; - // aapt resource value: 0x7f0c0175 - public const int TextAppearance_Design_Hint = 2131493237; + // aapt resource value: 0x7F0E0107 + public const int TextAppearance_Design_Hint = 2131624199; - // aapt resource value: 0x7f0c0176 - public const int TextAppearance_Design_Snackbar_Message = 2131493238; + // aapt resource value: 0x7F0E0108 + public const int TextAppearance_Design_Snackbar_Message = 2131624200; - // aapt resource value: 0x7f0c0177 - public const int TextAppearance_Design_Tab = 2131493239; + // aapt resource value: 0x7F0E0109 + public const int TextAppearance_Design_Tab = 2131624201; - // aapt resource value: 0x7f0c0000 - public const int TextAppearance_MediaRouter_PrimaryText = 2131492864; + // aapt resource value: 0x7F0E010A + public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - // aapt resource value: 0x7f0c0001 - public const int TextAppearance_MediaRouter_SecondaryText = 2131492865; + // aapt resource value: 0x7F0E010B + public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - // aapt resource value: 0x7f0c0002 - public const int TextAppearance_MediaRouter_Title = 2131492866; + // aapt resource value: 0x7F0E010C + public const int TextAppearance_MediaRouter_Title = 2131624204; - // aapt resource value: 0x7f0c0105 - public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131493125; + // aapt resource value: 0x7F0E010D + public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - // aapt resource value: 0x7f0c0106 - public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131493126; + // aapt resource value: 0x7F0E010E + public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - // aapt resource value: 0x7f0c0107 - public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131493127; + // aapt resource value: 0x7F0E010F + public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - // aapt resource value: 0x7f0c0108 - public const int Theme_AppCompat = 2131493128; + // aapt resource value: 0x7F0E012F + public const int ThemeOverlay_AppCompat = 2131624239; - // aapt resource value: 0x7f0c0109 - public const int Theme_AppCompat_CompactMenu = 2131493129; + // aapt resource value: 0x7F0E0130 + public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - // aapt resource value: 0x7f0c0013 - public const int Theme_AppCompat_DayNight = 2131492883; + // aapt resource value: 0x7F0E0131 + public const int ThemeOverlay_AppCompat_Dark = 2131624241; - // aapt resource value: 0x7f0c0014 - public const int Theme_AppCompat_DayNight_DarkActionBar = 2131492884; + // aapt resource value: 0x7F0E0132 + public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - // aapt resource value: 0x7f0c0015 - public const int Theme_AppCompat_DayNight_Dialog = 2131492885; + // aapt resource value: 0x7F0E0133 + public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - // aapt resource value: 0x7f0c0016 - public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131492886; + // aapt resource value: 0x7F0E0134 + public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - // aapt resource value: 0x7f0c0017 - public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131492887; + // aapt resource value: 0x7F0E0135 + public const int ThemeOverlay_AppCompat_Light = 2131624245; - // aapt resource value: 0x7f0c0018 - public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131492888; + // aapt resource value: 0x7F0E0136 + public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - // aapt resource value: 0x7f0c0019 - public const int Theme_AppCompat_DayNight_NoActionBar = 2131492889; + // aapt resource value: 0x7F0E0137 + public const int ThemeOverlay_MediaRouter_Light = 2131624247; - // aapt resource value: 0x7f0c010a - public const int Theme_AppCompat_Dialog = 2131493130; + // aapt resource value: 0x7F0E0110 + public const int Theme_AppCompat = 2131624208; - // aapt resource value: 0x7f0c010b - public const int Theme_AppCompat_Dialog_Alert = 2131493131; + // aapt resource value: 0x7F0E0111 + public const int Theme_AppCompat_CompactMenu = 2131624209; - // aapt resource value: 0x7f0c010c - public const int Theme_AppCompat_Dialog_MinWidth = 2131493132; + // aapt resource value: 0x7F0E0112 + public const int Theme_AppCompat_DayNight = 2131624210; - // aapt resource value: 0x7f0c010d - public const int Theme_AppCompat_DialogWhenLarge = 2131493133; + // aapt resource value: 0x7F0E0113 + public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - // aapt resource value: 0x7f0c010e - public const int Theme_AppCompat_Light = 2131493134; + // aapt resource value: 0x7F0E0114 + public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - // aapt resource value: 0x7f0c010f - public const int Theme_AppCompat_Light_DarkActionBar = 2131493135; + // aapt resource value: 0x7F0E0117 + public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - // aapt resource value: 0x7f0c0110 - public const int Theme_AppCompat_Light_Dialog = 2131493136; + // aapt resource value: 0x7F0E0115 + public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - // aapt resource value: 0x7f0c0111 - public const int Theme_AppCompat_Light_Dialog_Alert = 2131493137; + // aapt resource value: 0x7F0E0116 + public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - // aapt resource value: 0x7f0c0112 - public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131493138; + // aapt resource value: 0x7F0E0118 + public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - // aapt resource value: 0x7f0c0113 - public const int Theme_AppCompat_Light_DialogWhenLarge = 2131493139; + // aapt resource value: 0x7F0E0119 + public const int Theme_AppCompat_Dialog = 2131624217; - // aapt resource value: 0x7f0c0114 - public const int Theme_AppCompat_Light_NoActionBar = 2131493140; + // aapt resource value: 0x7F0E011C + public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - // aapt resource value: 0x7f0c0115 - public const int Theme_AppCompat_NoActionBar = 2131493141; + // aapt resource value: 0x7F0E011A + public const int Theme_AppCompat_Dialog_Alert = 2131624218; - // aapt resource value: 0x7f0c0178 - public const int Theme_Design = 2131493240; + // aapt resource value: 0x7F0E011B + public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - // aapt resource value: 0x7f0c0179 - public const int Theme_Design_BottomSheetDialog = 2131493241; + // aapt resource value: 0x7F0E011D + public const int Theme_AppCompat_Light = 2131624221; - // aapt resource value: 0x7f0c017a - public const int Theme_Design_Light = 2131493242; + // aapt resource value: 0x7F0E011E + public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - // aapt resource value: 0x7f0c017b - public const int Theme_Design_Light_BottomSheetDialog = 2131493243; + // aapt resource value: 0x7F0E011F + public const int Theme_AppCompat_Light_Dialog = 2131624223; - // aapt resource value: 0x7f0c017c - public const int Theme_Design_Light_NoActionBar = 2131493244; + // aapt resource value: 0x7F0E0122 + public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - // aapt resource value: 0x7f0c017d - public const int Theme_Design_NoActionBar = 2131493245; + // aapt resource value: 0x7F0E0120 + public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - // aapt resource value: 0x7f0c0003 - public const int Theme_MediaRouter = 2131492867; + // aapt resource value: 0x7F0E0121 + public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - // aapt resource value: 0x7f0c0004 - public const int Theme_MediaRouter_Light = 2131492868; + // aapt resource value: 0x7F0E0123 + public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - // aapt resource value: 0x7f0c0005 - public const int Theme_MediaRouter_Light_DarkControlPanel = 2131492869; + // aapt resource value: 0x7F0E0124 + public const int Theme_AppCompat_NoActionBar = 2131624228; - // aapt resource value: 0x7f0c0006 - public const int Theme_MediaRouter_LightControlPanel = 2131492870; + // aapt resource value: 0x7F0E0125 + public const int Theme_Design = 2131624229; - // aapt resource value: 0x7f0c0116 - public const int ThemeOverlay_AppCompat = 2131493142; + // aapt resource value: 0x7F0E0126 + public const int Theme_Design_BottomSheetDialog = 2131624230; - // aapt resource value: 0x7f0c0117 - public const int ThemeOverlay_AppCompat_ActionBar = 2131493143; + // aapt resource value: 0x7F0E0127 + public const int Theme_Design_Light = 2131624231; - // aapt resource value: 0x7f0c0118 - public const int ThemeOverlay_AppCompat_Dark = 2131493144; + // aapt resource value: 0x7F0E0128 + public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - // aapt resource value: 0x7f0c0119 - public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131493145; + // aapt resource value: 0x7F0E0129 + public const int Theme_Design_Light_NoActionBar = 2131624233; - // aapt resource value: 0x7f0c011a - public const int ThemeOverlay_AppCompat_Dialog = 2131493146; + // aapt resource value: 0x7F0E012A + public const int Theme_Design_NoActionBar = 2131624234; - // aapt resource value: 0x7f0c011b - public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131493147; + // aapt resource value: 0x7F0E012B + public const int Theme_MediaRouter = 2131624235; - // aapt resource value: 0x7f0c011c - public const int ThemeOverlay_AppCompat_Light = 2131493148; + // aapt resource value: 0x7F0E012C + public const int Theme_MediaRouter_Light = 2131624236; - // aapt resource value: 0x7f0c0007 - public const int ThemeOverlay_MediaRouter_Dark = 2131492871; + // aapt resource value: 0x7F0E012E + public const int Theme_MediaRouter_LightControlPanel = 2131624238; - // aapt resource value: 0x7f0c0008 - public const int ThemeOverlay_MediaRouter_Light = 2131492872; + // aapt resource value: 0x7F0E012D + public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - // aapt resource value: 0x7f0c011d - public const int Widget_AppCompat_ActionBar = 2131493149; + // aapt resource value: 0x7F0E0138 + public const int Widget_AppCompat_ActionBar = 2131624248; - // aapt resource value: 0x7f0c011e - public const int Widget_AppCompat_ActionBar_Solid = 2131493150; + // aapt resource value: 0x7F0E0139 + public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - // aapt resource value: 0x7f0c011f - public const int Widget_AppCompat_ActionBar_TabBar = 2131493151; + // aapt resource value: 0x7F0E013A + public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - // aapt resource value: 0x7f0c0120 - public const int Widget_AppCompat_ActionBar_TabText = 2131493152; + // aapt resource value: 0x7F0E013B + public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - // aapt resource value: 0x7f0c0121 - public const int Widget_AppCompat_ActionBar_TabView = 2131493153; + // aapt resource value: 0x7F0E013C + public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - // aapt resource value: 0x7f0c0122 - public const int Widget_AppCompat_ActionButton = 2131493154; + // aapt resource value: 0x7F0E013D + public const int Widget_AppCompat_ActionButton = 2131624253; - // aapt resource value: 0x7f0c0123 - public const int Widget_AppCompat_ActionButton_CloseMode = 2131493155; + // aapt resource value: 0x7F0E013E + public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - // aapt resource value: 0x7f0c0124 - public const int Widget_AppCompat_ActionButton_Overflow = 2131493156; + // aapt resource value: 0x7F0E013F + public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - // aapt resource value: 0x7f0c0125 - public const int Widget_AppCompat_ActionMode = 2131493157; + // aapt resource value: 0x7F0E0140 + public const int Widget_AppCompat_ActionMode = 2131624256; - // aapt resource value: 0x7f0c0126 - public const int Widget_AppCompat_ActivityChooserView = 2131493158; + // aapt resource value: 0x7F0E0141 + public const int Widget_AppCompat_ActivityChooserView = 2131624257; - // aapt resource value: 0x7f0c0127 - public const int Widget_AppCompat_AutoCompleteTextView = 2131493159; + // aapt resource value: 0x7F0E0142 + public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - // aapt resource value: 0x7f0c0128 - public const int Widget_AppCompat_Button = 2131493160; + // aapt resource value: 0x7F0E0143 + public const int Widget_AppCompat_Button = 2131624259; - // aapt resource value: 0x7f0c0129 - public const int Widget_AppCompat_Button_Borderless = 2131493161; + // aapt resource value: 0x7F0E0149 + public const int Widget_AppCompat_ButtonBar = 2131624265; - // aapt resource value: 0x7f0c012a - public const int Widget_AppCompat_Button_Borderless_Colored = 2131493162; + // aapt resource value: 0x7F0E014A + public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - // aapt resource value: 0x7f0c012b - public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131493163; + // aapt resource value: 0x7F0E0144 + public const int Widget_AppCompat_Button_Borderless = 2131624260; - // aapt resource value: 0x7f0c012c - public const int Widget_AppCompat_Button_Colored = 2131493164; + // aapt resource value: 0x7F0E0145 + public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - // aapt resource value: 0x7f0c012d - public const int Widget_AppCompat_Button_Small = 2131493165; + // aapt resource value: 0x7F0E0146 + public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - // aapt resource value: 0x7f0c012e - public const int Widget_AppCompat_ButtonBar = 2131493166; + // aapt resource value: 0x7F0E0147 + public const int Widget_AppCompat_Button_Colored = 2131624263; - // aapt resource value: 0x7f0c012f - public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131493167; + // aapt resource value: 0x7F0E0148 + public const int Widget_AppCompat_Button_Small = 2131624264; - // aapt resource value: 0x7f0c0130 - public const int Widget_AppCompat_CompoundButton_CheckBox = 2131493168; + // aapt resource value: 0x7F0E014B + public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - // aapt resource value: 0x7f0c0131 - public const int Widget_AppCompat_CompoundButton_RadioButton = 2131493169; + // aapt resource value: 0x7F0E014C + public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - // aapt resource value: 0x7f0c0132 - public const int Widget_AppCompat_CompoundButton_Switch = 2131493170; + // aapt resource value: 0x7F0E014D + public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - // aapt resource value: 0x7f0c0133 - public const int Widget_AppCompat_DrawerArrowToggle = 2131493171; + // aapt resource value: 0x7F0E014E + public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - // aapt resource value: 0x7f0c0134 - public const int Widget_AppCompat_DropDownItem_Spinner = 2131493172; + // aapt resource value: 0x7F0E014F + public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - // aapt resource value: 0x7f0c0135 - public const int Widget_AppCompat_EditText = 2131493173; + // aapt resource value: 0x7F0E0150 + public const int Widget_AppCompat_EditText = 2131624272; - // aapt resource value: 0x7f0c0136 - public const int Widget_AppCompat_ImageButton = 2131493174; + // aapt resource value: 0x7F0E0151 + public const int Widget_AppCompat_ImageButton = 2131624273; - // aapt resource value: 0x7f0c0137 - public const int Widget_AppCompat_Light_ActionBar = 2131493175; + // aapt resource value: 0x7F0E0152 + public const int Widget_AppCompat_Light_ActionBar = 2131624274; - // aapt resource value: 0x7f0c0138 - public const int Widget_AppCompat_Light_ActionBar_Solid = 2131493176; + // aapt resource value: 0x7F0E0153 + public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - // aapt resource value: 0x7f0c0139 - public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131493177; + // aapt resource value: 0x7F0E0154 + public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - // aapt resource value: 0x7f0c013a - public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131493178; + // aapt resource value: 0x7F0E0155 + public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - // aapt resource value: 0x7f0c013b - public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131493179; + // aapt resource value: 0x7F0E0156 + public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - // aapt resource value: 0x7f0c013c - public const int Widget_AppCompat_Light_ActionBar_TabText = 2131493180; + // aapt resource value: 0x7F0E0157 + public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - // aapt resource value: 0x7f0c013d - public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131493181; + // aapt resource value: 0x7F0E0158 + public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - // aapt resource value: 0x7f0c013e - public const int Widget_AppCompat_Light_ActionBar_TabView = 2131493182; + // aapt resource value: 0x7F0E0159 + public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - // aapt resource value: 0x7f0c013f - public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131493183; + // aapt resource value: 0x7F0E015A + public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - // aapt resource value: 0x7f0c0140 - public const int Widget_AppCompat_Light_ActionButton = 2131493184; + // aapt resource value: 0x7F0E015B + public const int Widget_AppCompat_Light_ActionButton = 2131624283; - // aapt resource value: 0x7f0c0141 - public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131493185; + // aapt resource value: 0x7F0E015C + public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - // aapt resource value: 0x7f0c0142 - public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131493186; + // aapt resource value: 0x7F0E015D + public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - // aapt resource value: 0x7f0c0143 - public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131493187; + // aapt resource value: 0x7F0E015E + public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - // aapt resource value: 0x7f0c0144 - public const int Widget_AppCompat_Light_ActivityChooserView = 2131493188; + // aapt resource value: 0x7F0E015F + public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - // aapt resource value: 0x7f0c0145 - public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131493189; + // aapt resource value: 0x7F0E0160 + public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - // aapt resource value: 0x7f0c0146 - public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131493190; + // aapt resource value: 0x7F0E0161 + public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - // aapt resource value: 0x7f0c0147 - public const int Widget_AppCompat_Light_ListPopupWindow = 2131493191; + // aapt resource value: 0x7F0E0162 + public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - // aapt resource value: 0x7f0c0148 - public const int Widget_AppCompat_Light_ListView_DropDown = 2131493192; + // aapt resource value: 0x7F0E0163 + public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - // aapt resource value: 0x7f0c0149 - public const int Widget_AppCompat_Light_PopupMenu = 2131493193; + // aapt resource value: 0x7F0E0164 + public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - // aapt resource value: 0x7f0c014a - public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131493194; + // aapt resource value: 0x7F0E0165 + public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - // aapt resource value: 0x7f0c014b - public const int Widget_AppCompat_Light_SearchView = 2131493195; + // aapt resource value: 0x7F0E0166 + public const int Widget_AppCompat_Light_SearchView = 2131624294; - // aapt resource value: 0x7f0c014c - public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131493196; + // aapt resource value: 0x7F0E0167 + public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - // aapt resource value: 0x7f0c014d - public const int Widget_AppCompat_ListMenuView = 2131493197; + // aapt resource value: 0x7F0E0168 + public const int Widget_AppCompat_ListMenuView = 2131624296; - // aapt resource value: 0x7f0c014e - public const int Widget_AppCompat_ListPopupWindow = 2131493198; + // aapt resource value: 0x7F0E0169 + public const int Widget_AppCompat_ListPopupWindow = 2131624297; - // aapt resource value: 0x7f0c014f - public const int Widget_AppCompat_ListView = 2131493199; + // aapt resource value: 0x7F0E016A + public const int Widget_AppCompat_ListView = 2131624298; - // aapt resource value: 0x7f0c0150 - public const int Widget_AppCompat_ListView_DropDown = 2131493200; + // aapt resource value: 0x7F0E016B + public const int Widget_AppCompat_ListView_DropDown = 2131624299; - // aapt resource value: 0x7f0c0151 - public const int Widget_AppCompat_ListView_Menu = 2131493201; + // aapt resource value: 0x7F0E016C + public const int Widget_AppCompat_ListView_Menu = 2131624300; - // aapt resource value: 0x7f0c0152 - public const int Widget_AppCompat_PopupMenu = 2131493202; + // aapt resource value: 0x7F0E016D + public const int Widget_AppCompat_PopupMenu = 2131624301; - // aapt resource value: 0x7f0c0153 - public const int Widget_AppCompat_PopupMenu_Overflow = 2131493203; + // aapt resource value: 0x7F0E016E + public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - // aapt resource value: 0x7f0c0154 - public const int Widget_AppCompat_PopupWindow = 2131493204; + // aapt resource value: 0x7F0E016F + public const int Widget_AppCompat_PopupWindow = 2131624303; - // aapt resource value: 0x7f0c0155 - public const int Widget_AppCompat_ProgressBar = 2131493205; + // aapt resource value: 0x7F0E0170 + public const int Widget_AppCompat_ProgressBar = 2131624304; - // aapt resource value: 0x7f0c0156 - public const int Widget_AppCompat_ProgressBar_Horizontal = 2131493206; + // aapt resource value: 0x7F0E0171 + public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - // aapt resource value: 0x7f0c0157 - public const int Widget_AppCompat_RatingBar = 2131493207; + // aapt resource value: 0x7F0E0172 + public const int Widget_AppCompat_RatingBar = 2131624306; - // aapt resource value: 0x7f0c0158 - public const int Widget_AppCompat_RatingBar_Indicator = 2131493208; + // aapt resource value: 0x7F0E0173 + public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - // aapt resource value: 0x7f0c0159 - public const int Widget_AppCompat_RatingBar_Small = 2131493209; + // aapt resource value: 0x7F0E0174 + public const int Widget_AppCompat_RatingBar_Small = 2131624308; - // aapt resource value: 0x7f0c015a - public const int Widget_AppCompat_SearchView = 2131493210; + // aapt resource value: 0x7F0E0175 + public const int Widget_AppCompat_SearchView = 2131624309; - // aapt resource value: 0x7f0c015b - public const int Widget_AppCompat_SearchView_ActionBar = 2131493211; + // aapt resource value: 0x7F0E0176 + public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - // aapt resource value: 0x7f0c015c - public const int Widget_AppCompat_SeekBar = 2131493212; + // aapt resource value: 0x7F0E0177 + public const int Widget_AppCompat_SeekBar = 2131624311; - // aapt resource value: 0x7f0c015d - public const int Widget_AppCompat_SeekBar_Discrete = 2131493213; + // aapt resource value: 0x7F0E0178 + public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - // aapt resource value: 0x7f0c015e - public const int Widget_AppCompat_Spinner = 2131493214; + // aapt resource value: 0x7F0E0179 + public const int Widget_AppCompat_Spinner = 2131624313; - // aapt resource value: 0x7f0c015f - public const int Widget_AppCompat_Spinner_DropDown = 2131493215; + // aapt resource value: 0x7F0E017A + public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - // aapt resource value: 0x7f0c0160 - public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131493216; + // aapt resource value: 0x7F0E017B + public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - // aapt resource value: 0x7f0c0161 - public const int Widget_AppCompat_Spinner_Underlined = 2131493217; + // aapt resource value: 0x7F0E017C + public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - // aapt resource value: 0x7f0c0162 - public const int Widget_AppCompat_TextView_SpinnerItem = 2131493218; + // aapt resource value: 0x7F0E017D + public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - // aapt resource value: 0x7f0c0163 - public const int Widget_AppCompat_Toolbar = 2131493219; + // aapt resource value: 0x7F0E017E + public const int Widget_AppCompat_Toolbar = 2131624318; - // aapt resource value: 0x7f0c0164 - public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131493220; + // aapt resource value: 0x7F0E017F + public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - // aapt resource value: 0x7f0c018c - public const int Widget_Compat_NotificationActionContainer = 2131493260; + // aapt resource value: 0x7F0E0180 + public const int Widget_Compat_NotificationActionContainer = 2131624320; - // aapt resource value: 0x7f0c018d - public const int Widget_Compat_NotificationActionText = 2131493261; + // aapt resource value: 0x7F0E0181 + public const int Widget_Compat_NotificationActionText = 2131624321; - // aapt resource value: 0x7f0c017e - public const int Widget_Design_AppBarLayout = 2131493246; + // aapt resource value: 0x7F0E0182 + public const int Widget_Design_AppBarLayout = 2131624322; - // aapt resource value: 0x7f0c017f - public const int Widget_Design_BottomNavigationView = 2131493247; + // aapt resource value: 0x7F0E0183 + public const int Widget_Design_BottomNavigationView = 2131624323; - // aapt resource value: 0x7f0c0180 - public const int Widget_Design_BottomSheet_Modal = 2131493248; + // aapt resource value: 0x7F0E0184 + public const int Widget_Design_BottomSheet_Modal = 2131624324; - // aapt resource value: 0x7f0c0181 - public const int Widget_Design_CollapsingToolbar = 2131493249; + // aapt resource value: 0x7F0E0185 + public const int Widget_Design_CollapsingToolbar = 2131624325; - // aapt resource value: 0x7f0c0182 - public const int Widget_Design_CoordinatorLayout = 2131493250; + // aapt resource value: 0x7F0E0186 + public const int Widget_Design_CoordinatorLayout = 2131624326; - // aapt resource value: 0x7f0c0183 - public const int Widget_Design_FloatingActionButton = 2131493251; + // aapt resource value: 0x7F0E0187 + public const int Widget_Design_FloatingActionButton = 2131624327; - // aapt resource value: 0x7f0c0184 - public const int Widget_Design_NavigationView = 2131493252; + // aapt resource value: 0x7F0E0188 + public const int Widget_Design_NavigationView = 2131624328; - // aapt resource value: 0x7f0c0185 - public const int Widget_Design_ScrimInsetsFrameLayout = 2131493253; + // aapt resource value: 0x7F0E0189 + public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - // aapt resource value: 0x7f0c0186 - public const int Widget_Design_Snackbar = 2131493254; + // aapt resource value: 0x7F0E018A + public const int Widget_Design_Snackbar = 2131624330; - // aapt resource value: 0x7f0c016a - public const int Widget_Design_TabLayout = 2131493226; + // aapt resource value: 0x7F0E018B + public const int Widget_Design_TabLayout = 2131624331; - // aapt resource value: 0x7f0c0187 - public const int Widget_Design_TextInputLayout = 2131493255; + // aapt resource value: 0x7F0E018C + public const int Widget_Design_TextInputLayout = 2131624332; - // aapt resource value: 0x7f0c0009 - public const int Widget_MediaRouter_Light_MediaRouteButton = 2131492873; + // aapt resource value: 0x7F0E018D + public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - // aapt resource value: 0x7f0c000a - public const int Widget_MediaRouter_MediaRouteButton = 2131492874; + // aapt resource value: 0x7F0E018E + public const int Widget_MediaRouter_MediaRouteButton = 2131624334; static Style() { @@ -7511,182 +7385,190 @@ private Style() public partial class Styleable { + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } public static int[] ActionBar = new int[] { - 2130772003, - 2130772005, - 2130772006, - 2130772007, - 2130772008, - 2130772009, - 2130772010, - 2130772011, - 2130772012, - 2130772013, - 2130772014, - 2130772015, - 2130772016, - 2130772017, - 2130772018, - 2130772019, - 2130772020, - 2130772021, - 2130772022, - 2130772023, - 2130772024, - 2130772025, - 2130772026, - 2130772027, - 2130772028, - 2130772029, - 2130772030, - 2130772031, - 2130772101}; - - // aapt resource value: 10 - public const int ActionBar_background = 10; + 2130903089, + 2130903090, + 2130903091, + 2130903142, + 2130903143, + 2130903144, + 2130903145, + 2130903146, + 2130903147, + 2130903159, + 2130903163, + 2130903164, + 2130903175, + 2130903208, + 2130903209, + 2130903213, + 2130903214, + 2130903215, + 2130903220, + 2130903226, + 2130903253, + 2130903275, + 2130903291, + 2130903295, + 2130903296, + 2130903332, + 2130903335, + 2130903379, + 2130903389}; + + // aapt resource value: { 0x10100B3 } + public static int[] ActionBarLayout = new int[] { + 16842931}; - // aapt resource value: 12 - public const int ActionBar_backgroundSplit = 12; + // aapt resource value: 0 + public const int ActionBarLayout_android_layout_gravity = 0; - // aapt resource value: 11 - public const int ActionBar_backgroundStacked = 11; + // aapt resource value: 0 + public const int ActionBar_background = 0; - // aapt resource value: 21 - public const int ActionBar_contentInsetEnd = 21; + // aapt resource value: 1 + public const int ActionBar_backgroundSplit = 1; - // aapt resource value: 25 - public const int ActionBar_contentInsetEndWithActions = 25; + // aapt resource value: 2 + public const int ActionBar_backgroundStacked = 2; - // aapt resource value: 22 - public const int ActionBar_contentInsetLeft = 22; + // aapt resource value: 3 + public const int ActionBar_contentInsetEnd = 3; - // aapt resource value: 23 - public const int ActionBar_contentInsetRight = 23; + // aapt resource value: 4 + public const int ActionBar_contentInsetEndWithActions = 4; - // aapt resource value: 20 - public const int ActionBar_contentInsetStart = 20; + // aapt resource value: 5 + public const int ActionBar_contentInsetLeft = 5; - // aapt resource value: 24 - public const int ActionBar_contentInsetStartWithNavigation = 24; + // aapt resource value: 6 + public const int ActionBar_contentInsetRight = 6; - // aapt resource value: 13 - public const int ActionBar_customNavigationLayout = 13; + // aapt resource value: 7 + public const int ActionBar_contentInsetStart = 7; - // aapt resource value: 3 - public const int ActionBar_displayOptions = 3; + // aapt resource value: 8 + public const int ActionBar_contentInsetStartWithNavigation = 8; // aapt resource value: 9 - public const int ActionBar_divider = 9; + public const int ActionBar_customNavigationLayout = 9; - // aapt resource value: 26 - public const int ActionBar_elevation = 26; + // aapt resource value: 10 + public const int ActionBar_displayOptions = 10; - // aapt resource value: 0 - public const int ActionBar_height = 0; + // aapt resource value: 11 + public const int ActionBar_divider = 11; - // aapt resource value: 19 - public const int ActionBar_hideOnContentScroll = 19; + // aapt resource value: 12 + public const int ActionBar_elevation = 12; - // aapt resource value: 28 - public const int ActionBar_homeAsUpIndicator = 28; + // aapt resource value: 13 + public const int ActionBar_height = 13; // aapt resource value: 14 - public const int ActionBar_homeLayout = 14; + public const int ActionBar_hideOnContentScroll = 14; - // aapt resource value: 7 - public const int ActionBar_icon = 7; + // aapt resource value: 15 + public const int ActionBar_homeAsUpIndicator = 15; // aapt resource value: 16 - public const int ActionBar_indeterminateProgressStyle = 16; + public const int ActionBar_homeLayout = 16; - // aapt resource value: 18 - public const int ActionBar_itemPadding = 18; + // aapt resource value: 17 + public const int ActionBar_icon = 17; - // aapt resource value: 8 - public const int ActionBar_logo = 8; + // aapt resource value: 18 + public const int ActionBar_indeterminateProgressStyle = 18; - // aapt resource value: 2 - public const int ActionBar_navigationMode = 2; + // aapt resource value: 19 + public const int ActionBar_itemPadding = 19; - // aapt resource value: 27 - public const int ActionBar_popupTheme = 27; + // aapt resource value: 20 + public const int ActionBar_logo = 20; - // aapt resource value: 17 - public const int ActionBar_progressBarPadding = 17; + // aapt resource value: 21 + public const int ActionBar_navigationMode = 21; - // aapt resource value: 15 - public const int ActionBar_progressBarStyle = 15; + // aapt resource value: 22 + public const int ActionBar_popupTheme = 22; - // aapt resource value: 4 - public const int ActionBar_subtitle = 4; + // aapt resource value: 23 + public const int ActionBar_progressBarPadding = 23; - // aapt resource value: 6 - public const int ActionBar_subtitleTextStyle = 6; + // aapt resource value: 24 + public const int ActionBar_progressBarStyle = 24; - // aapt resource value: 1 - public const int ActionBar_title = 1; + // aapt resource value: 25 + public const int ActionBar_subtitle = 25; - // aapt resource value: 5 - public const int ActionBar_titleTextStyle = 5; + // aapt resource value: 26 + public const int ActionBar_subtitleTextStyle = 26; - public static int[] ActionBarLayout = new int[] { - 16842931}; + // aapt resource value: 27 + public const int ActionBar_title = 27; - // aapt resource value: 0 - public const int ActionBarLayout_android_layout_gravity = 0; + // aapt resource value: 28 + public const int ActionBar_titleTextStyle = 28; + // aapt resource value: { 0x101013F } public static int[] ActionMenuItemView = new int[] { 16843071}; // aapt resource value: 0 public const int ActionMenuItemView_android_minWidth = 0; - public static int[] ActionMenuView; + // aapt resource value: { 0xFFFFFFFF } + public static int[] ActionMenuView = new int[] { + -1}; + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } public static int[] ActionMode = new int[] { - 2130772003, - 2130772009, - 2130772010, - 2130772014, - 2130772016, - 2130772032}; + 2130903089, + 2130903090, + 2130903124, + 2130903208, + 2130903335, + 2130903389}; - // aapt resource value: 3 - public const int ActionMode_background = 3; + // aapt resource value: 0 + public const int ActionMode_background = 0; - // aapt resource value: 4 - public const int ActionMode_backgroundSplit = 4; + // aapt resource value: 1 + public const int ActionMode_backgroundSplit = 1; - // aapt resource value: 5 - public const int ActionMode_closeItemLayout = 5; + // aapt resource value: 2 + public const int ActionMode_closeItemLayout = 2; - // aapt resource value: 0 - public const int ActionMode_height = 0; + // aapt resource value: 3 + public const int ActionMode_height = 3; - // aapt resource value: 2 - public const int ActionMode_subtitleTextStyle = 2; + // aapt resource value: 4 + public const int ActionMode_subtitleTextStyle = 4; - // aapt resource value: 1 - public const int ActionMode_titleTextStyle = 1; + // aapt resource value: 5 + public const int ActionMode_titleTextStyle = 5; + // aapt resource value: { 0x7F03008A,0x7F0300B5 } public static int[] ActivityChooserView = new int[] { - 2130772033, - 2130772034}; - - // aapt resource value: 1 - public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 1; + 2130903178, + 2130903221}; // aapt resource value: 0 - public const int ActivityChooserView_initialActivityCount = 0; + public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; + + // aapt resource value: 1 + public const int ActivityChooserView_initialActivityCount = 1; + // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } public static int[] AlertDialog = new int[] { 16842994, - 2130772035, - 2130772036, - 2130772037, - 2130772038, - 2130772039, - 2130772040}; + 2130903110, + 2130903244, + 2130903245, + 2130903272, + 2130903316, + 2130903317}; // aapt resource value: 0 public const int AlertDialog_android_layout = 0; @@ -7694,27 +7576,39 @@ public partial class Styleable // aapt resource value: 1 public const int AlertDialog_buttonPanelSideLayout = 1; - // aapt resource value: 5 - public const int AlertDialog_listItemLayout = 5; - // aapt resource value: 2 - public const int AlertDialog_listLayout = 2; + public const int AlertDialog_listItemLayout = 2; // aapt resource value: 3 - public const int AlertDialog_multiChoiceItemLayout = 3; - - // aapt resource value: 6 - public const int AlertDialog_showTitle = 6; + public const int AlertDialog_listLayout = 3; // aapt resource value: 4 - public const int AlertDialog_singleChoiceItemLayout = 4; + public const int AlertDialog_multiChoiceItemLayout = 4; + + // aapt resource value: 5 + public const int AlertDialog_showTitle = 5; + + // aapt resource value: 6 + public const int AlertDialog_singleChoiceItemLayout = 6; + // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } public static int[] AppBarLayout = new int[] { 16842964, 16843919, 16844096, - 2130772030, - 2130772248}; + 2130903175, + 2130903179}; + + // aapt resource value: { 0x7F03011E,0x7F03011F } + public static int[] AppBarLayoutStates = new int[] { + 2130903326, + 2130903327}; + + // aapt resource value: 0 + public const int AppBarLayoutStates_state_collapsed = 0; + + // aapt resource value: 1 + public const int AppBarLayoutStates_state_collapsible = 1; // aapt resource value: 0 public const int AppBarLayout_android_background = 0; @@ -7731,19 +7625,10 @@ public partial class Styleable // aapt resource value: 4 public const int AppBarLayout_expanded = 4; - public static int[] AppBarLayoutStates = new int[] { - 2130772249, - 2130772250}; - - // aapt resource value: 0 - public const int AppBarLayoutStates_state_collapsed = 0; - - // aapt resource value: 1 - public const int AppBarLayoutStates_state_collapsible = 1; - + // aapt resource value: { 0x7F0300C8,0x7F0300C9 } public static int[] AppBarLayout_Layout = new int[] { - 2130772251, - 2130772252}; + 2130903240, + 2130903241}; // aapt resource value: 0 public const int AppBarLayout_Layout_layout_scrollFlags = 0; @@ -7751,11 +7636,12 @@ public partial class Styleable // aapt resource value: 1 public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; + // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } public static int[] AppCompatImageView = new int[] { 16843033, - 2130772041, - 2130772042, - 2130772043}; + 2130903323, + 2130903377, + 2130903378}; // aapt resource value: 0 public const int AppCompatImageView_android_src = 0; @@ -7769,11 +7655,12 @@ public partial class Styleable // aapt resource value: 3 public const int AppCompatImageView_tintMode = 3; + // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } public static int[] AppCompatSeekBar = new int[] { 16843074, - 2130772044, - 2130772045, - 2130772046}; + 2130903374, + 2130903375, + 2130903376}; // aapt resource value: 0 public const int AppCompatSeekBar_android_thumb = 0; @@ -7787,6 +7674,7 @@ public partial class Styleable // aapt resource value: 3 public const int AppCompatSeekBar_tickMarkTintMode = 3; + // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } public static int[] AppCompatTextHelper = new int[] { 16842804, 16843117, @@ -7817,530 +7705,533 @@ public partial class Styleable // aapt resource value: 0 public const int AppCompatTextHelper_android_textAppearance = 0; + // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } public static int[] AppCompatTextView = new int[] { 16842804, - 2130772047, - 2130772048, - 2130772049, - 2130772050, - 2130772051, - 2130772052, - 2130772053}; + 2130903084, + 2130903085, + 2130903086, + 2130903087, + 2130903088, + 2130903195, + 2130903357}; // aapt resource value: 0 public const int AppCompatTextView_android_textAppearance = 0; - // aapt resource value: 6 - public const int AppCompatTextView_autoSizeMaxTextSize = 6; + // aapt resource value: 1 + public const int AppCompatTextView_autoSizeMaxTextSize = 1; - // aapt resource value: 5 - public const int AppCompatTextView_autoSizeMinTextSize = 5; + // aapt resource value: 2 + public const int AppCompatTextView_autoSizeMinTextSize = 2; + + // aapt resource value: 3 + public const int AppCompatTextView_autoSizePresetSizes = 3; // aapt resource value: 4 - public const int AppCompatTextView_autoSizePresetSizes = 4; + public const int AppCompatTextView_autoSizeStepGranularity = 4; - // aapt resource value: 3 - public const int AppCompatTextView_autoSizeStepGranularity = 3; + // aapt resource value: 5 + public const int AppCompatTextView_autoSizeTextType = 5; - // aapt resource value: 2 - public const int AppCompatTextView_autoSizeTextType = 2; + // aapt resource value: 6 + public const int AppCompatTextView_fontFamily = 6; // aapt resource value: 7 - public const int AppCompatTextView_fontFamily = 7; - - // aapt resource value: 1 - public const int AppCompatTextView_textAllCaps = 1; + public const int AppCompatTextView_textAllCaps = 7; + // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } public static int[] AppCompatTheme = new int[] { 16842839, 16842926, - 2130772054, - 2130772055, - 2130772056, - 2130772057, - 2130772058, - 2130772059, - 2130772060, - 2130772061, - 2130772062, - 2130772063, - 2130772064, - 2130772065, - 2130772066, - 2130772067, - 2130772068, - 2130772069, - 2130772070, - 2130772071, - 2130772072, - 2130772073, - 2130772074, - 2130772075, - 2130772076, - 2130772077, - 2130772078, - 2130772079, - 2130772080, - 2130772081, - 2130772082, - 2130772083, - 2130772084, - 2130772085, - 2130772086, - 2130772087, - 2130772088, - 2130772089, - 2130772090, - 2130772091, - 2130772092, - 2130772093, - 2130772094, - 2130772095, - 2130772096, - 2130772097, - 2130772098, - 2130772099, - 2130772100, - 2130772101, - 2130772102, - 2130772103, - 2130772104, - 2130772105, - 2130772106, - 2130772107, - 2130772108, - 2130772109, - 2130772110, - 2130772111, - 2130772112, - 2130772113, - 2130772114, - 2130772115, - 2130772116, - 2130772117, - 2130772118, - 2130772119, - 2130772120, - 2130772121, - 2130772122, - 2130772123, - 2130772124, - 2130772125, - 2130772126, - 2130772127, - 2130772128, - 2130772129, - 2130772130, - 2130772131, - 2130772132, - 2130772133, - 2130772134, - 2130772135, - 2130772136, - 2130772137, - 2130772138, - 2130772139, - 2130772140, - 2130772141, - 2130772142, - 2130772143, - 2130772144, - 2130772145, - 2130772146, - 2130772147, - 2130772148, - 2130772149, - 2130772150, - 2130772151, - 2130772152, - 2130772153, - 2130772154, - 2130772155, - 2130772156, - 2130772157, - 2130772158, - 2130772159, - 2130772160, - 2130772161, - 2130772162, - 2130772163, - 2130772164, - 2130772165, - 2130772166, - 2130772167, - 2130772168, - 2130772169, - 2130772170}; + 2130903040, + 2130903041, + 2130903042, + 2130903043, + 2130903044, + 2130903045, + 2130903046, + 2130903047, + 2130903048, + 2130903049, + 2130903050, + 2130903051, + 2130903052, + 2130903054, + 2130903055, + 2130903056, + 2130903057, + 2130903058, + 2130903059, + 2130903060, + 2130903061, + 2130903062, + 2130903063, + 2130903064, + 2130903065, + 2130903066, + 2130903067, + 2130903068, + 2130903069, + 2130903070, + 2130903073, + 2130903074, + 2130903075, + 2130903076, + 2130903077, + 2130903083, + 2130903101, + 2130903104, + 2130903105, + 2130903106, + 2130903107, + 2130903108, + 2130903111, + 2130903112, + 2130903121, + 2130903122, + 2130903130, + 2130903131, + 2130903132, + 2130903133, + 2130903134, + 2130903135, + 2130903136, + 2130903137, + 2130903138, + 2130903139, + 2130903154, + 2130903161, + 2130903162, + 2130903165, + 2130903167, + 2130903170, + 2130903171, + 2130903172, + 2130903173, + 2130903174, + 2130903213, + 2130903219, + 2130903242, + 2130903243, + 2130903246, + 2130903247, + 2130903248, + 2130903249, + 2130903250, + 2130903251, + 2130903252, + 2130903282, + 2130903283, + 2130903284, + 2130903290, + 2130903292, + 2130903299, + 2130903300, + 2130903301, + 2130903302, + 2130903309, + 2130903310, + 2130903311, + 2130903312, + 2130903320, + 2130903321, + 2130903339, + 2130903358, + 2130903359, + 2130903360, + 2130903361, + 2130903362, + 2130903363, + 2130903364, + 2130903365, + 2130903366, + 2130903368, + 2130903391, + 2130903392, + 2130903393, + 2130903394, + 2130903401, + 2130903402, + 2130903403, + 2130903404, + 2130903405, + 2130903406, + 2130903407, + 2130903408, + 2130903409, + 2130903410}; - // aapt resource value: 23 - public const int AppCompatTheme_actionBarDivider = 23; + // aapt resource value: 2 + public const int AppCompatTheme_actionBarDivider = 2; - // aapt resource value: 24 - public const int AppCompatTheme_actionBarItemBackground = 24; + // aapt resource value: 3 + public const int AppCompatTheme_actionBarItemBackground = 3; - // aapt resource value: 17 - public const int AppCompatTheme_actionBarPopupTheme = 17; + // aapt resource value: 4 + public const int AppCompatTheme_actionBarPopupTheme = 4; - // aapt resource value: 22 - public const int AppCompatTheme_actionBarSize = 22; + // aapt resource value: 5 + public const int AppCompatTheme_actionBarSize = 5; - // aapt resource value: 19 - public const int AppCompatTheme_actionBarSplitStyle = 19; + // aapt resource value: 6 + public const int AppCompatTheme_actionBarSplitStyle = 6; - // aapt resource value: 18 - public const int AppCompatTheme_actionBarStyle = 18; + // aapt resource value: 7 + public const int AppCompatTheme_actionBarStyle = 7; - // aapt resource value: 13 - public const int AppCompatTheme_actionBarTabBarStyle = 13; + // aapt resource value: 8 + public const int AppCompatTheme_actionBarTabBarStyle = 8; + + // aapt resource value: 9 + public const int AppCompatTheme_actionBarTabStyle = 9; + + // aapt resource value: 10 + public const int AppCompatTheme_actionBarTabTextStyle = 10; + + // aapt resource value: 11 + public const int AppCompatTheme_actionBarTheme = 11; // aapt resource value: 12 - public const int AppCompatTheme_actionBarTabStyle = 12; + public const int AppCompatTheme_actionBarWidgetTheme = 12; + + // aapt resource value: 13 + public const int AppCompatTheme_actionButtonStyle = 13; // aapt resource value: 14 - public const int AppCompatTheme_actionBarTabTextStyle = 14; + public const int AppCompatTheme_actionDropDownStyle = 14; + + // aapt resource value: 15 + public const int AppCompatTheme_actionMenuTextAppearance = 15; + + // aapt resource value: 16 + public const int AppCompatTheme_actionMenuTextColor = 16; + + // aapt resource value: 17 + public const int AppCompatTheme_actionModeBackground = 17; + + // aapt resource value: 18 + public const int AppCompatTheme_actionModeCloseButtonStyle = 18; + + // aapt resource value: 19 + public const int AppCompatTheme_actionModeCloseDrawable = 19; // aapt resource value: 20 - public const int AppCompatTheme_actionBarTheme = 20; + public const int AppCompatTheme_actionModeCopyDrawable = 20; // aapt resource value: 21 - public const int AppCompatTheme_actionBarWidgetTheme = 21; + public const int AppCompatTheme_actionModeCutDrawable = 21; - // aapt resource value: 50 - public const int AppCompatTheme_actionButtonStyle = 50; + // aapt resource value: 22 + public const int AppCompatTheme_actionModeFindDrawable = 22; - // aapt resource value: 46 - public const int AppCompatTheme_actionDropDownStyle = 46; + // aapt resource value: 23 + public const int AppCompatTheme_actionModePasteDrawable = 23; + + // aapt resource value: 24 + public const int AppCompatTheme_actionModePopupWindowStyle = 24; // aapt resource value: 25 - public const int AppCompatTheme_actionMenuTextAppearance = 25; + public const int AppCompatTheme_actionModeSelectAllDrawable = 25; // aapt resource value: 26 - public const int AppCompatTheme_actionMenuTextColor = 26; + public const int AppCompatTheme_actionModeShareDrawable = 26; - // aapt resource value: 29 - public const int AppCompatTheme_actionModeBackground = 29; + // aapt resource value: 27 + public const int AppCompatTheme_actionModeSplitBackground = 27; // aapt resource value: 28 - public const int AppCompatTheme_actionModeCloseButtonStyle = 28; + public const int AppCompatTheme_actionModeStyle = 28; - // aapt resource value: 31 - public const int AppCompatTheme_actionModeCloseDrawable = 31; + // aapt resource value: 29 + public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - // aapt resource value: 33 - public const int AppCompatTheme_actionModeCopyDrawable = 33; + // aapt resource value: 30 + public const int AppCompatTheme_actionOverflowButtonStyle = 30; + + // aapt resource value: 31 + public const int AppCompatTheme_actionOverflowMenuStyle = 31; // aapt resource value: 32 - public const int AppCompatTheme_actionModeCutDrawable = 32; + public const int AppCompatTheme_activityChooserViewStyle = 32; - // aapt resource value: 37 - public const int AppCompatTheme_actionModeFindDrawable = 37; + // aapt resource value: 33 + public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; // aapt resource value: 34 - public const int AppCompatTheme_actionModePasteDrawable = 34; - - // aapt resource value: 39 - public const int AppCompatTheme_actionModePopupWindowStyle = 39; + public const int AppCompatTheme_alertDialogCenterButtons = 34; // aapt resource value: 35 - public const int AppCompatTheme_actionModeSelectAllDrawable = 35; + public const int AppCompatTheme_alertDialogStyle = 35; // aapt resource value: 36 - public const int AppCompatTheme_actionModeShareDrawable = 36; + public const int AppCompatTheme_alertDialogTheme = 36; - // aapt resource value: 30 - public const int AppCompatTheme_actionModeSplitBackground = 30; + // aapt resource value: 1 + public const int AppCompatTheme_android_windowAnimationStyle = 1; - // aapt resource value: 27 - public const int AppCompatTheme_actionModeStyle = 27; + // aapt resource value: 0 + public const int AppCompatTheme_android_windowIsFloating = 0; + + // aapt resource value: 37 + public const int AppCompatTheme_autoCompleteTextViewStyle = 37; // aapt resource value: 38 - public const int AppCompatTheme_actionModeWebSearchDrawable = 38; + public const int AppCompatTheme_borderlessButtonStyle = 38; - // aapt resource value: 15 - public const int AppCompatTheme_actionOverflowButtonStyle = 15; + // aapt resource value: 39 + public const int AppCompatTheme_buttonBarButtonStyle = 39; - // aapt resource value: 16 - public const int AppCompatTheme_actionOverflowMenuStyle = 16; + // aapt resource value: 40 + public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - // aapt resource value: 58 - public const int AppCompatTheme_activityChooserViewStyle = 58; + // aapt resource value: 41 + public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - // aapt resource value: 95 - public const int AppCompatTheme_alertDialogButtonGroupStyle = 95; + // aapt resource value: 42 + public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - // aapt resource value: 96 - public const int AppCompatTheme_alertDialogCenterButtons = 96; + // aapt resource value: 43 + public const int AppCompatTheme_buttonBarStyle = 43; - // aapt resource value: 94 - public const int AppCompatTheme_alertDialogStyle = 94; + // aapt resource value: 44 + public const int AppCompatTheme_buttonStyle = 44; - // aapt resource value: 97 - public const int AppCompatTheme_alertDialogTheme = 97; + // aapt resource value: 45 + public const int AppCompatTheme_buttonStyleSmall = 45; - // aapt resource value: 1 - public const int AppCompatTheme_android_windowAnimationStyle = 1; + // aapt resource value: 46 + public const int AppCompatTheme_checkboxStyle = 46; - // aapt resource value: 0 - public const int AppCompatTheme_android_windowIsFloating = 0; + // aapt resource value: 47 + public const int AppCompatTheme_checkedTextViewStyle = 47; - // aapt resource value: 102 - public const int AppCompatTheme_autoCompleteTextViewStyle = 102; + // aapt resource value: 48 + public const int AppCompatTheme_colorAccent = 48; - // aapt resource value: 55 - public const int AppCompatTheme_borderlessButtonStyle = 55; + // aapt resource value: 49 + public const int AppCompatTheme_colorBackgroundFloating = 49; - // aapt resource value: 52 - public const int AppCompatTheme_buttonBarButtonStyle = 52; + // aapt resource value: 50 + public const int AppCompatTheme_colorButtonNormal = 50; - // aapt resource value: 100 - public const int AppCompatTheme_buttonBarNegativeButtonStyle = 100; + // aapt resource value: 51 + public const int AppCompatTheme_colorControlActivated = 51; - // aapt resource value: 101 - public const int AppCompatTheme_buttonBarNeutralButtonStyle = 101; + // aapt resource value: 52 + public const int AppCompatTheme_colorControlHighlight = 52; - // aapt resource value: 99 - public const int AppCompatTheme_buttonBarPositiveButtonStyle = 99; + // aapt resource value: 53 + public const int AppCompatTheme_colorControlNormal = 53; - // aapt resource value: 51 - public const int AppCompatTheme_buttonBarStyle = 51; + // aapt resource value: 54 + public const int AppCompatTheme_colorError = 54; - // aapt resource value: 103 - public const int AppCompatTheme_buttonStyle = 103; + // aapt resource value: 55 + public const int AppCompatTheme_colorPrimary = 55; - // aapt resource value: 104 - public const int AppCompatTheme_buttonStyleSmall = 104; + // aapt resource value: 56 + public const int AppCompatTheme_colorPrimaryDark = 56; - // aapt resource value: 105 - public const int AppCompatTheme_checkboxStyle = 105; + // aapt resource value: 57 + public const int AppCompatTheme_colorSwitchThumbNormal = 57; - // aapt resource value: 106 - public const int AppCompatTheme_checkedTextViewStyle = 106; + // aapt resource value: 58 + public const int AppCompatTheme_controlBackground = 58; - // aapt resource value: 86 - public const int AppCompatTheme_colorAccent = 86; + // aapt resource value: 59 + public const int AppCompatTheme_dialogPreferredPadding = 59; - // aapt resource value: 93 - public const int AppCompatTheme_colorBackgroundFloating = 93; + // aapt resource value: 60 + public const int AppCompatTheme_dialogTheme = 60; - // aapt resource value: 90 - public const int AppCompatTheme_colorButtonNormal = 90; + // aapt resource value: 61 + public const int AppCompatTheme_dividerHorizontal = 61; - // aapt resource value: 88 - public const int AppCompatTheme_colorControlActivated = 88; + // aapt resource value: 62 + public const int AppCompatTheme_dividerVertical = 62; - // aapt resource value: 89 - public const int AppCompatTheme_colorControlHighlight = 89; + // aapt resource value: 64 + public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - // aapt resource value: 87 - public const int AppCompatTheme_colorControlNormal = 87; + // aapt resource value: 63 + public const int AppCompatTheme_dropDownListViewStyle = 63; - // aapt resource value: 118 - public const int AppCompatTheme_colorError = 118; + // aapt resource value: 65 + public const int AppCompatTheme_editTextBackground = 65; - // aapt resource value: 84 - public const int AppCompatTheme_colorPrimary = 84; + // aapt resource value: 66 + public const int AppCompatTheme_editTextColor = 66; - // aapt resource value: 85 - public const int AppCompatTheme_colorPrimaryDark = 85; + // aapt resource value: 67 + public const int AppCompatTheme_editTextStyle = 67; - // aapt resource value: 91 - public const int AppCompatTheme_colorSwitchThumbNormal = 91; + // aapt resource value: 68 + public const int AppCompatTheme_homeAsUpIndicator = 68; - // aapt resource value: 92 - public const int AppCompatTheme_controlBackground = 92; + // aapt resource value: 69 + public const int AppCompatTheme_imageButtonStyle = 69; - // aapt resource value: 44 - public const int AppCompatTheme_dialogPreferredPadding = 44; + // aapt resource value: 70 + public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - // aapt resource value: 43 - public const int AppCompatTheme_dialogTheme = 43; + // aapt resource value: 71 + public const int AppCompatTheme_listDividerAlertDialog = 71; - // aapt resource value: 57 - public const int AppCompatTheme_dividerHorizontal = 57; + // aapt resource value: 72 + public const int AppCompatTheme_listMenuViewStyle = 72; - // aapt resource value: 56 - public const int AppCompatTheme_dividerVertical = 56; + // aapt resource value: 73 + public const int AppCompatTheme_listPopupWindowStyle = 73; + + // aapt resource value: 74 + public const int AppCompatTheme_listPreferredItemHeight = 74; // aapt resource value: 75 - public const int AppCompatTheme_dropDownListViewStyle = 75; + public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - // aapt resource value: 47 - public const int AppCompatTheme_dropdownListPreferredItemHeight = 47; + // aapt resource value: 76 + public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - // aapt resource value: 64 - public const int AppCompatTheme_editTextBackground = 64; + // aapt resource value: 77 + public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - // aapt resource value: 63 - public const int AppCompatTheme_editTextColor = 63; - - // aapt resource value: 107 - public const int AppCompatTheme_editTextStyle = 107; - - // aapt resource value: 49 - public const int AppCompatTheme_homeAsUpIndicator = 49; - - // aapt resource value: 65 - public const int AppCompatTheme_imageButtonStyle = 65; - - // aapt resource value: 83 - public const int AppCompatTheme_listChoiceBackgroundIndicator = 83; - - // aapt resource value: 45 - public const int AppCompatTheme_listDividerAlertDialog = 45; - - // aapt resource value: 115 - public const int AppCompatTheme_listMenuViewStyle = 115; - - // aapt resource value: 76 - public const int AppCompatTheme_listPopupWindowStyle = 76; - - // aapt resource value: 70 - public const int AppCompatTheme_listPreferredItemHeight = 70; - - // aapt resource value: 72 - public const int AppCompatTheme_listPreferredItemHeightLarge = 72; - - // aapt resource value: 71 - public const int AppCompatTheme_listPreferredItemHeightSmall = 71; - - // aapt resource value: 73 - public const int AppCompatTheme_listPreferredItemPaddingLeft = 73; + // aapt resource value: 78 + public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - // aapt resource value: 74 - public const int AppCompatTheme_listPreferredItemPaddingRight = 74; + // aapt resource value: 79 + public const int AppCompatTheme_panelBackground = 79; // aapt resource value: 80 - public const int AppCompatTheme_panelBackground = 80; - - // aapt resource value: 82 - public const int AppCompatTheme_panelMenuListTheme = 82; + public const int AppCompatTheme_panelMenuListTheme = 80; // aapt resource value: 81 public const int AppCompatTheme_panelMenuListWidth = 81; - // aapt resource value: 61 - public const int AppCompatTheme_popupMenuStyle = 61; + // aapt resource value: 82 + public const int AppCompatTheme_popupMenuStyle = 82; - // aapt resource value: 62 - public const int AppCompatTheme_popupWindowStyle = 62; + // aapt resource value: 83 + public const int AppCompatTheme_popupWindowStyle = 83; - // aapt resource value: 108 - public const int AppCompatTheme_radioButtonStyle = 108; + // aapt resource value: 84 + public const int AppCompatTheme_radioButtonStyle = 84; - // aapt resource value: 109 - public const int AppCompatTheme_ratingBarStyle = 109; + // aapt resource value: 85 + public const int AppCompatTheme_ratingBarStyle = 85; - // aapt resource value: 110 - public const int AppCompatTheme_ratingBarStyleIndicator = 110; + // aapt resource value: 86 + public const int AppCompatTheme_ratingBarStyleIndicator = 86; - // aapt resource value: 111 - public const int AppCompatTheme_ratingBarStyleSmall = 111; + // aapt resource value: 87 + public const int AppCompatTheme_ratingBarStyleSmall = 87; - // aapt resource value: 69 - public const int AppCompatTheme_searchViewStyle = 69; + // aapt resource value: 88 + public const int AppCompatTheme_searchViewStyle = 88; - // aapt resource value: 112 - public const int AppCompatTheme_seekBarStyle = 112; + // aapt resource value: 89 + public const int AppCompatTheme_seekBarStyle = 89; - // aapt resource value: 53 - public const int AppCompatTheme_selectableItemBackground = 53; + // aapt resource value: 90 + public const int AppCompatTheme_selectableItemBackground = 90; - // aapt resource value: 54 - public const int AppCompatTheme_selectableItemBackgroundBorderless = 54; + // aapt resource value: 91 + public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - // aapt resource value: 48 - public const int AppCompatTheme_spinnerDropDownItemStyle = 48; + // aapt resource value: 92 + public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - // aapt resource value: 113 - public const int AppCompatTheme_spinnerStyle = 113; + // aapt resource value: 93 + public const int AppCompatTheme_spinnerStyle = 93; - // aapt resource value: 114 - public const int AppCompatTheme_switchStyle = 114; + // aapt resource value: 94 + public const int AppCompatTheme_switchStyle = 94; - // aapt resource value: 40 - public const int AppCompatTheme_textAppearanceLargePopupMenu = 40; + // aapt resource value: 95 + public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - // aapt resource value: 77 - public const int AppCompatTheme_textAppearanceListItem = 77; + // aapt resource value: 96 + public const int AppCompatTheme_textAppearanceListItem = 96; - // aapt resource value: 78 - public const int AppCompatTheme_textAppearanceListItemSecondary = 78; + // aapt resource value: 97 + public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - // aapt resource value: 79 - public const int AppCompatTheme_textAppearanceListItemSmall = 79; + // aapt resource value: 98 + public const int AppCompatTheme_textAppearanceListItemSmall = 98; - // aapt resource value: 42 - public const int AppCompatTheme_textAppearancePopupMenuHeader = 42; + // aapt resource value: 99 + public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - // aapt resource value: 67 - public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 67; + // aapt resource value: 100 + public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - // aapt resource value: 66 - public const int AppCompatTheme_textAppearanceSearchResultTitle = 66; + // aapt resource value: 101 + public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - // aapt resource value: 41 - public const int AppCompatTheme_textAppearanceSmallPopupMenu = 41; + // aapt resource value: 102 + public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - // aapt resource value: 98 - public const int AppCompatTheme_textColorAlertDialogListItem = 98; + // aapt resource value: 103 + public const int AppCompatTheme_textColorAlertDialogListItem = 103; - // aapt resource value: 68 - public const int AppCompatTheme_textColorSearchUrl = 68; + // aapt resource value: 104 + public const int AppCompatTheme_textColorSearchUrl = 104; - // aapt resource value: 60 - public const int AppCompatTheme_toolbarNavigationButtonStyle = 60; + // aapt resource value: 105 + public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - // aapt resource value: 59 - public const int AppCompatTheme_toolbarStyle = 59; + // aapt resource value: 106 + public const int AppCompatTheme_toolbarStyle = 106; - // aapt resource value: 117 - public const int AppCompatTheme_tooltipForegroundColor = 117; + // aapt resource value: 107 + public const int AppCompatTheme_tooltipForegroundColor = 107; - // aapt resource value: 116 - public const int AppCompatTheme_tooltipFrameBackground = 116; + // aapt resource value: 108 + public const int AppCompatTheme_tooltipFrameBackground = 108; - // aapt resource value: 2 - public const int AppCompatTheme_windowActionBar = 2; + // aapt resource value: 109 + public const int AppCompatTheme_windowActionBar = 109; - // aapt resource value: 4 - public const int AppCompatTheme_windowActionBarOverlay = 4; + // aapt resource value: 110 + public const int AppCompatTheme_windowActionBarOverlay = 110; - // aapt resource value: 5 - public const int AppCompatTheme_windowActionModeOverlay = 5; + // aapt resource value: 111 + public const int AppCompatTheme_windowActionModeOverlay = 111; - // aapt resource value: 9 - public const int AppCompatTheme_windowFixedHeightMajor = 9; + // aapt resource value: 112 + public const int AppCompatTheme_windowFixedHeightMajor = 112; - // aapt resource value: 7 - public const int AppCompatTheme_windowFixedHeightMinor = 7; + // aapt resource value: 113 + public const int AppCompatTheme_windowFixedHeightMinor = 113; - // aapt resource value: 6 - public const int AppCompatTheme_windowFixedWidthMajor = 6; + // aapt resource value: 114 + public const int AppCompatTheme_windowFixedWidthMajor = 114; - // aapt resource value: 8 - public const int AppCompatTheme_windowFixedWidthMinor = 8; + // aapt resource value: 115 + public const int AppCompatTheme_windowFixedWidthMinor = 115; - // aapt resource value: 10 - public const int AppCompatTheme_windowMinWidthMajor = 10; + // aapt resource value: 116 + public const int AppCompatTheme_windowMinWidthMajor = 116; - // aapt resource value: 11 - public const int AppCompatTheme_windowMinWidthMinor = 11; + // aapt resource value: 117 + public const int AppCompatTheme_windowMinWidthMinor = 117; - // aapt resource value: 3 - public const int AppCompatTheme_windowNoTitle = 3; + // aapt resource value: 118 + public const int AppCompatTheme_windowNoTitle = 118; + // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } public static int[] BottomNavigationView = new int[] { - 2130772030, - 2130772291, - 2130772292, - 2130772293, - 2130772294}; + 2130903175, + 2130903224, + 2130903225, + 2130903228, + 2130903271}; // aapt resource value: 0 public const int BottomNavigationView_elevation = 0; - // aapt resource value: 4 - public const int BottomNavigationView_itemBackground = 4; + // aapt resource value: 1 + public const int BottomNavigationView_itemBackground = 1; // aapt resource value: 2 public const int BottomNavigationView_itemIconTint = 2; @@ -8348,43 +8239,46 @@ public partial class Styleable // aapt resource value: 3 public const int BottomNavigationView_itemTextColor = 3; - // aapt resource value: 1 - public const int BottomNavigationView_menu = 1; + // aapt resource value: 4 + public const int BottomNavigationView_menu = 4; + // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } public static int[] BottomSheetBehavior_Layout = new int[] { - 2130772253, - 2130772254, - 2130772255}; - - // aapt resource value: 1 - public const int BottomSheetBehavior_Layout_behavior_hideable = 1; + 2130903096, + 2130903098, + 2130903099}; // aapt resource value: 0 - public const int BottomSheetBehavior_Layout_behavior_peekHeight = 0; + public const int BottomSheetBehavior_Layout_behavior_hideable = 0; + + // aapt resource value: 1 + public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; // aapt resource value: 2 public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; + // aapt resource value: { 0x7F030026 } public static int[] ButtonBarLayout = new int[] { - 2130772171}; + 2130903078}; // aapt resource value: 0 public const int ButtonBarLayout_allowStacking = 0; + // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } public static int[] CardView = new int[] { 16843071, 16843072, - 2130771991, - 2130771992, - 2130771993, - 2130771994, - 2130771995, - 2130771996, - 2130771997, - 2130771998, - 2130771999, - 2130772000, - 2130772001}; + 2130903115, + 2130903116, + 2130903117, + 2130903118, + 2130903119, + 2130903120, + 2130903148, + 2130903149, + 2130903150, + 2130903151, + 2130903152}; // aapt resource value: 1 public const int CardView_android_minHeight = 1; @@ -8404,96 +8298,80 @@ public partial class Styleable // aapt resource value: 5 public const int CardView_cardMaxElevation = 5; - // aapt resource value: 7 - public const int CardView_cardPreventCornerOverlap = 7; - // aapt resource value: 6 - public const int CardView_cardUseCompatPadding = 6; + public const int CardView_cardPreventCornerOverlap = 6; + + // aapt resource value: 7 + public const int CardView_cardUseCompatPadding = 7; // aapt resource value: 8 public const int CardView_contentPadding = 8; - // aapt resource value: 12 - public const int CardView_contentPaddingBottom = 12; - // aapt resource value: 9 - public const int CardView_contentPaddingLeft = 9; + public const int CardView_contentPaddingBottom = 9; // aapt resource value: 10 - public const int CardView_contentPaddingRight = 10; + public const int CardView_contentPaddingLeft = 10; // aapt resource value: 11 - public const int CardView_contentPaddingTop = 11; + public const int CardView_contentPaddingRight = 11; + // aapt resource value: 12 + public const int CardView_contentPaddingTop = 12; + + // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } public static int[] CollapsingToolbarLayout = new int[] { - 2130772005, - 2130772256, - 2130772257, - 2130772258, - 2130772259, - 2130772260, - 2130772261, - 2130772262, - 2130772263, - 2130772264, - 2130772265, - 2130772266, - 2130772267, - 2130772268, - 2130772269, - 2130772270}; + 2130903127, + 2130903128, + 2130903153, + 2130903180, + 2130903181, + 2130903182, + 2130903183, + 2130903184, + 2130903185, + 2130903186, + 2130903305, + 2130903306, + 2130903329, + 2130903379, + 2130903380, + 2130903390}; - // aapt resource value: 13 - public const int CollapsingToolbarLayout_collapsedTitleGravity = 13; + // aapt resource value: 0 + public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - // aapt resource value: 7 - public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 7; + // aapt resource value: 1 + public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - // aapt resource value: 8 - public const int CollapsingToolbarLayout_contentScrim = 8; + // aapt resource value: 2 + public const int CollapsingToolbarLayout_contentScrim = 2; - // aapt resource value: 14 - public const int CollapsingToolbarLayout_expandedTitleGravity = 14; + // aapt resource value: 3 + public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - // aapt resource value: 1 - public const int CollapsingToolbarLayout_expandedTitleMargin = 1; + // aapt resource value: 4 + public const int CollapsingToolbarLayout_expandedTitleMargin = 4; // aapt resource value: 5 public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - // aapt resource value: 4 - public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 4; - - // aapt resource value: 2 - public const int CollapsingToolbarLayout_expandedTitleMarginStart = 2; - - // aapt resource value: 3 - public const int CollapsingToolbarLayout_expandedTitleMarginTop = 3; - // aapt resource value: 6 - public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 6; + public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - // aapt resource value: 12 - public const int CollapsingToolbarLayout_scrimAnimationDuration = 12; + // aapt resource value: 7 + public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - // aapt resource value: 11 - public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; + // aapt resource value: 8 + public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; // aapt resource value: 9 - public const int CollapsingToolbarLayout_statusBarScrim = 9; - - // aapt resource value: 0 - public const int CollapsingToolbarLayout_title = 0; - - // aapt resource value: 15 - public const int CollapsingToolbarLayout_titleEnabled = 15; - - // aapt resource value: 10 - public const int CollapsingToolbarLayout_toolbarId = 10; + public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; + // aapt resource value: { 0x7F0300C3,0x7F0300C4 } public static int[] CollapsingToolbarLayout_Layout = new int[] { - 2130772271, - 2130772272}; + 2130903235, + 2130903236}; // aapt resource value: 0 public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; @@ -8501,10 +8379,29 @@ public partial class Styleable // aapt resource value: 1 public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; + // aapt resource value: 10 + public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; + + // aapt resource value: 11 + public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; + + // aapt resource value: 12 + public const int CollapsingToolbarLayout_statusBarScrim = 12; + + // aapt resource value: 13 + public const int CollapsingToolbarLayout_title = 13; + + // aapt resource value: 14 + public const int CollapsingToolbarLayout_titleEnabled = 14; + + // aapt resource value: 15 + public const int CollapsingToolbarLayout_toolbarId = 15; + + // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } public static int[] ColorStateListItem = new int[] { 16843173, 16843551, - 2130772172}; + 2130903079}; // aapt resource value: 2 public const int ColorStateListItem_alpha = 2; @@ -8515,10 +8412,11 @@ public partial class Styleable // aapt resource value: 0 public const int ColorStateListItem_android_color = 0; + // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } public static int[] CompoundButton = new int[] { 16843015, - 2130772173, - 2130772174}; + 2130903113, + 2130903114}; // aapt resource value: 0 public const int CompoundButton_android_button = 0; @@ -8529,50 +8427,53 @@ public partial class Styleable // aapt resource value: 2 public const int CompoundButton_buttonTintMode = 2; + // aapt resource value: { 0x7F0300BD,0x7F030120 } public static int[] CoordinatorLayout = new int[] { - 2130772273, - 2130772274}; + 2130903229, + 2130903328}; // aapt resource value: 0 public const int CoordinatorLayout_keylines = 0; - // aapt resource value: 1 - public const int CoordinatorLayout_statusBarBackground = 1; - + // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } public static int[] CoordinatorLayout_Layout = new int[] { 16842931, - 2130772275, - 2130772276, - 2130772277, - 2130772278, - 2130772279, - 2130772280}; + 2130903232, + 2130903233, + 2130903234, + 2130903237, + 2130903238, + 2130903239}; // aapt resource value: 0 public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - // aapt resource value: 2 - public const int CoordinatorLayout_Layout_layout_anchor = 2; + // aapt resource value: 1 + public const int CoordinatorLayout_Layout_layout_anchor = 1; - // aapt resource value: 4 - public const int CoordinatorLayout_Layout_layout_anchorGravity = 4; + // aapt resource value: 2 + public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - // aapt resource value: 1 - public const int CoordinatorLayout_Layout_layout_behavior = 1; + // aapt resource value: 3 + public const int CoordinatorLayout_Layout_layout_behavior = 3; - // aapt resource value: 6 - public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 6; + // aapt resource value: 4 + public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; // aapt resource value: 5 public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - // aapt resource value: 3 - public const int CoordinatorLayout_Layout_layout_keyline = 3; + // aapt resource value: 6 + public const int CoordinatorLayout_Layout_layout_keyline = 6; + // aapt resource value: 1 + public const int CoordinatorLayout_statusBarBackground = 1; + + // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } public static int[] DesignTheme = new int[] { - 2130772281, - 2130772282, - 2130772283}; + 2130903102, + 2130903103, + 2130903367}; // aapt resource value: 0 public const int DesignTheme_bottomSheetDialogTheme = 0; @@ -8583,113 +8484,100 @@ public partial class Styleable // aapt resource value: 2 public const int DesignTheme_textColorError = 2; + // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } public static int[] DrawerArrowToggle = new int[] { - 2130772175, - 2130772176, - 2130772177, - 2130772178, - 2130772179, - 2130772180, - 2130772181, - 2130772182}; - - // aapt resource value: 4 - public const int DrawerArrowToggle_arrowHeadLength = 4; - - // aapt resource value: 5 - public const int DrawerArrowToggle_arrowShaftLength = 5; - - // aapt resource value: 6 - public const int DrawerArrowToggle_barLength = 6; + 2130903081, + 2130903082, + 2130903094, + 2130903129, + 2130903168, + 2130903205, + 2130903319, + 2130903370}; // aapt resource value: 0 - public const int DrawerArrowToggle_color = 0; + public const int DrawerArrowToggle_arrowHeadLength = 0; + + // aapt resource value: 1 + public const int DrawerArrowToggle_arrowShaftLength = 1; // aapt resource value: 2 - public const int DrawerArrowToggle_drawableSize = 2; + public const int DrawerArrowToggle_barLength = 2; // aapt resource value: 3 - public const int DrawerArrowToggle_gapBetweenBars = 3; + public const int DrawerArrowToggle_color = 3; - // aapt resource value: 1 - public const int DrawerArrowToggle_spinBars = 1; + // aapt resource value: 4 + public const int DrawerArrowToggle_drawableSize = 4; + + // aapt resource value: 5 + public const int DrawerArrowToggle_gapBetweenBars = 5; + + // aapt resource value: 6 + public const int DrawerArrowToggle_spinBars = 6; // aapt resource value: 7 public const int DrawerArrowToggle_thickness = 7; + // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } public static int[] FloatingActionButton = new int[] { - 2130772030, - 2130772246, - 2130772247, - 2130772284, - 2130772285, - 2130772286, - 2130772287, - 2130772288}; - - // aapt resource value: 1 - public const int FloatingActionButton_backgroundTint = 1; - - // aapt resource value: 2 - public const int FloatingActionButton_backgroundTintMode = 2; - - // aapt resource value: 6 - public const int FloatingActionButton_borderWidth = 6; + 2130903092, + 2130903093, + 2130903100, + 2130903175, + 2130903188, + 2130903294, + 2130903304, + 2130903399}; // aapt resource value: 0 - public const int FloatingActionButton_elevation = 0; - - // aapt resource value: 4 - public const int FloatingActionButton_fabSize = 4; - - // aapt resource value: 5 - public const int FloatingActionButton_pressedTranslationZ = 5; + public const int FloatingActionButton_backgroundTint = 0; - // aapt resource value: 3 - public const int FloatingActionButton_rippleColor = 3; - - // aapt resource value: 7 - public const int FloatingActionButton_useCompatPadding = 7; + // aapt resource value: 1 + public const int FloatingActionButton_backgroundTintMode = 1; + // aapt resource value: { 0x7F030037 } public static int[] FloatingActionButton_Behavior_Layout = new int[] { - 2130772289}; + 2130903095}; // aapt resource value: 0 public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - public static int[] FontFamily = new int[] { - 2130772330, - 2130772331, - 2130772332, - 2130772333, - 2130772334, - 2130772335}; - - // aapt resource value: 0 - public const int FontFamily_fontProviderAuthority = 0; + // aapt resource value: 2 + public const int FloatingActionButton_borderWidth = 2; // aapt resource value: 3 - public const int FontFamily_fontProviderCerts = 3; + public const int FloatingActionButton_elevation = 3; // aapt resource value: 4 - public const int FontFamily_fontProviderFetchStrategy = 4; + public const int FloatingActionButton_fabSize = 4; // aapt resource value: 5 - public const int FontFamily_fontProviderFetchTimeout = 5; + public const int FloatingActionButton_pressedTranslationZ = 5; - // aapt resource value: 1 - public const int FontFamily_fontProviderPackage = 1; + // aapt resource value: 6 + public const int FloatingActionButton_rippleColor = 6; - // aapt resource value: 2 - public const int FontFamily_fontProviderQuery = 2; + // aapt resource value: 7 + public const int FloatingActionButton_useCompatPadding = 7; + // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } + public static int[] FontFamily = new int[] { + 2130903196, + 2130903197, + 2130903198, + 2130903199, + 2130903200, + 2130903201}; + + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } public static int[] FontFamilyFont = new int[] { 16844082, 16844083, 16844095, - 2130772336, - 2130772337, - 2130772338}; + 2130903194, + 2130903202, + 2130903203}; // aapt resource value: 0 public const int FontFamilyFont_android_font = 0; @@ -8700,19 +8588,38 @@ public partial class Styleable // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; + // aapt resource value: 3 + public const int FontFamilyFont_font = 3; + // aapt resource value: 4 - public const int FontFamilyFont_font = 4; + public const int FontFamilyFont_fontStyle = 4; + + // aapt resource value: 5 + public const int FontFamilyFont_fontWeight = 5; + + // aapt resource value: 0 + public const int FontFamily_fontProviderAuthority = 0; + + // aapt resource value: 1 + public const int FontFamily_fontProviderCerts = 1; + + // aapt resource value: 2 + public const int FontFamily_fontProviderFetchStrategy = 2; // aapt resource value: 3 - public const int FontFamilyFont_fontStyle = 3; + public const int FontFamily_fontProviderFetchTimeout = 3; + + // aapt resource value: 4 + public const int FontFamily_fontProviderPackage = 4; // aapt resource value: 5 - public const int FontFamilyFont_fontWeight = 5; + public const int FontFamily_fontProviderQuery = 5; + // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } public static int[] ForegroundLinearLayout = new int[] { 16843017, 16843264, - 2130772290}; + 2130903204}; // aapt resource value: 0 public const int ForegroundLinearLayout_android_foreground = 0; @@ -8723,16 +8630,17 @@ public partial class Styleable // aapt resource value: 2 public const int ForegroundLinearLayout_foregroundInsidePadding = 2; + // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } public static int[] LinearLayoutCompat = new int[] { 16842927, 16842948, 16843046, 16843047, 16843048, - 2130772013, - 2130772183, - 2130772184, - 2130772185}; + 2130903164, + 2130903166, + 2130903257, + 2130903314}; // aapt resource value: 2 public const int LinearLayoutCompat_android_baselineAligned = 2; @@ -8752,15 +8660,10 @@ public partial class Styleable // aapt resource value: 5 public const int LinearLayoutCompat_divider = 5; - // aapt resource value: 8 - public const int LinearLayoutCompat_dividerPadding = 8; - // aapt resource value: 6 - public const int LinearLayoutCompat_measureWithLargestChild = 6; - - // aapt resource value: 7 - public const int LinearLayoutCompat_showDividers = 7; + public const int LinearLayoutCompat_dividerPadding = 6; + // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } public static int[] LinearLayoutCompat_Layout = new int[] { 16842931, 16842996, @@ -8779,6 +8682,13 @@ public partial class Styleable // aapt resource value: 1 public const int LinearLayoutCompat_Layout_android_layout_width = 1; + // aapt resource value: 7 + public const int LinearLayoutCompat_measureWithLargestChild = 7; + + // aapt resource value: 8 + public const int LinearLayoutCompat_showDividers = 8; + + // aapt resource value: { 0x10102AC,0x10102AD } public static int[] ListPopupWindow = new int[] { 16843436, 16843437}; @@ -8789,11 +8699,12 @@ public partial class Styleable // aapt resource value: 1 public const int ListPopupWindow_android_dropDownVerticalOffset = 1; + // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } public static int[] MediaRouteButton = new int[] { 16843071, 16843072, - 2130771989, - 2130771990}; + 2130903187, + 2130903260}; // aapt resource value: 1 public const int MediaRouteButton_android_minHeight = 1; @@ -8807,6 +8718,7 @@ public partial class Styleable // aapt resource value: 3 public const int MediaRouteButton_mediaRouteButtonTint = 3; + // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } public static int[] MenuGroup = new int[] { 16842766, 16842960, @@ -8833,6 +8745,7 @@ public partial class Styleable // aapt resource value: 2 public const int MenuGroup_android_visible = 2; + // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } public static int[] MenuItem = new int[] { 16842754, 16842766, @@ -8847,28 +8760,28 @@ public partial class Styleable 16843236, 16843237, 16843375, - 2130772186, - 2130772187, - 2130772188, - 2130772189, - 2130772190, - 2130772191, - 2130772192, - 2130772193, - 2130772194, - 2130772195}; + 2130903053, + 2130903071, + 2130903072, + 2130903080, + 2130903141, + 2130903216, + 2130903217, + 2130903276, + 2130903313, + 2130903395}; - // aapt resource value: 16 - public const int MenuItem_actionLayout = 16; + // aapt resource value: 13 + public const int MenuItem_actionLayout = 13; - // aapt resource value: 18 - public const int MenuItem_actionProviderClass = 18; + // aapt resource value: 14 + public const int MenuItem_actionProviderClass = 14; - // aapt resource value: 17 - public const int MenuItem_actionViewClass = 17; + // aapt resource value: 15 + public const int MenuItem_actionViewClass = 15; - // aapt resource value: 13 - public const int MenuItem_alphabeticModifiers = 13; + // aapt resource value: 16 + public const int MenuItem_alphabeticModifiers = 16; // aapt resource value: 9 public const int MenuItem_android_alphabeticShortcut = 9; @@ -8909,24 +8822,25 @@ public partial class Styleable // aapt resource value: 4 public const int MenuItem_android_visible = 4; - // aapt resource value: 19 - public const int MenuItem_contentDescription = 19; + // aapt resource value: 17 + public const int MenuItem_contentDescription = 17; - // aapt resource value: 21 - public const int MenuItem_iconTint = 21; + // aapt resource value: 18 + public const int MenuItem_iconTint = 18; - // aapt resource value: 22 - public const int MenuItem_iconTintMode = 22; + // aapt resource value: 19 + public const int MenuItem_iconTintMode = 19; - // aapt resource value: 14 - public const int MenuItem_numericModifiers = 14; + // aapt resource value: 20 + public const int MenuItem_numericModifiers = 20; - // aapt resource value: 15 - public const int MenuItem_showAsAction = 15; + // aapt resource value: 21 + public const int MenuItem_showAsAction = 21; - // aapt resource value: 20 - public const int MenuItem_tooltipText = 20; + // aapt resource value: 22 + public const int MenuItem_tooltipText = 22; + // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } public static int[] MenuView = new int[] { 16842926, 16843052, @@ -8935,8 +8849,8 @@ public partial class Styleable 16843055, 16843056, 16843057, - 2130772196, - 2130772197}; + 2130903293, + 2130903330}; // aapt resource value: 4 public const int MenuView_android_headerBackground = 4; @@ -8965,17 +8879,18 @@ public partial class Styleable // aapt resource value: 8 public const int MenuView_subMenuArrow = 8; + // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } public static int[] NavigationView = new int[] { 16842964, 16842973, 16843039, - 2130772030, - 2130772291, - 2130772292, - 2130772293, - 2130772294, - 2130772295, - 2130772296}; + 2130903175, + 2130903207, + 2130903224, + 2130903225, + 2130903227, + 2130903228, + 2130903271}; // aapt resource value: 0 public const int NavigationView_android_background = 0; @@ -8989,28 +8904,36 @@ public partial class Styleable // aapt resource value: 3 public const int NavigationView_elevation = 3; - // aapt resource value: 9 - public const int NavigationView_headerLayout = 9; - - // aapt resource value: 7 - public const int NavigationView_itemBackground = 7; + // aapt resource value: 4 + public const int NavigationView_headerLayout = 4; // aapt resource value: 5 - public const int NavigationView_itemIconTint = 5; - - // aapt resource value: 8 - public const int NavigationView_itemTextAppearance = 8; + public const int NavigationView_itemBackground = 5; // aapt resource value: 6 - public const int NavigationView_itemTextColor = 6; + public const int NavigationView_itemIconTint = 6; - // aapt resource value: 4 - public const int NavigationView_menu = 4; + // aapt resource value: 7 + public const int NavigationView_itemTextAppearance = 7; + + // aapt resource value: 8 + public const int NavigationView_itemTextColor = 8; + + // aapt resource value: 9 + public const int NavigationView_menu = 9; + // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } public static int[] PopupWindow = new int[] { 16843126, 16843465, - 2130772198}; + 2130903277}; + + // aapt resource value: { 0x7F03011D } + public static int[] PopupWindowBackgroundState = new int[] { + 2130903325}; + + // aapt resource value: 0 + public const int PopupWindowBackgroundState_state_above_anchor = 0; // aapt resource value: 1 public const int PopupWindow_android_popupAnimationStyle = 1; @@ -9021,15 +8944,10 @@ public partial class Styleable // aapt resource value: 2 public const int PopupWindow_overlapAnchor = 2; - public static int[] PopupWindowBackgroundState = new int[] { - 2130772199}; - - // aapt resource value: 0 - public const int PopupWindowBackgroundState_state_above_anchor = 0; - + // aapt resource value: { 0x7F0300EE,0x7F0300F1 } public static int[] RecycleListView = new int[] { - 2130772200, - 2130772201}; + 2130903278, + 2130903281}; // aapt resource value: 0 public const int RecycleListView_paddingBottomNoButtons = 0; @@ -9037,18 +8955,19 @@ public partial class Styleable // aapt resource value: 1 public const int RecycleListView_paddingTopNoTitle = 1; + // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } public static int[] RecyclerView = new int[] { 16842948, 16842993, - 2130771968, - 2130771969, - 2130771970, - 2130771971, - 2130771972, - 2130771973, - 2130771974, - 2130771975, - 2130771976}; + 2130903189, + 2130903190, + 2130903191, + 2130903192, + 2130903193, + 2130903231, + 2130903303, + 2130903318, + 2130903324}; // aapt resource value: 1 public const int RecyclerView_android_descendantFocusability = 1; @@ -9056,63 +8975,66 @@ public partial class Styleable // aapt resource value: 0 public const int RecyclerView_android_orientation = 0; - // aapt resource value: 6 - public const int RecyclerView_fastScrollEnabled = 6; + // aapt resource value: 2 + public const int RecyclerView_fastScrollEnabled = 2; - // aapt resource value: 9 - public const int RecyclerView_fastScrollHorizontalThumbDrawable = 9; + // aapt resource value: 3 + public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - // aapt resource value: 10 - public const int RecyclerView_fastScrollHorizontalTrackDrawable = 10; + // aapt resource value: 4 + public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - // aapt resource value: 7 - public const int RecyclerView_fastScrollVerticalThumbDrawable = 7; + // aapt resource value: 5 + public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - // aapt resource value: 8 - public const int RecyclerView_fastScrollVerticalTrackDrawable = 8; + // aapt resource value: 6 + public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - // aapt resource value: 2 - public const int RecyclerView_layoutManager = 2; + // aapt resource value: 7 + public const int RecyclerView_layoutManager = 7; - // aapt resource value: 4 - public const int RecyclerView_reverseLayout = 4; + // aapt resource value: 8 + public const int RecyclerView_reverseLayout = 8; - // aapt resource value: 3 - public const int RecyclerView_spanCount = 3; + // aapt resource value: 9 + public const int RecyclerView_spanCount = 9; - // aapt resource value: 5 - public const int RecyclerView_stackFromEnd = 5; + // aapt resource value: 10 + public const int RecyclerView_stackFromEnd = 10; + // aapt resource value: { 0x7F0300B6 } public static int[] ScrimInsetsFrameLayout = new int[] { - 2130772297}; + 2130903222}; // aapt resource value: 0 public const int ScrimInsetsFrameLayout_insetForeground = 0; + // aapt resource value: { 0x7F030039 } public static int[] ScrollingViewBehavior_Layout = new int[] { - 2130772298}; + 2130903097}; // aapt resource value: 0 public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; + // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } public static int[] SearchView = new int[] { 16842970, 16843039, 16843296, 16843364, - 2130772202, - 2130772203, - 2130772204, - 2130772205, - 2130772206, - 2130772207, - 2130772208, - 2130772209, - 2130772210, - 2130772211, - 2130772212, - 2130772213, - 2130772214}; + 2130903123, + 2130903140, + 2130903160, + 2130903206, + 2130903218, + 2130903230, + 2130903297, + 2130903298, + 2130903307, + 2130903308, + 2130903331, + 2130903336, + 2130903400}; // aapt resource value: 0 public const int SearchView_android_focusable = 0; @@ -9126,49 +9048,50 @@ public partial class Styleable // aapt resource value: 1 public const int SearchView_android_maxWidth = 1; - // aapt resource value: 8 - public const int SearchView_closeIcon = 8; - - // aapt resource value: 13 - public const int SearchView_commitIcon = 13; + // aapt resource value: 4 + public const int SearchView_closeIcon = 4; - // aapt resource value: 7 - public const int SearchView_defaultQueryHint = 7; + // aapt resource value: 5 + public const int SearchView_commitIcon = 5; - // aapt resource value: 9 - public const int SearchView_goIcon = 9; + // aapt resource value: 6 + public const int SearchView_defaultQueryHint = 6; - // aapt resource value: 5 - public const int SearchView_iconifiedByDefault = 5; + // aapt resource value: 7 + public const int SearchView_goIcon = 7; - // aapt resource value: 4 - public const int SearchView_layout = 4; + // aapt resource value: 8 + public const int SearchView_iconifiedByDefault = 8; - // aapt resource value: 15 - public const int SearchView_queryBackground = 15; + // aapt resource value: 9 + public const int SearchView_layout = 9; - // aapt resource value: 6 - public const int SearchView_queryHint = 6; + // aapt resource value: 10 + public const int SearchView_queryBackground = 10; // aapt resource value: 11 - public const int SearchView_searchHintIcon = 11; + public const int SearchView_queryHint = 11; - // aapt resource value: 10 - public const int SearchView_searchIcon = 10; + // aapt resource value: 12 + public const int SearchView_searchHintIcon = 12; - // aapt resource value: 16 - public const int SearchView_submitBackground = 16; + // aapt resource value: 13 + public const int SearchView_searchIcon = 13; // aapt resource value: 14 - public const int SearchView_suggestionRowLayout = 14; + public const int SearchView_submitBackground = 14; - // aapt resource value: 12 - public const int SearchView_voiceIcon = 12; + // aapt resource value: 15 + public const int SearchView_suggestionRowLayout = 15; + + // aapt resource value: 16 + public const int SearchView_voiceIcon = 16; + // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } public static int[] SnackbarLayout = new int[] { 16843039, - 2130772030, - 2130772299}; + 2130903175, + 2130903255}; // aapt resource value: 0 public const int SnackbarLayout_android_maxWidth = 0; @@ -9179,12 +9102,13 @@ public partial class Styleable // aapt resource value: 2 public const int SnackbarLayout_maxActionInlineWidth = 2; + // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } public static int[] Spinner = new int[] { 16842930, 16843126, 16843131, 16843362, - 2130772031}; + 2130903291}; // aapt resource value: 3 public const int Spinner_android_dropDownWidth = 3; @@ -9201,21 +9125,22 @@ public partial class Styleable // aapt resource value: 4 public const int Spinner_popupTheme = 4; + // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } public static int[] SwitchCompat = new int[] { 16843044, 16843045, 16843074, - 2130772215, - 2130772216, - 2130772217, - 2130772218, - 2130772219, - 2130772220, - 2130772221, - 2130772222, - 2130772223, - 2130772224, - 2130772225}; + 2130903315, + 2130903322, + 2130903337, + 2130903338, + 2130903340, + 2130903371, + 2130903372, + 2130903373, + 2130903396, + 2130903397, + 2130903398}; // aapt resource value: 1 public const int SwitchCompat_android_textOff = 1; @@ -9226,39 +9151,40 @@ public partial class Styleable // aapt resource value: 2 public const int SwitchCompat_android_thumb = 2; - // aapt resource value: 13 - public const int SwitchCompat_showText = 13; + // aapt resource value: 3 + public const int SwitchCompat_showText = 3; - // aapt resource value: 12 - public const int SwitchCompat_splitTrack = 12; + // aapt resource value: 4 + public const int SwitchCompat_splitTrack = 4; - // aapt resource value: 10 - public const int SwitchCompat_switchMinWidth = 10; + // aapt resource value: 5 + public const int SwitchCompat_switchMinWidth = 5; - // aapt resource value: 11 - public const int SwitchCompat_switchPadding = 11; + // aapt resource value: 6 + public const int SwitchCompat_switchPadding = 6; - // aapt resource value: 9 - public const int SwitchCompat_switchTextAppearance = 9; + // aapt resource value: 7 + public const int SwitchCompat_switchTextAppearance = 7; // aapt resource value: 8 public const int SwitchCompat_thumbTextPadding = 8; - // aapt resource value: 3 - public const int SwitchCompat_thumbTint = 3; + // aapt resource value: 9 + public const int SwitchCompat_thumbTint = 9; - // aapt resource value: 4 - public const int SwitchCompat_thumbTintMode = 4; + // aapt resource value: 10 + public const int SwitchCompat_thumbTintMode = 10; - // aapt resource value: 5 - public const int SwitchCompat_track = 5; + // aapt resource value: 11 + public const int SwitchCompat_track = 11; - // aapt resource value: 6 - public const int SwitchCompat_trackTint = 6; + // aapt resource value: 12 + public const int SwitchCompat_trackTint = 12; - // aapt resource value: 7 - public const int SwitchCompat_trackTintMode = 7; + // aapt resource value: 13 + public const int SwitchCompat_trackTintMode = 13; + // aapt resource value: { 0x1010002,0x10100F2,0x101014F } public static int[] TabItem = new int[] { 16842754, 16842994, @@ -9273,56 +9199,57 @@ public partial class Styleable // aapt resource value: 2 public const int TabItem_android_text = 2; + // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } public static int[] TabLayout = new int[] { - 2130772300, - 2130772301, - 2130772302, - 2130772303, - 2130772304, - 2130772305, - 2130772306, - 2130772307, - 2130772308, - 2130772309, - 2130772310, - 2130772311, - 2130772312, - 2130772313, - 2130772314, - 2130772315}; + 2130903341, + 2130903342, + 2130903343, + 2130903344, + 2130903345, + 2130903346, + 2130903347, + 2130903348, + 2130903349, + 2130903350, + 2130903351, + 2130903352, + 2130903353, + 2130903354, + 2130903355, + 2130903356}; - // aapt resource value: 3 - public const int TabLayout_tabBackground = 3; + // aapt resource value: 0 + public const int TabLayout_tabBackground = 0; - // aapt resource value: 2 - public const int TabLayout_tabContentStart = 2; + // aapt resource value: 1 + public const int TabLayout_tabContentStart = 1; - // aapt resource value: 5 - public const int TabLayout_tabGravity = 5; + // aapt resource value: 2 + public const int TabLayout_tabGravity = 2; - // aapt resource value: 0 - public const int TabLayout_tabIndicatorColor = 0; + // aapt resource value: 3 + public const int TabLayout_tabIndicatorColor = 3; - // aapt resource value: 1 - public const int TabLayout_tabIndicatorHeight = 1; + // aapt resource value: 4 + public const int TabLayout_tabIndicatorHeight = 4; - // aapt resource value: 7 - public const int TabLayout_tabMaxWidth = 7; + // aapt resource value: 5 + public const int TabLayout_tabMaxWidth = 5; // aapt resource value: 6 public const int TabLayout_tabMinWidth = 6; - // aapt resource value: 4 - public const int TabLayout_tabMode = 4; + // aapt resource value: 7 + public const int TabLayout_tabMode = 7; - // aapt resource value: 15 - public const int TabLayout_tabPadding = 15; + // aapt resource value: 8 + public const int TabLayout_tabPadding = 8; - // aapt resource value: 14 - public const int TabLayout_tabPaddingBottom = 14; + // aapt resource value: 9 + public const int TabLayout_tabPaddingBottom = 9; - // aapt resource value: 13 - public const int TabLayout_tabPaddingEnd = 13; + // aapt resource value: 10 + public const int TabLayout_tabPaddingEnd = 10; // aapt resource value: 11 public const int TabLayout_tabPaddingStart = 11; @@ -9330,15 +9257,16 @@ public partial class Styleable // aapt resource value: 12 public const int TabLayout_tabPaddingTop = 12; - // aapt resource value: 10 - public const int TabLayout_tabSelectedTextColor = 10; + // aapt resource value: 13 + public const int TabLayout_tabSelectedTextColor = 13; - // aapt resource value: 8 - public const int TabLayout_tabTextAppearance = 8; + // aapt resource value: 14 + public const int TabLayout_tabTextAppearance = 14; - // aapt resource value: 9 - public const int TabLayout_tabTextColor = 9; + // aapt resource value: 15 + public const int TabLayout_tabTextColor = 15; + // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } public static int[] TextAppearance = new int[] { 16842901, 16842902, @@ -9351,8 +9279,8 @@ public partial class Styleable 16843107, 16843108, 16843692, - 2130772047, - 2130772053}; + 2130903195, + 2130903357}; // aapt resource value: 10 public const int TextAppearance_android_fontFamily = 10; @@ -9387,29 +9315,30 @@ public partial class Styleable // aapt resource value: 1 public const int TextAppearance_android_typeface = 1; - // aapt resource value: 12 - public const int TextAppearance_fontFamily = 12; - // aapt resource value: 11 - public const int TextAppearance_textAllCaps = 11; + public const int TextAppearance_fontFamily = 11; + // aapt resource value: 12 + public const int TextAppearance_textAllCaps = 12; + + // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } public static int[] TextInputLayout = new int[] { 16842906, 16843088, - 2130772316, - 2130772317, - 2130772318, - 2130772319, - 2130772320, - 2130772321, - 2130772322, - 2130772323, - 2130772324, - 2130772325, - 2130772326, - 2130772327, - 2130772328, - 2130772329}; + 2130903155, + 2130903156, + 2130903157, + 2130903158, + 2130903176, + 2130903177, + 2130903210, + 2130903211, + 2130903212, + 2130903285, + 2130903286, + 2130903287, + 2130903288, + 2130903289}; // aapt resource value: 1 public const int TextInputLayout_android_hint = 1; @@ -9417,41 +9346,41 @@ public partial class Styleable // aapt resource value: 0 public const int TextInputLayout_android_textColorHint = 0; - // aapt resource value: 6 - public const int TextInputLayout_counterEnabled = 6; - - // aapt resource value: 7 - public const int TextInputLayout_counterMaxLength = 7; - - // aapt resource value: 9 - public const int TextInputLayout_counterOverflowTextAppearance = 9; + // aapt resource value: 2 + public const int TextInputLayout_counterEnabled = 2; - // aapt resource value: 8 - public const int TextInputLayout_counterTextAppearance = 8; + // aapt resource value: 3 + public const int TextInputLayout_counterMaxLength = 3; // aapt resource value: 4 - public const int TextInputLayout_errorEnabled = 4; + public const int TextInputLayout_counterOverflowTextAppearance = 4; // aapt resource value: 5 - public const int TextInputLayout_errorTextAppearance = 5; + public const int TextInputLayout_counterTextAppearance = 5; - // aapt resource value: 10 - public const int TextInputLayout_hintAnimationEnabled = 10; + // aapt resource value: 6 + public const int TextInputLayout_errorEnabled = 6; - // aapt resource value: 3 - public const int TextInputLayout_hintEnabled = 3; + // aapt resource value: 7 + public const int TextInputLayout_errorTextAppearance = 7; - // aapt resource value: 2 - public const int TextInputLayout_hintTextAppearance = 2; + // aapt resource value: 8 + public const int TextInputLayout_hintAnimationEnabled = 8; - // aapt resource value: 13 - public const int TextInputLayout_passwordToggleContentDescription = 13; + // aapt resource value: 9 + public const int TextInputLayout_hintEnabled = 9; + + // aapt resource value: 10 + public const int TextInputLayout_hintTextAppearance = 10; + + // aapt resource value: 11 + public const int TextInputLayout_passwordToggleContentDescription = 11; // aapt resource value: 12 public const int TextInputLayout_passwordToggleDrawable = 12; - // aapt resource value: 11 - public const int TextInputLayout_passwordToggleEnabled = 11; + // aapt resource value: 13 + public const int TextInputLayout_passwordToggleEnabled = 13; // aapt resource value: 14 public const int TextInputLayout_passwordToggleTint = 14; @@ -9459,36 +9388,37 @@ public partial class Styleable // aapt resource value: 15 public const int TextInputLayout_passwordToggleTintMode = 15; + // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } public static int[] Toolbar = new int[] { 16842927, 16843072, - 2130772005, - 2130772008, - 2130772012, - 2130772024, - 2130772025, - 2130772026, - 2130772027, - 2130772028, - 2130772029, - 2130772031, - 2130772226, - 2130772227, - 2130772228, - 2130772229, - 2130772230, - 2130772231, - 2130772232, - 2130772233, - 2130772234, - 2130772235, - 2130772236, - 2130772237, - 2130772238, - 2130772239, - 2130772240, - 2130772241, - 2130772242}; + 2130903109, + 2130903125, + 2130903126, + 2130903142, + 2130903143, + 2130903144, + 2130903145, + 2130903146, + 2130903147, + 2130903253, + 2130903254, + 2130903256, + 2130903273, + 2130903274, + 2130903291, + 2130903332, + 2130903333, + 2130903334, + 2130903379, + 2130903381, + 2130903382, + 2130903383, + 2130903384, + 2130903385, + 2130903386, + 2130903387, + 2130903388}; // aapt resource value: 0 public const int Toolbar_android_gravity = 0; @@ -9496,20 +9426,20 @@ public partial class Styleable // aapt resource value: 1 public const int Toolbar_android_minHeight = 1; - // aapt resource value: 21 - public const int Toolbar_buttonGravity = 21; + // aapt resource value: 2 + public const int Toolbar_buttonGravity = 2; - // aapt resource value: 23 - public const int Toolbar_collapseContentDescription = 23; + // aapt resource value: 3 + public const int Toolbar_collapseContentDescription = 3; - // aapt resource value: 22 - public const int Toolbar_collapseIcon = 22; + // aapt resource value: 4 + public const int Toolbar_collapseIcon = 4; - // aapt resource value: 6 - public const int Toolbar_contentInsetEnd = 6; + // aapt resource value: 5 + public const int Toolbar_contentInsetEnd = 5; - // aapt resource value: 10 - public const int Toolbar_contentInsetEndWithActions = 10; + // aapt resource value: 6 + public const int Toolbar_contentInsetEndWithActions = 6; // aapt resource value: 7 public const int Toolbar_contentInsetLeft = 7; @@ -9517,92 +9447,79 @@ public partial class Styleable // aapt resource value: 8 public const int Toolbar_contentInsetRight = 8; - // aapt resource value: 5 - public const int Toolbar_contentInsetStart = 5; - // aapt resource value: 9 - public const int Toolbar_contentInsetStartWithNavigation = 9; - - // aapt resource value: 4 - public const int Toolbar_logo = 4; - - // aapt resource value: 26 - public const int Toolbar_logoDescription = 26; + public const int Toolbar_contentInsetStart = 9; - // aapt resource value: 20 - public const int Toolbar_maxButtonHeight = 20; - - // aapt resource value: 25 - public const int Toolbar_navigationContentDescription = 25; - - // aapt resource value: 24 - public const int Toolbar_navigationIcon = 24; + // aapt resource value: 10 + public const int Toolbar_contentInsetStartWithNavigation = 10; // aapt resource value: 11 - public const int Toolbar_popupTheme = 11; + public const int Toolbar_logo = 11; - // aapt resource value: 3 - public const int Toolbar_subtitle = 3; + // aapt resource value: 12 + public const int Toolbar_logoDescription = 12; // aapt resource value: 13 - public const int Toolbar_subtitleTextAppearance = 13; - - // aapt resource value: 28 - public const int Toolbar_subtitleTextColor = 28; - - // aapt resource value: 2 - public const int Toolbar_title = 2; + public const int Toolbar_maxButtonHeight = 13; // aapt resource value: 14 - public const int Toolbar_titleMargin = 14; + public const int Toolbar_navigationContentDescription = 14; - // aapt resource value: 18 - public const int Toolbar_titleMarginBottom = 18; + // aapt resource value: 15 + public const int Toolbar_navigationIcon = 15; // aapt resource value: 16 - public const int Toolbar_titleMarginEnd = 16; - - // aapt resource value: 15 - public const int Toolbar_titleMarginStart = 15; + public const int Toolbar_popupTheme = 16; // aapt resource value: 17 - public const int Toolbar_titleMarginTop = 17; + public const int Toolbar_subtitle = 17; + + // aapt resource value: 18 + public const int Toolbar_subtitleTextAppearance = 18; // aapt resource value: 19 - public const int Toolbar_titleMargins = 19; + public const int Toolbar_subtitleTextColor = 19; - // aapt resource value: 12 - public const int Toolbar_titleTextAppearance = 12; + // aapt resource value: 20 + public const int Toolbar_title = 20; - // aapt resource value: 27 - public const int Toolbar_titleTextColor = 27; + // aapt resource value: 21 + public const int Toolbar_titleMargin = 21; - public static int[] View = new int[] { - 16842752, - 16842970, - 2130772243, - 2130772244, - 2130772245}; + // aapt resource value: 22 + public const int Toolbar_titleMarginBottom = 22; - // aapt resource value: 1 - public const int View_android_focusable = 1; + // aapt resource value: 23 + public const int Toolbar_titleMarginEnd = 23; - // aapt resource value: 0 - public const int View_android_theme = 0; + // aapt resource value: 26 + public const int Toolbar_titleMargins = 26; - // aapt resource value: 3 - public const int View_paddingEnd = 3; + // aapt resource value: 24 + public const int Toolbar_titleMarginStart = 24; - // aapt resource value: 2 - public const int View_paddingStart = 2; + // aapt resource value: 25 + public const int Toolbar_titleMarginTop = 25; - // aapt resource value: 4 - public const int View_theme = 4; + // aapt resource value: 27 + public const int Toolbar_titleTextAppearance = 27; + + // aapt resource value: 28 + public const int Toolbar_titleTextColor = 28; + // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } + public static int[] View = new int[] { + 16842752, + 16842970, + 2130903279, + 2130903280, + 2130903369}; + + // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } public static int[] ViewBackgroundHelper = new int[] { 16842964, - 2130772246, - 2130772247}; + 2130903092, + 2130903093}; // aapt resource value: 0 public const int ViewBackgroundHelper_android_background = 0; @@ -9613,6 +9530,7 @@ public partial class Styleable // aapt resource value: 2 public const int ViewBackgroundHelper_backgroundTintMode = 2; + // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } public static int[] ViewStubCompat = new int[] { 16842960, 16842994, @@ -9627,6 +9545,21 @@ public partial class Styleable // aapt resource value: 1 public const int ViewStubCompat_android_layout = 1; + // aapt resource value: 1 + public const int View_android_focusable = 1; + + // aapt resource value: 0 + public const int View_android_theme = 0; + + // aapt resource value: 2 + public const int View_paddingEnd = 2; + + // aapt resource value: 3 + public const int View_paddingStart = 3; + + // aapt resource value: 4 + public const int View_theme = 4; + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index 76f4c466..a0dea2cc 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,34 +1,38 @@ using System; -using Common.Logging; -using LaunchDarkly.Client; +using LaunchDarkly.Logging; using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { [Collection("serialize all tests")] public class BaseTest : IDisposable { + protected readonly ILogAdapter testLogging; + protected readonly Logger testLogger; + protected readonly LogCapture logCapture; + public BaseTest() { - LogManager.Adapter = new LogSinkFactoryAdapter(); - TestUtil.ClearClient(); + logCapture = Logs.Capture(); + testLogging = logCapture; + testLogger = logCapture.Logger(""); } - public BaseTest(ITestOutputHelper testOutput) + public BaseTest(ITestOutputHelper testOutput) : this() { - LogManager.Adapter = new LogSinkFactoryAdapter(testOutput.WriteLine); - TestUtil.ClearClient(); + testLogging = Logs.ToMultiple(TestLogging.TestOutputAdapter(testOutput), logCapture); + testLogger = testLogging.Logger(""); } - public void Dispose() + protected void ClearCachedFlags(User user) { - TestUtil.ClearClient(); + PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key, testLogger); } - protected void ClearCachedFlags(User user) + public void Dispose() { - PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key); + TestUtil.ClearClient(); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs index 485231b6..b1731b84 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ConfigurationTest.cs @@ -1,11 +1,14 @@ using System; using System.Net.Http; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class ConfigurationTest : BaseTest { + public ConfigurationTest(ITestOutputHelper testOutput) : base(testOutput) { } + [Fact] public void TestDefaultsFromDefaultFactoryMethod() { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs index e376326f..b5aab2c9 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs @@ -1,21 +1,24 @@ -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { // The DefaultDeviceInfo functionality is also tested by LdClientEndToEndTests.InitWithKeylessAnonUserAddsKeyAndReusesIt(), // which is a more realistic test since it uses a full client instance. However, currently LdClientEndToEndTests can't be // run on every platform, so we'll also test the lower-level logic here. public class DefaultDeviceInfoTests : BaseTest { + public DefaultDeviceInfoTests(ITestOutputHelper testOutput) : base(testOutput) { } + [Fact] public void UniqueDeviceIdGeneratesStableValue() { // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. - ClientIdentifier.ClearCachedClientId(); + ClientIdentifier.ClearCachedClientId(testLogger); - var ddi = new DefaultDeviceInfo(); + var ddi = new DefaultDeviceInfo(testLogger); var id0 = ddi.UniqueDeviceId(); Assert.NotNull(id0); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs new file mode 100644 index 00000000..7c08f1b3 --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs @@ -0,0 +1,14 @@ +using Xunit; + +namespace LaunchDarkly.Sdk.Xamarin +{ + public class ExtensionsTest + { + [Fact] + public void TestUrlSafeBase64Encode() + { + Assert.Equal("eyJrZXkiOiJmb28-YmFyX18_In0=", + @"{""key"":""foo>bar__?""}".UrlSafeBase64Encode()); + } + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs index c446d7b4..9043916e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs @@ -1,7 +1,6 @@ using System; -using LaunchDarkly.Client; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { internal class FeatureFlagBuilder { @@ -15,9 +14,9 @@ internal class FeatureFlagBuilder // same properties. private bool _trackEvents; private bool _trackReason; - private long? _debugEventsUntilDate; + private UnixMillisecondTime? _debugEventsUntilDate; #pragma warning disable 0649 - private EvaluationReason _reason; + private EvaluationReason? _reason; public FeatureFlagBuilder() { @@ -52,7 +51,7 @@ public FeatureFlagBuilder Variation(int? variation) return this; } - public FeatureFlagBuilder Reason(EvaluationReason reason) + public FeatureFlagBuilder Reason(EvaluationReason? reason) { _reason = reason; return this; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index ee27c6d4..d41b6d04 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,21 +1,22 @@ using System; using System.Threading.Tasks; -using LaunchDarkly.Client; -using LaunchDarkly.Xamarin.Tests.HttpHelpers; +using LaunchDarkly.Sdk.Xamarin.HttpHelpers; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { // End-to-end tests of this component against an embedded HTTP server. public class FeatureFlagRequestorTests : BaseTest { + public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput) { } + private const string _mobileKey = "FAKE_KEY"; - // User key constructed to test base64 encoding of 62 and 63, which differ between the standard and "URL and Filename safe" + // User key constructed to test base64 encoding that differs between the standard and "URL and Filename safe" // base64 encodings from RFC4648. We need to use the URL safe encoding for flag requests. - private static readonly User _user = User.WithKey("foo>bar__?"); - private const string _userJson = "{\"key\":\"foo>bar__?\"}"; - private const string _encodedUser = "eyJrZXkiOiJmb28-YmFyX18_In0="; + private static readonly User _user = User.WithKey("foo_bar__?"); + private const string _encodedUser = "eyJrZXkiOiJmb29fYmFyX18_In0="; // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. @@ -42,7 +43,7 @@ string expectedQuery .EvaluationReasons(withReasons) .Build(); - using (var requestor = new FeatureFlagRequestor(config, _user)) + using (var requestor = new FeatureFlagRequestor(config, _user, testLogger)) { var resp = await requestor.FeatureFlagsAsync(); Assert.Equal(200, resp.statusCode); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs deleted file mode 100644 index 4f6cd85c..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Xunit; - -namespace LaunchDarkly.Xamarin.Tests -{ - public class FeatureFlagEventTests : BaseTest - { - [Fact] - public void ReturnsFlagVersionAsVersion() - { - var flag = new FeatureFlagBuilder().FlagVersion(123).Version(456).Build(); - var flagEvent = new FeatureFlagEvent("my-flag", flag); - Assert.Equal(123, flagEvent.EventVersion); - } - - [Fact] - public void FallsBackToVersionAsVersion() - { - var flag = new FeatureFlagBuilder().Version(456).Build(); - var flagEvent = new FeatureFlagEvent("my-flag", flag); - Assert.Equal(456, flagEvent.EventVersion); - } - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs index 0c010166..e0e51aa4 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs @@ -1,7 +1,7 @@ -using LaunchDarkly.Client; -using Xunit; +using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class FlagCacheManagerTests : BaseTest { @@ -13,10 +13,15 @@ public class FlagCacheManagerTests : BaseTest IUserFlagCache deviceCache = new UserFlagInMemoryCache(); IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); - FlagChangedEventManager listenerManager = new FlagChangedEventManager(); + FlagChangedEventManager listenerManager; User user = User.WithKey("someKey"); + public FlagCacheManagerTests(ITestOutputHelper testOutput) : base(testOutput) + { + listenerManager = new FlagChangedEventManager(testLogger); + } + IFlagCacheManager ManagerWithCachedFlags() { var flagCacheManager = new FlagCacheManager(deviceCache, inMemoryCache, listenerManager, user); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs index d44595e6..68a4536f 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FlagChangedEventTests.cs @@ -1,18 +1,20 @@ using System.Collections.Concurrent; using System.Threading; -using LaunchDarkly.Client; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class FlagChangedEventTests : BaseTest { private const string INT_FLAG = "int-flag"; private const string DOUBLE_FLAG = "double-flag"; + public FlagChangedEventTests(ITestOutputHelper testOutput) : base(testOutput) { } + FlagChangedEventManager Manager() { - return new FlagChangedEventManager(); + return new FlagChangedEventManager(testLogger); } [Fact] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs index 6b2ce840..32c78886 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs @@ -10,7 +10,7 @@ using EmbedIO; using EmbedIO.Routing; -namespace LaunchDarkly.Xamarin.Tests.HttpHelpers +namespace LaunchDarkly.Sdk.Xamarin.HttpHelpers { /// /// An abstraction used by implementations to hide the details of the diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs index 277c3f48..0baf07db 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Xunit; -namespace LaunchDarkly.Xamarin.Tests.HttpHelpers +namespace LaunchDarkly.Sdk.Xamarin.HttpHelpers { public class HttpHelpersTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs index c0e499f3..75e87aed 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs @@ -1,8 +1,7 @@ -using LaunchDarkly.Client; -using Moq; +using Moq; using Xunit; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class ILdClientExtensionsTest { @@ -82,7 +81,7 @@ public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() var client = clientMock.Object; var result = client.EnumVariationDetail("key", MyEnum.Blue); - var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, result); } @@ -109,7 +108,7 @@ public void EnumVariationDetailReturnsDefaultValueForNonEnumType() var client = clientMock.Object; var result = client.EnumVariationDetail("key", defaultValue); - var expected = new EvaluationDetail(defaultValue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(defaultValue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, result); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index b1549573..7c0b26a8 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -4,14 +4,12 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Common.Logging; -using LaunchDarkly.Client; -using LaunchDarkly.Xamarin.PlatformSpecific; -using LaunchDarkly.Xamarin.Tests.HttpHelpers; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.HttpHelpers; using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { // Tests of an LDClient instance doing actual HTTP against an embedded server. These aren't intended to cover // every possible type of interaction, since the lower-level component tests like FeatureFlagRequestorTests @@ -103,16 +101,13 @@ public void InitCanTimeOutSync() var handler = Handlers.DelayBefore(TimeSpan.FromSeconds(2), SetupResponse(_flagData1, UpdateMode.Polling)); using (var server = TestHttpServer.Start(handler)) { - using (var log = new LogSinkScope()) + var config = BaseConfig(server.Uri, builder => builder.IsStreamingEnabled(false)); + using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { - var config = BaseConfig(server.Uri, builder => builder.IsStreamingEnabled(false)); - using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) - { - Assert.False(client.Initialized); - Assert.Null(client.StringVariation(_flagData1.First().Key, null)); - Assert.Contains(log.Messages, m => m.Level == LogLevel.Warn && - m.Text == "Client did not successfully initialize within 200 milliseconds."); - } + Assert.False(client.Initialized); + Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + Assert.True(logCapture.HasMessageWithText(Logging.LogLevel.Warn, + "Client did not successfully initialize within 200 milliseconds.")); } } } @@ -123,13 +118,10 @@ public void InitFailsOn401Sync(UpdateMode mode) { using (var server = TestHttpServer.Start(Handlers.Status(401))) { - using (var log = new LogSinkScope()) + var config = BaseConfig(server.Uri, mode); + using (var client = TestUtil.CreateClient(config, _user)) { - var config = BaseConfig(server.Uri, mode); - using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(10))) - { - Assert.False(client.Initialized); - } + Assert.False(client.Initialized); } } } @@ -140,17 +132,14 @@ public async Task InitFailsOn401Async(UpdateMode mode) { using (var server = TestHttpServer.Start(Handlers.Status(401))) { - using (var log = new LogSinkScope()) - { - var config = BaseConfig(server.Uri, mode); + var config = BaseConfig(server.Uri, mode); - // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is - // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that - // will complete successfully with an uninitialized client. - using (var client = await TestUtil.CreateClientAsync(config, _user)) - { - Assert.False(client.Initialized); - } + // Currently the behavior of LdClient.InitAsync is somewhat inconsistent with LdClient.Init if there is + // an unrecoverable error: LdClient.Init throws an exception, but LdClient.InitAsync returns a task that + // will complete successfully with an uninitialized client. + using (var client = await TestUtil.CreateClientAsync(config, _user)) + { + Assert.False(client.Initialized); } } } @@ -167,7 +156,7 @@ public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. - ClientIdentifier.ClearCachedClientId(); + ClientIdentifier.ClearCachedClientId(testLogger); string generatedKey = null; using (var client = await TestUtil.CreateClientAsync(config, anonUser)) @@ -490,6 +479,7 @@ private Configuration BaseConfig(Uri serverUri, Func - + - netcoreapp2.0 - 2.0.0 + net5.0 LaunchDarkly.XamarinSdk.Tests - - - @@ -25,4 +20,9 @@ + + + + + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs index 187ad92c..3e543d17 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEvaluationTests.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using LaunchDarkly.Client; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class LdClientEvaluationTests : BaseTest { @@ -10,9 +10,11 @@ public class LdClientEvaluationTests : BaseTest static readonly string nonexistentFlagKey = "some flag key"; static readonly User user = User.WithKey("userkey"); - private static LdClient ClientWithFlagsJson(string flagsJson) + public LdClientEvaluationTests(ITestOutputHelper testOutput) : base(testOutput) { } + + private LdClient ClientWithFlagsJson(string flagsJson) { - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson).Build(); + var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson).Logging(testLogging).Build(); return TestUtil.CreateClient(config, user); } @@ -226,7 +228,7 @@ public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); using (var client = ClientWithFlagsJson(flagsJson)) { - var expected = new EvaluationDetail(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE)); + var expected = new EvaluationDetail(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 73ecd4e2..8d47c0d9 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -1,18 +1,22 @@ using System; -using LaunchDarkly.Client; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +using static LaunchDarkly.Sdk.Xamarin.Internal.Events.EventProcessorTypes; + +namespace LaunchDarkly.Sdk.Xamarin { public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); private MockEventProcessor eventProcessor = new MockEventProcessor(); + public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { } + public LdClient MakeClient(User user, string flagsJson) { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); - config.EventProcessor(eventProcessor); + config.EventProcessor(eventProcessor).Logging(testLogging); return TestUtil.CreateClient(config.Build(), user); } @@ -39,7 +43,7 @@ public void TrackSendsCustomEvent() e => CheckIdentifyEvent(e, user), e => { CustomEvent ce = Assert.IsType(e); - Assert.Equal("eventkey", ce.Key); + Assert.Equal("eventkey", ce.EventKey); Assert.Equal(user.Key, ce.User.Key); Assert.Equal(LdValue.Null, ce.Data); Assert.Null(ce.MetricValue); @@ -58,7 +62,7 @@ public void TrackWithDataSendsCustomEvent() e => CheckIdentifyEvent(e, user), e => { CustomEvent ce = Assert.IsType(e); - Assert.Equal("eventkey", ce.Key); + Assert.Equal("eventkey", ce.EventKey); Assert.Equal(user.Key, ce.User.Key); Assert.Equal(data, ce.Data); Assert.Null(ce.MetricValue); @@ -78,7 +82,7 @@ public void TrackWithMetricValueSendsCustomEvent() e => CheckIdentifyEvent(e, user), e => { CustomEvent ce = Assert.IsType(e); - Assert.Equal("eventkey", ce.Key); + Assert.Equal("eventkey", ce.EventKey); Assert.Equal(user.Key, ce.User.Key); Assert.Equal(data, ce.Data); Assert.Equal(metricValue, ce.MetricValue); @@ -99,14 +103,14 @@ public void VariationSendsFeatureEventForValidFlag() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); - Assert.Equal(1000, fe.Version); + Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); - Assert.Equal(2000, fe.DebugEventsUntilDate); + Assert.Equal(UnixMillisecondTime.OfMillis(2000), fe.DebugEventsUntilDate); Assert.Null(fe.Reason); }); } @@ -125,11 +129,11 @@ public void FeatureEventUsesFlagVersionIfProvided() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); - Assert.Equal(1500, fe.Version); + Assert.Equal(1500, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); }); } @@ -147,11 +151,11 @@ public void VariationSendsFeatureEventForDefaultValue() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); - Assert.Equal(1000, fe.Version); + Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); @@ -168,11 +172,11 @@ public void VariationSendsFeatureEventForUnknownFlag() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); - Assert.Null(fe.Version); + Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); @@ -195,11 +199,11 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); - Assert.Null(fe.Version); + Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); }); @@ -220,11 +224,11 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); - Assert.Equal(1000, fe.Version); + Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); @@ -249,14 +253,14 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); - Assert.Equal(1000, fe.Version); + Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); - Assert.Equal(2000, fe.DebugEventsUntilDate); + Assert.Equal(UnixMillisecondTime.OfMillis(2000), fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.OffReason, fe.Reason); }); } @@ -268,17 +272,17 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() using (LdClient client = MakeClient(user, "{}")) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); - var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND); + var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.FlagNotFound); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); - Assert.Null(fe.Version); + Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); @@ -298,17 +302,17 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); - var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.CLIENT_NOT_READY); + var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.ClientNotReady); Assert.Equal("b", result.Value); Assert.Equal(expectedReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { - FeatureRequestEvent fe = Assert.IsType(e); - Assert.Equal("flag", fe.Key); + EvaluationEvent fe = Assert.IsType(e); + Assert.Equal("flag", fe.FlagKey); Assert.Equal("b", fe.Value.AsString); Assert.Null(fe.Variation); - Assert.Null(fe.Version); + Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); @@ -317,7 +321,7 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni } } - private void CheckIdentifyEvent(Event e, User u) + private void CheckIdentifyEvent(object e, User u) { IdentifyEvent ie = Assert.IsType(e); Assert.Equal(u.Key, ie.User.Key); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index ace185dd..2dfd3e46 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -2,19 +2,24 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using LaunchDarkly.Client; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class LdClientTests : BaseTest { static readonly string appKey = "some app key"; static readonly User simpleUser = User.WithKey("user-key"); + public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } + + IConfigurationBuilder BaseConfig() => + TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Logging(testLogging); + LdClient Client() { - var configuration = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var configuration = BaseConfig().Build(); return TestUtil.CreateClient(configuration, simpleUser); } @@ -27,21 +32,21 @@ public void CannotCreateClientWithNullConfig() [Fact] public void CannotCreateClientWithNullUser() { - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var config = BaseConfig().Build(); Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); } [Fact] public void CannotCreateClientWithNegativeWaitTime() { - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var config = BaseConfig().Build(); Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.FromMilliseconds(-2))); } [Fact] public void CanCreateClientWithInfiniteWaitTime() { - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var config = BaseConfig().Build(); using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } TestUtil.ClearClient(); } @@ -54,6 +59,7 @@ public async void InitPassesUserToUpdateProcessorFactory() var config = TestUtil.ConfigWithFlagsJson(testUser, appKey, "{}") .UpdateProcessorFactory(stub.AsFactory()) + .Logging(testLogging) .Build(); using (var client = await LdClient.InitAsync(config, testUser)) @@ -72,6 +78,7 @@ public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProces var config = TestUtil.ConfigWithFlagsJson(anonUserIn, appKey, "{}") .UpdateProcessorFactory(stub.AsFactory()) + .Logging(testLogging) .Build(); using (var client = await LdClient.InitAsync(config, anonUserIn)) @@ -134,6 +141,7 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func { - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var config = BaseConfig().Build(); using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); @@ -250,7 +260,7 @@ public void CanCreateNewClientAfterDisposingOfSharedInstance() TestUtil.WithClientLock(() => { TestUtil.ClearClient(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Build(); + var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Logging(testLogging).Build(); using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } Assert.Null(LdClient.Instance); // Dispose() is called automatically at end of "using" block @@ -266,6 +276,7 @@ public void ConnectionChangeShouldStopUpdateProcessor() var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .UpdateProcessorFactory(mockUpdateProc.AsFactory()) .ConnectivityStateManager(mockConnectivityStateManager) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -280,7 +291,9 @@ public void UserWithNullKeyWillHaveUniqueKeySet() var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, appKey, "{}") - .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); + .DeviceInfo(new MockDeviceInfo(uniqueId)) + .Logging(testLogging) + .Build(); using (var client = TestUtil.CreateClient(config, userWithNullKey)) { Assert.Equal(uniqueId, client.User.Key); @@ -294,7 +307,9 @@ public void UserWithEmptyKeyWillHaveUniqueKeySet() var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, appKey, "{}") - .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); + .DeviceInfo(new MockDeviceInfo(uniqueId)) + .Logging(testLogging) + .Build(); using (var client = TestUtil.CreateClient(config, userWithEmptyKey)) { Assert.Equal(uniqueId, client.User.Key); @@ -308,7 +323,9 @@ public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); + .DeviceInfo(new MockDeviceInfo(uniqueId)) + .Logging(testLogging) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(userWithNullKey, TimeSpan.FromSeconds(1)); @@ -323,7 +340,9 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); + .DeviceInfo(new MockDeviceInfo(uniqueId)) + .Logging(testLogging) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(userWithEmptyKey, TimeSpan.FromSeconds(1)); @@ -348,7 +367,9 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() .Build(); var uniqueId = "some-unique-key"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .DeviceInfo(new MockDeviceInfo(uniqueId)).Build(); + .DeviceInfo(new MockDeviceInfo(uniqueId)) + .Logging(testLogging) + .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { client.Identify(user, TimeSpan.FromSeconds(1)); @@ -393,6 +414,7 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() .PersistentStorage(storage) .FlagCacheManager(null) // use actual cache logic, not mock component (even though persistence layer is a mock) .Offline(true) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -411,6 +433,7 @@ public void FlagsAreNotLoadedFromPersistentStorageIfPersistFlagValuesIsFalse() .FlagCacheManager(null) // use actual cache logic, not mock component (even though persistence layer is a mock) .PersistFlagValues(false) .Offline(true) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -427,6 +450,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() .FlagCacheManager(null) .UpdateProcessorFactory(MockPollingProcessor.Factory(flagsJson)) .PersistentStorage(storage) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -442,6 +466,7 @@ public void EventProcessorIsOnlineByDefault() var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .EventProcessor(eventProcessor) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -458,6 +483,7 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() .ConnectivityStateManager(connectivityStateManager) .EventProcessor(eventProcessor) .Offline(true) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { @@ -486,6 +512,7 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .ConnectivityStateManager(connectivityStateManager) .EventProcessor(eventProcessor) + .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs deleted file mode 100644 index db04d43a..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LogSink.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using Common.Logging; -using Common.Logging.Simple; - -namespace LaunchDarkly.Xamarin.Tests -{ - // This mechanism allows unit tests to capture and inspect log output. In order for it to work properly, all - // tests must derive from BaseTest so that the global Common.Logging configuration is modified before any - // test code runs (i.e. before any SDK code has a change to create a logger instance). - // - // For debugging purposes, this also allows you to mirror all log output to the console (which Common.Logging - // does not do by default) by setting the environment variable LD_TEST_LOGS to any value. - - public class LogSink : AbstractSimpleLogger - { - public static LogSink Instance = new LogSink(); - - private static readonly bool _showLogs = Environment.GetEnvironmentVariable("LD_TEST_LOGS") != null; - - public LogSink() : base("", LogLevel.All, false, false, false, "") {} - - protected override void WriteInternal(LogLevel level, object message, Exception exception) - { - var str = message?.ToString(); - try - { - LogSinkFactoryAdapter._logFn?.Invoke(DateTime.Now.ToString() + " [" + level + "] " + str); - } - catch { } - if (_showLogs) - { - Console.WriteLine("*** LOG: [" + level + "] " + str); - } - LogSinkScope.WithCurrent(s => s.Messages.Add(new LogItem { Level = level, Text = str })); - } - } - - public struct LogItem - { - public LogLevel Level { get; set; } - public string Text { get; set; } - } - - public class LogSinkFactoryAdapter : AbstractSimpleLoggerFactoryAdapter - { - public static Action _logFn; - - public LogSinkFactoryAdapter() : this(null) { } - - public LogSinkFactoryAdapter(Action logFn) : base(null) - { - _logFn = logFn; - } - - protected override ILog CreateLogger(string name, LogLevel level, bool showLevel, bool showDateTime, bool showLogName, string dateTimeFormat) - { - return LogSink.Instance; - } - } - - public class LogSinkScope : IDisposable - { - private static Stack _scopes = new Stack(); - - public List Messages = new List(); - - public LogSinkScope() - { - _scopes.Push(this); - } - - public void Dispose() - { - _scopes.Pop(); - } - - public static void WithCurrent(Action a) - { - if (_scopes.TryPeek(out var s)) - { - a(s); - } - } - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs index 10127382..4c504f43 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs @@ -1,8 +1,8 @@ using System; -using LaunchDarkly.Client; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class MobilePollingProcessorTests : BaseTest { @@ -15,13 +15,15 @@ public class MobilePollingProcessorTests : BaseTest IFlagCacheManager mockFlagCacheManager; User user; + public MobilePollingProcessorTests(ITestOutputHelper testOutput) : base(testOutput) { } + IMobileUpdateProcessor Processor() { var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); var stubbedFlagCache = new UserFlagInMemoryCache(); mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); user = User.WithKey("user1Key"); - return new MobilePollingProcessor(mockFeatureFlagRequestor, mockFlagCacheManager, user, TimeSpan.FromSeconds(30), TimeSpan.Zero); + return new MobilePollingProcessor(mockFeatureFlagRequestor, mockFlagCacheManager, user, TimeSpan.FromSeconds(30), TimeSpan.Zero, testLogger); } [Fact] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs index 62ebbba2..f9b450d2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs @@ -1,13 +1,12 @@ using System; -using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; -using LaunchDarkly.Client; -using LaunchDarkly.Common; using LaunchDarkly.EventSource; +using LaunchDarkly.Sdk.Internal.Http; using Xunit; +using Xunit.Abstractions; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class MobileStreamingProcessorTests : BaseTest { @@ -26,7 +25,7 @@ public class MobileStreamingProcessorTests : BaseTest private IFeatureFlagRequestor mockRequestor; private IConfigurationBuilder configBuilder; - public MobileStreamingProcessorTests() + public MobileStreamingProcessorTests(ITestOutputHelper testOutput) : base(testOutput) { mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); @@ -35,15 +34,14 @@ public MobileStreamingProcessorTests() configBuilder = Configuration.BuilderInternal("someKey") .ConnectivityStateManager(new MockConnectivityStateManager(true)) .FlagCacheManager(mockFlagCacheMgr) - .IsStreamingEnabled(true); - - + .IsStreamingEnabled(true) + .Logging(testLogging); } private IMobileUpdateProcessor MobileStreamingProcessorStarted() { IMobileUpdateProcessor processor = new MobileStreamingProcessor(configBuilder.Build(), - mockFlagCacheMgr, mockRequestor, user, eventSourceFactory.Create()); + mockFlagCacheMgr, mockRequestor, user, eventSourceFactory.Create(), testLogger); processor.Start(); return processor; } @@ -66,21 +64,10 @@ string expectedQuery var fakeBaseUri = fakeRootUri + baseUriExtraPath; configBuilder.StreamUri(new Uri(fakeBaseUri)); configBuilder.EvaluationReasons(withReasons); - var config = configBuilder.Build(); MobileStreamingProcessorStarted(); - var props = eventSourceFactory.ReceivedProperties; - Assert.Equal(HttpMethod.Get, props.Method); + Assert.Equal(HttpMethod.Get, eventSourceFactory.ReceivedMethod); Assert.Equal(new Uri(fakeRootUri + expectedPathWithoutUser + encodedUser + expectedQuery), - props.StreamUri); - } - - [Fact] - public void StreamUriInGetModeHasReasonsParameterIfConfigured() - { - var config = configBuilder.EvaluationReasons(true).Build(); - MobileStreamingProcessorStarted(); - var props = eventSourceFactory.ReceivedProperties; - Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + encodedUser + "?withReasons=true"), props.StreamUri); + eventSourceFactory.ReceivedUri); } // Report mode is currently disabled - ch47341 @@ -139,7 +126,7 @@ public void PatchUpdatesFeatureFlag() Assert.Equal(15, intFlagFromPUT); //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlag(), null), "patch"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("patch", UpdatedFlag(), null)); mockEventSource.RaiseMessageRcvd(eventArgs); //verify flag has changed @@ -157,7 +144,7 @@ public void PatchDoesnotUpdateFlagIfVersionIsLower() Assert.Equal(15, intFlagFromPUT); //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(UpdatedFlagWithLowerVersion(), null), "patch"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("patch", UpdatedFlagWithLowerVersion(), null)); mockEventSource.RaiseMessageRcvd(eventArgs); //verify flag has not changed @@ -175,7 +162,7 @@ public void DeleteRemovesFeatureFlag() Assert.Equal(15, intFlagFromPUT); // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlag(), null), "delete"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("delete", DeleteFlag(), null)); mockEventSource.RaiseMessageRcvd(eventArgs); // verify flag was deleted @@ -192,7 +179,7 @@ public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() Assert.Equal(15, intFlagFromPUT); // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(DeleteFlagWithLowerVersion(), null), "delete"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("delete", DeleteFlagWithLowerVersion(), null)); mockEventSource.RaiseMessageRcvd(eventArgs); // verify flag was not deleted @@ -203,7 +190,7 @@ public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() public async void PingCausesPoll() { MobileStreamingProcessorStarted(); - mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("", null), "ping")); + mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("ping", null, null))); var deadline = DateTime.Now.Add(TimeSpan.FromSeconds(5)); while (DateTime.Now < deadline) { @@ -243,15 +230,17 @@ string DeleteFlagWithLowerVersion() void PUTMessageSentToProcessor() { - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent(initialFlagsJson, null), "put"); + MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("put", initialFlagsJson, null)); mockEventSource.RaiseMessageRcvd(eventArgs); } } class TestEventSourceFactory { - public StreamProperties ReceivedProperties { get; private set; } - public IDictionary ReceivedHeaders { get; private set; } + public HttpProperties ReceivedHttpProperties { get; private set; } + public HttpMethod ReceivedMethod { get; private set; } + public Uri ReceivedUri { get; private set; } + public string ReceivedBody { get; private set; } IEventSource _eventSource; public TestEventSourceFactory(IEventSource eventSource) @@ -259,12 +248,14 @@ public TestEventSourceFactory(IEventSource eventSource) _eventSource = eventSource; } - public StreamManager.EventSourceCreator Create() + public MobileStreamingProcessor.EventSourceCreator Create() { - return (StreamProperties sp, IDictionary headers) => + return (httpProperties, method, uri, jsonBody) => { - ReceivedProperties = sp; - ReceivedHeaders = headers; + ReceivedHttpProperties = httpProperties; + ReceivedMethod = method; + ReceivedUri = uri; + ReceivedBody = jsonBody; return _eventSource; }; } @@ -292,6 +283,8 @@ public Task StartAsync() return Task.CompletedTask; } + public void Restart(bool withDelay) { } + public void RaiseMessageRcvd(MessageReceivedEventArgs eventArgs) { MessageReceived(null, eventArgs); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 74b6e2c6..ae62d6b2 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; -using LaunchDarkly.Client; -using LaunchDarkly.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { internal class MockBackgroundModeManager : IBackgroundModeManager { @@ -68,7 +68,7 @@ public string UniqueDeviceId() internal class MockEventProcessor : IEventProcessor { - public List Events = new List(); + public List Events = new List(); public bool Offline = false; public void SetOffline(bool offline) @@ -76,14 +76,18 @@ public void SetOffline(bool offline) Offline = offline; } - public void SendEvent(Event e) - { - Events.Add(e); - } - public void Flush() { } - public void Dispose() { } + public void Dispose() { } + + public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) => + Events.Add(e); + + public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) => + Events.Add(e); + + public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) => + Events.Add(e); } internal class MockFeatureFlagRequestor : IFeatureFlagRequestor diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestLogging.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestLogging.cs new file mode 100644 index 00000000..c90ac86e --- /dev/null +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestLogging.cs @@ -0,0 +1,48 @@ +using System; +using LaunchDarkly.Logging; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Xamarin +{ + /// + /// Allows logging from SDK components to appear in test output. + /// + /// + /// Xunit disables all console output from unit tests, because multiple tests can run in parallel so it + /// would be impossible to see which test produced the output. Instead, it provides an ITestOutputHelper + /// which is passed into your test class automatically if you declare a constructor parameter for it; any + /// output written to this object is buffered on a per-test-method basis, and dumped into the output all at + /// once if the test method fails. We were unable to take advantage of this when we were using Common.Logging, + /// because Common.Logging is configured globally with static methods; but now that we're using our own API, + /// we can direct output from a component to a specific logger instance, which can be redirected to Xunit. + /// See for the simplest way to use this in tests. + /// + public class TestLogging + { + /// + /// Creates an that sends logging to the Xunit output buffer. Use this in + /// contexts where an ILogAdapter is expected instead of an individual logger instance (such as + /// in the SDK client configuration). + /// + /// the that Xunit passed to the test + /// class constructor + /// a log adapter + public static ILogAdapter TestOutputAdapter(ITestOutputHelper testOutputHelper) => + Logs.ToMethod(line => testOutputHelper.WriteLine("LOG OUTPUT >> " + line)); + + /// + /// Creates a that sends logging to the Xunit output buffer. Use this when testing + /// lower-level components that want a specific logger instance instead of an . + /// + /// the that Xunit passed to the test + /// class constructor + /// a logger + public static Logger TestLogger(ITestOutputHelper testOutputHelper) => + TestOutputAdapter(testOutputHelper).Logger(""); + + /// + /// Convenience property for getting a millisecond timestamp string. + /// + public static string TimestampString => DateTime.Now.ToString("O"); + } +} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index 2e945dea..c64e2bd0 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -2,10 +2,9 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using LaunchDarkly.Client; using Xunit; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public static class TestUtil { @@ -112,13 +111,13 @@ public static void ClearClient() }); } - internal static IImmutableDictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) + internal static IImmutableDictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) { var flag = new FeatureFlagBuilder().Value(value).Variation(variation).Reason(reason).Build(); return ImmutableDictionary.Create().SetItem(flagKey, flag); } - internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason reason = null) + internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) { return JsonUtil.EncodeJson(MakeSingleFlagData(flagKey, value, variation, reason)); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs index b8b8e0f6..b043c21d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs @@ -1,7 +1,6 @@ -using LaunchDarkly.Client; -using Xunit; +using Xunit; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class UserFlagCacheTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs index 80c86777..7f72e44e 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/IOsSpecificTests.cs @@ -1,7 +1,6 @@ -using LaunchDarkly.Client; -using Xunit; +using Xunit; -namespace LaunchDarkly.Xamarin.Tests +namespace LaunchDarkly.Sdk.Xamarin { public class IOsSpecificTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 2d1d17e9..be5db5e4 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -69,11 +69,10 @@ - - + @@ -144,9 +143,6 @@ SharedTestCode\FeatureFlagRequestorTests.cs - - SharedTestCode\FeatureFlagTests.cs - SharedTestCode\FlagCacheManagerTests.cs @@ -168,15 +164,15 @@ SharedTestCode\LdClientTests.cs - - SharedTestCode\LogSink.cs - SharedTestCode\MobilePollingProcessorTests.cs SharedTestCode\MockComponents.cs + + SharedTestCode\TestLogging.cs + SharedTestCode\TestUtil.cs From 65411d2eac5aa9040b42830235aa5339b16cf06c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 12 Apr 2021 11:48:05 -0700 Subject: [PATCH 335/499] (2.0 - #2) remove Newtonsoft.Json (#106) --- src/LaunchDarkly.XamarinSdk/FeatureFlag.cs | 102 +++++++++++++++++- src/LaunchDarkly.XamarinSdk/JsonUtil.cs | 67 +++++------- .../LaunchDarkly.XamarinSdk.csproj | 2 - .../MobilePollingProcessor.cs | 2 +- .../MobileStreamingProcessor.cs | 15 ++- .../UserFlagDeviceCache.cs | 4 +- .../LdClientEventTests.cs | 4 +- .../LdClientTests.cs | 2 +- .../MockComponents.cs | 2 +- .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 4 +- 10 files changed, 140 insertions(+), 64 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs index 1616a21a..2c78d6c1 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs @@ -1,9 +1,11 @@ using System; -using Newtonsoft.Json; +using LaunchDarkly.JsonStream; +using LaunchDarkly.Sdk.Json; namespace LaunchDarkly.Sdk.Xamarin { - internal sealed class FeatureFlag : IEquatable + [JsonStreamConverter(typeof(FeatureFlag.JsonConverter))] + internal sealed class FeatureFlag : IEquatable, IJsonSerializable { public readonly LdValue value; public readonly int version; @@ -14,7 +16,6 @@ internal sealed class FeatureFlag : IEquatable public readonly UnixMillisecondTime? debugEventsUntilDate; public readonly EvaluationReason? reason; - [JsonConstructor] public FeatureFlag(LdValue value, int version, int? flagVersion, bool trackEvents, bool trackReason, int? variation, UnixMillisecondTime? debugEventsUntilDate, EvaluationReason? reason) { @@ -38,5 +39,100 @@ public bool Equals(FeatureFlag otherFlag) && debugEventsUntilDate == otherFlag.debugEventsUntilDate && reason.Equals(otherFlag.reason); } + + internal sealed class JsonConverter : IJsonStreamConverter + { + public object ReadJson(ref JReader reader) + { + return ReadJsonValue(ref reader); + } + + public static FeatureFlag ReadJsonValue(ref JReader reader) + { + LdValue value = LdValue.Null; + int version = 0; + int? flagVersion = null; + int? variation = null; + EvaluationReason? reason = null; + bool trackEvents = false; + bool trackReason = false; + UnixMillisecondTime? debugEventsUntilDate = null; + + for (var or = reader.Object(); or.Next(ref reader); ) + { + // The use of multiple == tests instead of switch allows for a slight optimization on + // some platforms where it wouldn't always need to allocate a string for or.Name. See: + // https://github.com/launchdarkly/dotnet-jsonstream/blob/master/src/LaunchDarkly.JsonStream/PropertyNameToken.cs + var name = or.Name; + if (name == "value") + { + value = LdJsonConverters.LdValueConverter.ReadJsonValue(ref reader); + } + else if (name == "version") + { + version = reader.Int(); + } + else if (name == "flagVersion") + { + flagVersion = reader.IntOrNull(); + } + else if (name == "variation") + { + variation = reader.IntOrNull(); + } + else if (name == "reason") + { + reason = LdJsonConverters.EvaluationReasonConverter.ReadJsonNullableValue(ref reader); + } + else if (name == "trackEvents") + { + trackEvents = reader.Bool(); + } + else if (name == "trackReason") + { + trackReason = reader.Bool(); + } + else if (name == "debugEventsUntilDate") + { + var dt = reader.LongOrNull(); + if (dt.HasValue) + { + debugEventsUntilDate = UnixMillisecondTime.OfMillis(dt.Value); + } + } + } + + return new FeatureFlag(value, version, flagVersion, trackEvents, trackReason, variation, + debugEventsUntilDate, reason); + } + + public void WriteJson(object o, IValueWriter writer) + { + if (!(o is FeatureFlag value)) + { + throw new InvalidOperationException(); + } + WriteJsonValue(value, writer); + } + + public static void WriteJsonValue(FeatureFlag value, IValueWriter writer) + { + using (var ow = writer.Object()) + { + LdJsonConverters.LdValueConverter.WriteJsonValue(value.value, ow.Name("value")); + ow.Name("version").Int(value.version); + ow.MaybeName("flagVersion", value.flagVersion.HasValue).Int(value.flagVersion.GetValueOrDefault()); + ow.MaybeName("variation", value.variation.HasValue).Int(value.variation.GetValueOrDefault()); + if (value.reason.HasValue) + { + LdJsonConverters.EvaluationReasonConverter.WriteJsonValue(value.reason.Value, ow.Name("reason")); + } + ow.MaybeName("trackEvents", value.trackEvents).Bool(value.trackEvents); + ow.MaybeName("trackReason", value.trackReason).Bool(value.trackReason); + ow.MaybeName("debugEventsUntilDate", value.debugEventsUntilDate.HasValue) + .Long(value.debugEventsUntilDate.GetValueOrDefault().Value); + } + } + } } } diff --git a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs index 343fca18..f825eedd 100644 --- a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs +++ b/src/LaunchDarkly.XamarinSdk/JsonUtil.cs @@ -1,65 +1,48 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; -using Newtonsoft.Json; namespace LaunchDarkly.Sdk.Xamarin { internal class JsonUtil { - private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings - { - Converters = new List { LdJsonNet.Converter, new UnixMillisecondTimeConverter() }, - DateParseHandling = DateParseHandling.None - }; - - // Wrapper for JsonConvert.DeserializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. - internal static T DecodeJson(string json) - { - return JsonConvert.DeserializeObject(json, _jsonSettings); - } + internal static T DecodeJson(string json) where T : IJsonSerializable => + LdJsonSerialization.DeserializeObject(json); - // Wrapper for JsonConvert.DeserializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. - internal static object DecodeJson(string json, Type type) - { - return JsonConvert.DeserializeObject(json, type, _jsonSettings); - } - - // Wrapper for JsonConvert.SerializeObject that ensures we use consistent settings and minimizes our Newtonsoft references. - internal static string EncodeJson(object o) - { - return JsonConvert.SerializeObject(o, _jsonSettings); - } + internal static string EncodeJson(T o) where T : IJsonSerializable => + LdJsonSerialization.SerializeObject(o); - private class UnixMillisecondTimeConverter : JsonConverter + public static ImmutableDictionary DeserializeFlags(string json) { - public override bool CanConvert(Type objectType) => - objectType == typeof(UnixMillisecondTime) || objectType == typeof(UnixMillisecondTime?); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + var r = JReader.FromString(json); + try { - if (objectType == typeof(UnixMillisecondTime?)) + var builder = ImmutableDictionary.CreateBuilder(); + for (var or = r.Object(); or.Next(ref r);) { - if (reader.TokenType == JsonToken.Null) - { - reader.Skip(); - return null; - } + builder.Add(or.Name.ToString(), FeatureFlag.JsonConverter.ReadJsonValue(ref r)); } - return UnixMillisecondTime.OfMillis((long)reader.Value); + return builder.ToImmutable(); + } + catch (Exception e) + { + throw r.TranslateException(e); } + } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public static string SerializeFlags(IReadOnlyDictionary flags) + { + var w = JWriter.New(); + using (var ow = w.Object()) { - if (value is null) - { - writer.WriteNull(); - } - else + foreach (var kv in flags) { - writer.WriteValue(((UnixMillisecondTime)value).Value); + FeatureFlag.JsonConverter.WriteJsonValue(kv.Value, ow.Name(kv.Key)); } } + return w.GetString(); } } } diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index ff104d70..73d94e51 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -38,12 +38,10 @@ - - diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index fa1fb678..e1ca1dbb 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -83,7 +83,7 @@ private async Task UpdateTaskAsync() if (response.statusCode == 200) { var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); + var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); _flagCacheManager.CacheFlagsFromService(flagsDictionary, _user); // We can't use bool in CompareExchange because it is not a reference type. diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index 14bc9385..cc19b17d 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using System.Net.Http; -using Newtonsoft.Json.Linq; using LaunchDarkly.EventSource; using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; @@ -162,7 +161,7 @@ void HandleMessage(string messageType, string messageData) { case Constants.PUT: { - _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(messageData), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DeserializeFlags(messageData), _user); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -173,8 +172,8 @@ void HandleMessage(string messageType, string messageData) { try { - var parsed = JsonUtil.DecodeJson(messageData); - var flagkey = (string)parsed[Constants.KEY]; + var parsed = LdValue.Parse(messageData); + var flagkey = parsed.Get(Constants.KEY).AsString; var featureFlag = JsonUtil.DecodeJson(messageData); PatchFeatureFlag(flagkey, featureFlag); } @@ -188,9 +187,9 @@ void HandleMessage(string messageType, string messageData) { try { - var dictionary = JsonUtil.DecodeJson>(messageData); - int version = dictionary[Constants.VERSION].ToObject(); - string flagKey = dictionary[Constants.KEY].ToString(); + var parsed = LdValue.Parse(messageData); + int version = parsed.Get(Constants.VERSION).AsInt; + string flagKey = parsed.Get(Constants.KEY).AsString; DeleteFeatureFlag(flagKey, version); } catch (Exception ex) @@ -207,7 +206,7 @@ void HandleMessage(string messageType, string messageData) { var response = await _requestor.FeatureFlagsAsync(); var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DecodeJson>(flagsAsJsonString); + var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); _cacheManager.CacheFlagsFromService(flagsDictionary, _user); if (!_initialized.GetAndSet(true)) { diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs index 08142209..4da6c091 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs @@ -17,7 +17,7 @@ public UserFlagDeviceCache(IPersistentStorage persister, Logger log) void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) { - var jsonString = JsonUtil.EncodeJson(flags); + var jsonString = JsonUtil.SerializeFlags(flags); try { persister.Save(Constants.FLAGS_KEY_PREFIX + user.Key, jsonString); @@ -36,7 +36,7 @@ IImmutableDictionary IUserFlagCache.RetrieveFlags(User user var flagsAsJson = persister.GetValue(Constants.FLAGS_KEY_PREFIX + user.Key); if (flagsAsJson != null) { - return JsonUtil.DecodeJson>(flagsAsJson); // surprisingly, this works + return JsonUtil.DeserializeFlags(flagsAsJson); } } catch (Exception ex) diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs index 8d47c0d9..4d30f6c8 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientEventTests.cs @@ -186,7 +186,7 @@ public void VariationSendsFeatureEventForUnknownFlag() [Fact] public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") + var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor); config.EventProcessor(eventProcessor); @@ -294,7 +294,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() [Fact] public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "") + var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor); config.EventProcessor(eventProcessor); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 2dfd3e46..74c999de 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -455,7 +455,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() using (var client = TestUtil.CreateClient(config, simpleUser)) { var storedJson = storage.GetValue(Constants.FLAGS_KEY_PREFIX + simpleUser.Key); - var flags = JsonUtil.DecodeJson>(storedJson); + var flags = JsonUtil.DeserializeFlags(storedJson); Assert.Equal(100, flags["flag"].value.AsInt); } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index ae62d6b2..37c58555 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -230,7 +230,7 @@ public Task Start() IsRunning = true; if (_cacheManager != null && _flagsJson != null) { - _cacheManager.CacheFlagsFromService(JsonUtil.DecodeJson>(_flagsJson), _user); + _cacheManager.CacheFlagsFromService(JsonUtil.DeserializeFlags(_flagsJson), _user); } return Task.FromResult(true); } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index c64e2bd0..b9bc367c 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -119,12 +119,12 @@ internal static IImmutableDictionary MakeSingleFlagData(str internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) { - return JsonUtil.EncodeJson(MakeSingleFlagData(flagKey, value, variation, reason)); + return JsonUtil.SerializeFlags(MakeSingleFlagData(flagKey, value, variation, reason)); } internal static IImmutableDictionary DecodeFlagsJson(string flagsJson) { - return JsonUtil.DecodeJson>(flagsJson); + return JsonUtil.DeserializeFlags(flagsJson); } internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) From a9636425e51c3d7d4693aeced1bdc866eb13b345 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 12 Apr 2021 19:45:52 -0700 Subject: [PATCH 336/499] improve EnumVariation with type constraint, fix tests --- README.md | 2 +- .../ILdClientExtensions.cs | 31 +--- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 5 +- .../ILdClientExtensionsTest.cs | 150 ++++++++++++------ .../LaunchDarkly.XamarinSdk.Tests.csproj | 1 - .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 3 + 6 files changed, 121 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index f6c73c2e..4e3ff093 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ LaunchDarkly overview Supported platforms ------------------- -This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 1.6, 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. +This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. Note that if you are targeting .NET Framework, there is a [known issue](https://stackoverflow.com/questions/46788323/installing-a-netstandard-2-0-nuget-package-into-a-vs2015-net-4-6-1-project) in Visual Studio 2015 where it does not correctly detect compatibility with .NET Standard packages. This is not a problem in later versions of Visual Studio. diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs index 0b3ffdf1..5daf96d7 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs @@ -21,11 +21,6 @@ public static class ILdClientExtensions /// If the flag has a value that is not one of the allowed enum value names, or is not a string, /// defaultValue is returned. /// - /// - /// Note that there is no type constraint to guarantee that T really is an enum type, because that is - /// a C# 7.3 feature that is unavailable in older versions of .NET Standard. If you try to use a - /// non-enum type, you will simply receive the default value back. - /// /// /// the enum type /// the client instance @@ -33,17 +28,15 @@ public static class ILdClientExtensions /// the default value of the flag (as an enum value) /// the variation for the given user, or defaultValue if the flag cannot /// be evaluated or does not have a valid enum value - public static T EnumVariation(this ILdClient client, string key, T defaultValue) + public static T EnumVariation(this ILdClient client, string key, T defaultValue) where T : struct, Enum { var stringVal = client.StringVariation(key, defaultValue.ToString()); if (stringVal != null) { - try + if (Enum.TryParse(stringVal, out var enumValue)) { - return (T)Enum.Parse(typeof(T), stringVal, true); + return enumValue; } - catch (ArgumentException) - { } } return defaultValue; } @@ -57,31 +50,23 @@ public static T EnumVariation(this ILdClient client, string key, T defaultVal /// If the flag has a value that is not one of the allowed enum value names, or is not a string, /// defaultValue is returned. /// - /// - /// Note that there is no type constraint to guarantee that T really is an enum type, because that is - /// a C# 7.3 feature that is unavailable in older versions of .NET Standard. If you try to use a - /// non-enum type, you will simply receive the default value back. - /// /// /// the enum type /// the client instance /// the unique feature key for the feature flag /// the default value of the flag (as an enum value) /// an object - public static EvaluationDetail EnumVariationDetail(this ILdClient client, string key, T defaultValue) + public static EvaluationDetail EnumVariationDetail(this ILdClient client, + string key, T defaultValue) where T : struct, Enum { var stringDetail = client.StringVariationDetail(key, defaultValue.ToString()); - if (stringDetail.Value != null) + if (!stringDetail.IsDefaultValue && stringDetail.Value != null) { - try + if (Enum.TryParse(stringDetail.Value, out var enumValue)) { - var enumValue = (T)Enum.Parse(typeof(T), stringDetail.Value, true); return new EvaluationDetail(enumValue, stringDetail.VariationIndex, stringDetail.Reason); } - catch (ArgumentException) - { - return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); - } + return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); } return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, stringDetail.Reason); } diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index aaec15d2..1c27c78e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -85,6 +85,9 @@ SharedTestCode\HttpHelpers.cs + + SharedTestCode\ILdClientExtensionsTest.cs + SharedTestCode\LDClientEndToEndTests.cs @@ -156,7 +159,7 @@ - {7717A2B2-9905-40A7-989F-790139D69543} + {9197B4B6-DE72-4A1B-AB8D-F31FE4AC2BE3} LaunchDarkly.XamarinSdk diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs index 75e87aed..f875b663 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs @@ -1,4 +1,6 @@ -using Moq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Xunit; namespace LaunchDarkly.Sdk.Xamarin @@ -15,9 +17,8 @@ enum MyEnum [Fact] public void EnumVariationConvertsStringToEnum() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("Green"); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariation("key", "Blue", "Green"); var result = client.EnumVariation("key", MyEnum.Blue); Assert.Equal(MyEnum.Green, result); @@ -26,9 +27,8 @@ public void EnumVariationConvertsStringToEnum() [Fact] public void EnumVariationReturnsDefaultValueForInvalidFlagValue() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("not-a-color"); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariation("key", "Blue", "not-a-color"); var defaultValue = MyEnum.Blue; var result = client.EnumVariation("key", defaultValue); @@ -38,34 +38,20 @@ public void EnumVariationReturnsDefaultValueForInvalidFlagValue() [Fact] public void EnumVariationReturnsDefaultValueForNullFlagValue() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns((string)null); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariation("key", "Blue", null); var defaultValue = MyEnum.Blue; var result = client.EnumVariation("key", defaultValue); Assert.Equal(defaultValue, result); } - [Fact] - public void EnumVariationReturnsDefaultValueForNonEnumType() - { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariation("key", "Blue")).Returns("Green"); - var client = clientMock.Object; - - var defaultValue = "this is a string, not an enum"; - var result = client.EnumVariation("key", defaultValue); - Assert.Equal(defaultValue, result); - } - [Fact] public void EnumVariationDetailConvertsStringToEnum() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail("Green", 1, EvaluationReason.FallthroughReason)); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariationDetail("key", "Blue", + new EvaluationDetail("Green", 1, EvaluationReason.FallthroughReason)); var result = client.EnumVariationDetail("key", MyEnum.Blue); var expected = new EvaluationDetail(MyEnum.Green, 1, EvaluationReason.FallthroughReason); @@ -75,10 +61,9 @@ public void EnumVariationDetailConvertsStringToEnum() [Fact] public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail("not-a-color", 1, EvaluationReason.FallthroughReason)); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariationDetail("key", "Blue", + new EvaluationDetail("not-a-color", 1, EvaluationReason.FallthroughReason)); var result = client.EnumVariationDetail("key", MyEnum.Blue); var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); @@ -88,28 +73,103 @@ public void EnumVariationDetailReturnsDefaultValueForInvalidFlagValue() [Fact] public void EnumVariationDetailReturnsDefaultValueForNullFlagValue() { - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariationDetail("key", "Blue")) - .Returns(new EvaluationDetail(null, 1, EvaluationReason.FallthroughReason)); - var client = clientMock.Object; + var client = new MockStringVariationClient(); + client.SetupStringVariationDetail("key", "Blue", + new EvaluationDetail(null, 1, EvaluationReason.FallthroughReason)); var result = client.EnumVariationDetail("key", MyEnum.Blue); var expected = new EvaluationDetail(MyEnum.Blue, 1, EvaluationReason.FallthroughReason); Assert.Equal(expected, result); } - [Fact] - public void EnumVariationDetailReturnsDefaultValueForNonEnumType() + private sealed class MockStringVariationClient : ILdClient { - var defaultValue = "this is a string, not an enum"; - var clientMock = new Mock(); - clientMock.Setup(c => c.StringVariationDetail("key", defaultValue)) - .Returns(new EvaluationDetail("Green", 1, EvaluationReason.FallthroughReason)); - var client = clientMock.Object; - - var result = client.EnumVariationDetail("key", defaultValue); - var expected = new EvaluationDetail(defaultValue, 1, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); - Assert.Equal(expected, result); + private Func _stringVariationFn; + private Func> _stringVariationDetailFn; + + public void SetupStringVariation(string expectedKey, string expectedDefault, string result) + { + _stringVariationFn = (key, defaultValue) => + { + Assert.Equal(expectedKey, key); + Assert.Equal(expectedDefault, defaultValue); + return result; + }; + } + + public void SetupStringVariationDetail(string expectedKey, string expectedDefault, EvaluationDetail result) + { + _stringVariationDetailFn = (key, defaultValue) => + { + Assert.Equal(expectedKey, key); + Assert.Equal(expectedDefault, defaultValue); + return result; + }; + } + + public string StringVariation(string key, string defaultValue) => + _stringVariationFn(key, defaultValue); + + public EvaluationDetail StringVariationDetail(string key, string defaultValue) => + _stringVariationDetailFn(key, defaultValue); + + // Other methods aren't relevant to these tests + + public bool Initialized => true; + public bool Offline => false; + public event System.EventHandler FlagChanged; + + public IDictionary AllFlags() => + throw new System.NotImplementedException(); + + public bool BoolVariation(string key, bool defaultValue = false) => + throw new System.NotImplementedException(); + + public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) => + throw new System.NotImplementedException(); + + public void Dispose() { } + + public float FloatVariation(string key, float defaultValue = 0) => + throw new System.NotImplementedException(); + + public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) => + throw new System.NotImplementedException(); + + public void Flush() { } + + public bool Identify(User user, System.TimeSpan maxWaitTime) => + throw new System.NotImplementedException(); + + public Task IdentifyAsync(User user) => + throw new System.NotImplementedException(); + + public int IntVariation(string key, int defaultValue = 0) => + throw new System.NotImplementedException(); + + public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) => + throw new System.NotImplementedException(); + + public LdValue JsonVariation(string key, LdValue defaultValue) => + throw new System.NotImplementedException(); + + public EvaluationDetail JsonVariationDetail(string key, LdValue defaultValue) => + throw new System.NotImplementedException(); + + public bool SetOffline(bool value, System.TimeSpan maxWaitTime) => + throw new System.NotImplementedException(); + + public Task SetOfflineAsync(bool value) => + throw new System.NotImplementedException(); + + public void Track(string eventName) => + throw new System.NotImplementedException(); + + public void Track(string eventName, LdValue data) => + throw new System.NotImplementedException(); + + public void Track(string eventName, LdValue data, double metricValue) => + throw new System.NotImplementedException(); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 803994e5..d1c19137 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -7,7 +7,6 @@ - diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index be5db5e4..571e5e85 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -152,6 +152,9 @@ SharedTestCode\HttpHelpers.cs + + SharedTestCode\ILdClientExtensionsTest.cs + SharedTestCode\LdClientEndToEndTests.cs From e51b084e091b1de26a1648efcea5b0d5427a7c47 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 27 Apr 2021 12:25:01 -0700 Subject: [PATCH 337/499] use LaunchDarkly.TestHelpers for HTTP tests --- .../FeatureFlagRequestorTests.cs | 35 +- .../HttpHelpers.cs | 481 ------------------ .../HttpHelpersTest.cs | 223 -------- .../LDClientEndToEndTests.cs | 105 ++-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 2 +- 5 files changed, 62 insertions(+), 784 deletions(-) delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs index d41b6d04..18f8389e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs @@ -1,6 +1,7 @@ using System; +using System.Linq; using System.Threading.Tasks; -using LaunchDarkly.Sdk.Xamarin.HttpHelpers; +using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; @@ -36,10 +37,10 @@ public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync( string expectedQuery ) { - using (var server = TestHttpServer.Start(Handlers.JsonResponse("{}"))) + using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) { var config = Configuration.Builder(_mobileKey) - .BaseUri(new Uri(server.Uri.ToString() + baseUriExtraPath)) + .BaseUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .EvaluationReasons(withReasons) .Build(); @@ -54,7 +55,7 @@ string expectedQuery Assert.Equal(expectedPathWithoutUser + _encodedUser, req.Path); Assert.Equal(expectedQuery, req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); - Assert.Null(req.Body); + Assert.Equal("", req.Body); } } } @@ -63,24 +64,22 @@ string expectedQuery //[Fact] //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() //{ - // await WithServerAsync(async server => + // using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) // { - // server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) // .UseReport(true).Build(); - + // // using (var requestor = new FeatureFlagRequestor(config, _user)) // { // var resp = await requestor.FeatureFlagsAsync(); // Assert.Equal(200, resp.statusCode); // Assert.Equal(_allDataJson, resp.jsonResponse); - - // var req = server.GetLastRequest(); + // + // var req = server.Recorder.RequireRequest(); // Assert.Equal("REPORT", req.Method); // Assert.Equal($"/msdk/evalx/user", req.Path); - // Assert.Equal("", req.RawQuery); - // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + // Assert.Equal("", req.Query); + // Assert.Equal(_mobileKey, req.Headers["Authorization"]); // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); // } // }); @@ -89,24 +88,22 @@ string expectedQuery //[Fact] //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() //{ - // await WithServerAsync(async server => + // using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) // { - // server.ForAllRequests(r => r.WithJsonBody(_allDataJson)); - // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) // .UseReport(true).EvaluationReasons(true).Build(); - + // // using (var requestor = new FeatureFlagRequestor(config, _user)) // { // var resp = await requestor.FeatureFlagsAsync(); // Assert.Equal(200, resp.statusCode); // Assert.Equal(_allDataJson, resp.jsonResponse); - + // // var req = server.GetLastRequest(); // Assert.Equal("REPORT", req.Method); // Assert.Equal($"/msdk/evalx/user", req.Path); - // Assert.Equal("?withReasons=true", req.RawQuery); - // Assert.Equal(_mobileKey, req.Headers["Authorization"][0]); + // Assert.Equal("?withReasons=true", req.Query); + // Assert.Equal(_mobileKey, req.Headers["Authorization"]); // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); // } // }); diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs deleted file mode 100644 index 32c78886..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpers.cs +++ /dev/null @@ -1,481 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using EmbedIO; -using EmbedIO.Routing; - -namespace LaunchDarkly.Sdk.Xamarin.HttpHelpers -{ - /// - /// An abstraction used by implementations to hide the details of the - /// underlying HTTP server framework. - /// - public sealed class RequestContext - { - public RequestInfo RequestInfo { get; } - public CancellationToken CancellationToken { get; } - private IHttpContext InternalContext { get; } - private volatile bool _chunked; - - private RequestContext(IHttpContext context, RequestInfo requestInfo) - { - InternalContext = context; - RequestInfo = requestInfo; - CancellationToken = context.CancellationToken; - } - - internal static async Task FromHttpContext(IHttpContext ctx) - { - var requestInfo = new RequestInfo - { - Method = ctx.Request.HttpMethod, - Path = ctx.RequestedPath, - Query = ctx.Request.Url.Query, - Headers = ctx.Request.Headers - }; - - if (ctx.Request.HasEntityBody) - { - requestInfo.Body = await ctx.GetRequestBodyAsStringAsync(); - } - return new RequestContext(ctx, requestInfo); - } - - /// - /// Sets the response status. - /// - /// the HTTP status - public void SetStatus(int statusCode) => InternalContext.Response.StatusCode = statusCode; - - /// - /// Sets a response header. - /// - /// the header name - /// the header value - public void SetHeader(string name, string value) - { - if (name.ToLower() == "content-type") - { - InternalContext.Response.ContentType = value; - } - else - { - InternalContext.Response.Headers.Set(name, value); - } - } - - /// - /// Adds a response header, allowing multiple values. - /// - /// the header name - /// the header value - public void AddHeader(string name, string value) - { - if (name.ToLower() == "content-type") - { - InternalContext.Response.ContentType = value; - } - else - { - InternalContext.Response.Headers.Add(name, value); - } - } - - /// - /// Writes a chunk of data in a chunked response. - /// - public async Task WriteChunkedDataAsync(byte[] data) - { - if (!_chunked) - { - _chunked = true; - InternalContext.Response.SendChunked = true; - } - await InternalContext.Response.OutputStream.WriteAsync(data, 0, data.Length); - } - - /// - /// Writes a complete response body. - /// - /// the Content-Type header value - /// the data - /// the asynchronous task - public async Task WriteFullResponseAsync(string contentType, byte[] data) - { - InternalContext.Response.ContentLength64 = data.Length; - InternalContext.Response.ContentType = contentType; - await InternalContext.Response.OutputStream.WriteAsync(data, 0, data.Length, - InternalContext.CancellationToken); - } - } - - /// - /// Properties of a request received by a . - /// - public struct RequestInfo - { - public string Method { get; set; } - public string Path { get; set; } - public string Query { get; set; } - public NameValueCollection Headers { get; set; } - public string Body { get; set; } - } - - /// - /// An asynchronous function that handles HTTP requests to a . - /// - /// - /// Use the factory methods in to create standard implementations. - /// - /// the request context - /// the asynchronous task - public delegate Task Handler(RequestContext context); - - /// - /// A simplified system for setting up embedded test HTTP servers. - /// - /// - /// - /// This abstraction is designed to allow writing test code that does not need to know anything - /// about the underlying implementation details of the HTTP framework, so that if a different - /// library needs to be used for compatibility reasons, it can be substituted without disrupting - /// the tests. - /// - /// - /// - /// // Start a server that returns a 200 status for all requests - /// using (var server = StartServer(Handlers.Status(200))) - /// { - /// DoSomethingThatMakesARequestTo(server.Uri); - /// - /// var req = server.RequireRequest(); - /// // Check for expected properties of the request - /// } - /// - /// - /// - public sealed class TestHttpServer : IDisposable - { - private static int nextPort = 10000; - - /// - /// The base URI of the server. - /// - public Uri Uri { get; } - - /// - /// Returns the that captures all requests to this server. - /// - public RequestRecorder Recorder => _baseHandler; - - private readonly WebServer _server; - private readonly RequestRecorder _baseHandler; - - private TestHttpServer(WebServer server, RequestRecorder baseHandler, Uri uri) - { - _server = server; - _baseHandler = baseHandler; - Uri = uri; - } - - /// - /// Shuts down the server. - /// - public void Dispose() => _server.Dispose(); - - /// - /// Starts a new test server. - /// - /// - /// Make sure to close this when done, by calling Dispose or with a using - /// statement. - /// - /// A function that will handle all requests to this server. Use - /// the factory methods in for standard handlers. If you will need - /// to change the behavior of the handler during the lifetime of the server, use - /// . - /// - public static TestHttpServer Start(Handler handler) - { - var recorder = Handlers.RecordAndDelegateTo(handler); - var server = StartWebServerOnAvailablePort(out var uri, recorder); - return new TestHttpServer(server, recorder, uri); - } - - private static WebServer StartWebServerOnAvailablePort(out Uri serverUri, Handler handler) - { - while (true) - { - var port = nextPort++; - var options = new WebServerOptions() - .WithUrlPrefix($"http://*:{port}") - .WithMode(HttpListenerMode.Microsoft); - var server = new WebServer(options); - server.Listener.IgnoreWriteExceptions = true; - server.OnAny("/", async internalContext => - { - var ctx = await RequestContext.FromHttpContext(internalContext); - await handler(ctx); - }); - try - { - _ = server.RunAsync(); - } - catch (HttpListenerException) - { - continue; - } - serverUri = new Uri(string.Format("http://localhost:{0}", port)); - return server; - } - } - } - - /// - /// Factory methods for standard implementations. - /// - public static class Handlers - { - /// - /// Creates a that sleeps for the specified amount of time - /// before passing the request to the target handler. - /// - /// how long to delay - /// the handler to call after the delay - /// a - public static Handler DelayBefore(TimeSpan delay, Handler target) => - async ctx => - { - await Task.Delay(delay, ctx.CancellationToken); - if (!ctx.CancellationToken.IsCancellationRequested) - { - await target(ctx); - } - }; - - /// - /// Creates a that sends a 200 response with a JSON content type. - /// - /// the JSON data - /// additional headers (may be null) - /// - public static Handler JsonResponse( - string jsonBody, - NameValueCollection headers = null - ) => - StringResponse(200, headers, "application/json", jsonBody); - - /// - /// Creates a that captures requests while delegating to - /// another handler - /// - /// the handler to delegate to - /// a - public static RequestRecorder RecordAndDelegateTo(Handler target) => - new RequestRecorder(target); - - /// - /// Creates a that always sends the same response, - /// specifying the response body (if any) as a byte array. - /// - /// the HTTP status code - /// response headers (may be null) - /// response content type (used only if body is not null) - /// response body (may be null) - /// - public static Handler Response( - int statusCode, - NameValueCollection headers, - string contentType = null, - byte[] body = null - ) => - async ctx => - { - ctx.SetStatus(statusCode); - if (headers != null) - { - foreach (var k in headers.AllKeys) - { - foreach (var v in headers.GetValues(k)) - { - ctx.AddHeader(k, v); - } - } - } - if (body != null) - { - await ctx.WriteFullResponseAsync(contentType, body); - } - }; - - /// - /// Creates a that always sends the same response, - /// specifying the response body (if any) as a string. - /// - /// the HTTP status code - /// response headers (may be null) - /// response content type (used only if body is not null) - /// response body (may be null) - /// response encoding (defaults to UTF8) - /// - public static Handler StringResponse( - int statusCode, - NameValueCollection headers, - string contentType = null, - string body = null, - Encoding encoding = null - ) => - Response( - statusCode, - headers, - contentType is null || contentType.Contains("charset=") ? contentType : - contentType + "; charset=" + (encoding ?? Encoding.UTF8).WebName, - body == null ? null : (encoding ?? Encoding.UTF8).GetBytes(body) - ); - - /// - /// Creates a that always returns the specified HTTP - /// response status, with no custom headers and no body. - /// - /// - /// a - public static Handler Status(int statusCode) => - Sync(ctx => ctx.SetStatus(statusCode)); - - /// - /// Creates a for changing handler behavior - /// dynamically. - /// - /// the initial to delegate to - /// a - public static HandlerDelegator DelegateTo(Handler target) => - new HandlerDelegator(target); - - /// - /// Wraps a synchronous action in an asynchronous . - /// - /// the action to run - /// a - public static Handler Sync(Action action) => - ctx => - { - action(ctx); - return Task.CompletedTask; - }; - } - - /// - /// A delegator that forwards requests to another handler, which can be changed at any time. - /// - /// - /// There is an implicit conversion allowing a HandlerDelegator to be used as a - /// . - /// - public sealed class HandlerDelegator - { - private volatile Handler _target; - - /// - /// The handler that will actually handle the request. - /// - public Handler Target - { - get => _target; - set - { - _target = value; - } - } - - /// - /// Returns the stable that is the external entry point to this - /// delegator. This is used implicitly if you use a HandlerDelegator anywhere that - /// a is expected. - /// - public Handler Handler => ctx => _target(ctx); - - internal HandlerDelegator(Handler target) - { - _target = target; - } - - public static implicit operator Handler(HandlerDelegator me) => me.Handler; - } - - /// - /// An object that delegates requests to another handler while recording all requests. - /// - /// - /// Normally you won't need to use this class directly, because - /// has a built-in instance that captures all requests. You can use it if you need to - /// capture only a subset of requests. - /// - public class RequestRecorder - { - public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); - - private readonly BlockingCollection _requests = new BlockingCollection(); - private readonly Handler _target; - - /// - /// Returns the stable that is the external entry point to this - /// delegator. This is used implicitly if you use a RequestRecorder anywhere that - /// a is expected. - /// - public Handler Handler => DoRequestAsync; - - /// - /// The number of requests currently in the queue. - /// - public int Count => _requests.Count; - - internal RequestRecorder(Handler target) - { - _target = target; - } - - /// - /// Consumes and returns the first request in the queue, blocking until one is available. - /// Throws an exception if the timeout expires. - /// - /// the maximum length of time to wait - /// the request information - public RequestInfo RequireRequest(TimeSpan timeout) - { - if (!_requests.TryTake(out var req, timeout)) - { - throw new TimeoutException("timed out waiting for request"); - } - return req; - } - - /// - /// Returns the first request in the queue, blocking until one is available, - /// using . - /// - /// the request information - public RequestInfo RequireRequest() => RequireRequest(DefaultTimeout); - - public void RequireNoRequests(TimeSpan timeout) - { - if (_requests.TryTake(out var _, timeout)) - { - throw new Exception("received an unexpected request"); - } - } - - private async Task DoRequestAsync(RequestContext ctx) - { - _requests.Add(ctx.RequestInfo); - await _target(ctx); - } - - public static implicit operator Handler(RequestRecorder me) => me.Handler; - } -} \ No newline at end of file diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs deleted file mode 100644 index 0baf07db..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/HttpHelpersTest.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace LaunchDarkly.Sdk.Xamarin.HttpHelpers -{ - public class HttpHelpersTest - { - [Fact] - public async Task ServerWithSimpleStatusHandler() - { - await WithServerAndClient(Handlers.Status(419), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(419, (int)resp.StatusCode); - }); - } - - [Fact] - public async Task ServerCapturesRequestWithoutBody() - { - await WithServerAndClient(Handlers.Status(200), async (server, client) => - { - var req = new HttpRequestMessage(HttpMethod.Get, server.Uri.ToString() + "request/path?a=b"); - req.Headers.Add("header-name", "header-value"); - - var resp = await client.SendAsync(req); - Assert.Equal(200, (int)resp.StatusCode); - - var received = server.Recorder.RequireRequest(); - Assert.Equal("GET", received.Method); - Assert.Equal("/request/path", received.Path); - Assert.Equal("?a=b", received.Query); - Assert.Equal("header-value", received.Headers["header-name"]); - Assert.Null(received.Body); - }); - } - - [Fact] - public async Task ServerCapturesRequestWithBody() - { - await WithServerAndClient(Handlers.Status(200), async (server, client) => - { - var req = new HttpRequestMessage(HttpMethod.Post, server.Uri.ToString() + "request/path"); - req.Content = new StringContent("hello", Encoding.UTF8, "text/plain"); - - var resp = await client.SendAsync(req); - Assert.Equal(200, (int)resp.StatusCode); - - var received = server.Recorder.RequireRequest(); - Assert.Equal("POST", received.Method); - Assert.Equal("/request/path", received.Path); - Assert.Equal("hello", received.Body); - }); - } - - [Fact] - public async Task CustomAsyncHandler() - { - Handler handler = async ctx => - { - await ctx.WriteFullResponseAsync("text/plain", Encoding.UTF8.GetBytes("hello")); - }; - await WithServerAndClient(handler, async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal("hello", await resp.Content.ReadAsStringAsync()); - }); - } - - [Fact] - public async Task CustomSyncHandler() - { - Action action = ctx => - { - ctx.SetStatus(419); - }; - await WithServerAndClient(Handlers.Sync(action), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(419, (int)resp.StatusCode); - }); - } - - [Fact] - public async Task ResponseHandlerWithoutBody() - { - var headers = new NameValueCollection(); - headers.Add("header-name", "header-value"); - await WithServerAndClient(Handlers.Response(419, headers), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(419, (int)resp.StatusCode); - Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); - Assert.Equal("", await resp.Content.ReadAsStringAsync()); - }); - } - - [Fact] - public async Task ResponseHandlerWithBody() - { - var headers = new NameValueCollection(); - headers.Add("header-name", "header-value"); - byte[] data = new byte[] { 1, 2, 3 }; - await WithServerAndClient(Handlers.Response(200, headers, "weird", data), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(200, (int)resp.StatusCode); - Assert.Equal("weird", resp.Content.Headers.GetValues("content-type").First()); - Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); - Assert.Equal(data, await resp.Content.ReadAsByteArrayAsync()); - }); - } - - [Fact] - public async Task StringResponseHandlerWithBody() - { - var headers = new NameValueCollection(); - headers.Add("header-name", "header-value"); - string body = "hello"; - await WithServerAndClient(Handlers.StringResponse(200, headers, "weird", body), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(200, (int)resp.StatusCode); - Assert.Equal("weird; charset=utf-8", resp.Content.Headers.GetValues("content-type").First()); - Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); - Assert.Equal(body, await resp.Content.ReadAsStringAsync()); - }); - } - - [Fact] - public async Task JsonResponseHandler() - { - await WithServerAndClient(Handlers.JsonResponse("true"), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(200, (int)resp.StatusCode); - Assert.Equal("application/json; charset=utf-8", resp.Content.Headers.ContentType.ToString()); - Assert.Equal("true", await resp.Content.ReadAsStringAsync()); - }); - } - - [Fact] - public async Task JsonResponseHandlerWithHeaders() - { - var headers = new NameValueCollection(); - headers.Add("header-name", "header-value"); - await WithServerAndClient(Handlers.JsonResponse("true", headers), async (server, client) => - { - var resp = await client.GetAsync(server.Uri); - Assert.Equal(200, (int)resp.StatusCode); - Assert.Equal("application/json; charset=utf-8", resp.Content.Headers.ContentType.ToString()); - Assert.Equal("header-value", resp.Headers.GetValues("header-name").First()); - Assert.Equal("true", await resp.Content.ReadAsStringAsync()); - }); - } - - [Fact] - public async Task SwitchableDelegatorHandler() - { - var switchable = Handlers.DelegateTo(Handlers.Status(200)); - await WithServerAndClient(switchable, async (server, client) => - { - var resp1 = await client.GetAsync(server.Uri); - Assert.Equal(200, (int)resp1.StatusCode); - - switchable.Target = Handlers.Status(400); - - var resp2 = await client.GetAsync(server.Uri); - Assert.Equal(400, (int)resp2.StatusCode); - }); - } - - [Fact] - public async Task ChunkedResponse() - { - Handler handler = async ctx => - { - ctx.SetHeader("Content-Type", "text/plain; charset=utf-8"); - await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("chunk1,")); - await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("chunk2")); - await Task.Delay(Timeout.Infinite, ctx.CancellationToken); - }; - var expected = "chunk1,chunk2"; - await WithServerAndClient(handler, async (server, client) => - { - var resp = await client.GetAsync(server.Uri, HttpCompletionOption.ResponseHeadersRead); - Assert.Equal(200, (int)resp.StatusCode); - Assert.Equal("text/plain; charset=utf-8", resp.Content.Headers.ContentType.ToString()); - var stream = await resp.Content.ReadAsStreamAsync(); - var received = new StringBuilder(); - while (true) - { - var buf = new byte[100]; - int n = await stream.ReadAsync(buf, 0, buf.Length); - received.Append(Encoding.UTF8.GetString(buf, 0, n)); - if (received.Length >= expected.Length) - { - Assert.Equal(expected, received.ToString()); - break; - } - } - }); - } - - private static async Task WithServerAndClient(Handler handler, Func action) - { - using (var server = TestHttpServer.Start(handler)) - { - using (var client = new HttpClient()) - { - await action(server, client); - } - } - } - } -} diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 7c0b26a8..7f11e975 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -using LaunchDarkly.Sdk.Xamarin.HttpHelpers; +using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; @@ -38,14 +37,14 @@ public class LdClientEndToEndTests : BaseTest { new object[] { UpdateMode.Polling } }, { new object[] { UpdateMode.Streaming } } }; - + public LdClientEndToEndTests(ITestOutputHelper testOutput) : base(testOutput) { } [Theory] [MemberData(nameof(PollingAndStreaming))] public void InitGetsFlagsSync(UpdateMode mode) { - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, mode))) { var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(10))) @@ -60,7 +59,7 @@ public void InitGetsFlagsSync(UpdateMode mode) [MemberData(nameof(PollingAndStreaming))] public async Task InitGetsFlagsAsync(UpdateMode mode) { - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, mode))) { var config = BaseConfig(server.Uri, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -73,15 +72,12 @@ public async Task InitGetsFlagsAsync(UpdateMode mode) [Fact] public void StreamingInitMakesPollRequestIfStreamSendsPing() { - Handler streamHandler = async ctx => - { - ctx.AddHeader("Content-Type", "text/event-stream"); - await ctx.WriteChunkedDataAsync(Encoding.UTF8.GetBytes("event: ping\ndata: \n\n")); - await Task.Delay(-1, ctx.CancellationToken); - }; - using (var streamServer = TestHttpServer.Start(streamHandler)) + Handler streamHandler = Handlers.SSE.Start() + .Then(Handlers.SSE.Event("ping", "")) + .Then(Handlers.SSE.LeaveOpen()); + using (var streamServer = HttpServer.Start(streamHandler)) { - using (var pollServer = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) + using (var pollServer = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { var config = BaseConfig(streamServer.Uri, UpdateMode.Streaming, b => b.BaseUri(pollServer.Uri)); @@ -98,8 +94,8 @@ public void StreamingInitMakesPollRequestIfStreamSendsPing() [Fact] public void InitCanTimeOutSync() { - var handler = Handlers.DelayBefore(TimeSpan.FromSeconds(2), SetupResponse(_flagData1, UpdateMode.Polling)); - using (var server = TestHttpServer.Start(handler)) + var handler = Handlers.Delay(TimeSpan.FromSeconds(2)).Then(SetupResponse(_flagData1, UpdateMode.Polling)); + using (var server = HttpServer.Start(handler)) { var config = BaseConfig(server.Uri, builder => builder.IsStreamingEnabled(false)); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) @@ -116,7 +112,7 @@ public void InitCanTimeOutSync() [MemberData(nameof(PollingAndStreaming))] public void InitFailsOn401Sync(UpdateMode mode) { - using (var server = TestHttpServer.Start(Handlers.Status(401))) + using (var server = HttpServer.Start(Handlers.Status(401))) { var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) @@ -130,7 +126,7 @@ public void InitFailsOn401Sync(UpdateMode mode) [MemberData(nameof(PollingAndStreaming))] public async Task InitFailsOn401Async(UpdateMode mode) { - using (var server = TestHttpServer.Start(Handlers.Status(401))) + using (var server = HttpServer.Start(Handlers.Status(401))) { var config = BaseConfig(server.Uri, mode); @@ -148,7 +144,7 @@ public async Task InitFailsOn401Async(UpdateMode mode) public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() { // Note, we don't care about polling mode vs. streaming mode for this functionality. - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { var config = BaseConfig(server.Uri, UpdateMode.Polling); var name = "Sue"; @@ -177,9 +173,10 @@ public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) { - var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); - using (var server = TestHttpServer.Start(switchable)) + using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { + switchable.Target = SetupResponse(_flagData1, mode); + var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) { @@ -205,9 +202,10 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) [MemberData(nameof(PollingAndStreaming))] public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) { - var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); - using (var server = TestHttpServer.Start(switchable)) + using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { + switchable.Target = SetupResponse(_flagData1, mode); + var config = BaseConfig(server.Uri, mode); using (var client = await TestUtil.CreateClientAsync(config, _user)) { @@ -233,17 +231,18 @@ public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) [MemberData(nameof(PollingAndStreaming))] public void IdentifyCanTimeOutSync(UpdateMode mode) { - var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, mode)); - using (var server = TestHttpServer.Start(switchable)) + using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { + switchable.Target = SetupResponse(_flagData1, mode); + var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) { var req1 = VerifyRequest(server.Recorder, mode); VerifyFlagValues(client, _flagData1); - switchable.Target = Handlers.DelayBefore(TimeSpan.FromSeconds(2), - SetupResponse(_flagData1, mode)); + switchable.Target = Handlers.Delay(TimeSpan.FromSeconds(2)) + .Then(SetupResponse(_flagData1, mode)); var success = client.Identify(_otherUser, TimeSpan.FromMilliseconds(100)); Assert.False(success); @@ -262,11 +261,11 @@ public void EventsAreSentToCorrectEndpointAsync( string expectedPath ) { - using (var server = TestHttpServer.Start(Handlers.Status(202))) + using (var server = HttpServer.Start(Handlers.Status(202))) { var config = Configuration.BuilderInternal(_mobileKey) .UpdateProcessorFactory(MockPollingProcessor.Factory("{}")) - .EventsUri(new Uri(server.Uri.ToString() + baseUriExtraPath)) + .EventsUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .PersistFlagValues(false) .Build(); @@ -286,7 +285,7 @@ string expectedPath public void OfflineClientUsesCachedFlagsSync() { // streaming vs. polling should make no difference for this - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { ClearCachedFlags(_user); try @@ -316,7 +315,7 @@ public void OfflineClientUsesCachedFlagsSync() public async Task OfflineClientUsesCachedFlagsAsync() { // streaming vs. polling should make no difference for this - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { ClearCachedFlags(_user); try @@ -349,9 +348,10 @@ public async Task BackgroundModeForcesPollingAsync() ClearCachedFlags(_user); - var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, UpdateMode.Streaming)); - using (var server = TestHttpServer.Start(switchable)) + using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { + switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); + var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder .BackgroundModeManager(mockBackgroundModeManager) .BackgroundPollingIntervalWithoutMinimum(backgroundInterval) @@ -394,9 +394,11 @@ public async Task BackgroundModePollingCanBeDisabledAsync() var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); ClearCachedFlags(_user); - var switchable = Handlers.DelegateTo(SetupResponse(_flagData1, UpdateMode.Streaming)); - using (var server = TestHttpServer.Start(switchable)) + + using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { + switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); + var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder .BackgroundModeManager(mockBackgroundModeManager) .EnableBackgroundUpdating(false) @@ -435,7 +437,7 @@ public async Task BackgroundModePollingCanBeDisabledAsync() [MemberData(nameof(PollingAndStreaming))] public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) { - using (var server = TestHttpServer.Start(SetupResponse(_flagData1, mode))) + using (var server = HttpServer.Start(SetupResponse(_flagData1, mode))) { ClearCachedFlags(_user); var config = BaseConfig(server.Uri, mode, builder => builder.Offline(true).PersistFlagValues(false)); @@ -464,7 +466,7 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) { "flag1", dateLikeString1 }, { "flag2", dateLikeString2 } }; - using (var server = TestHttpServer.Start(SetupResponse(flagData, mode))) + using (var server = HttpServer.Start(SetupResponse(flagData, mode))) { var config = BaseConfig(server.Uri, mode); using (var client = TestUtil.CreateClient(config, _user)) @@ -496,24 +498,12 @@ private Configuration BaseConfig(Uri serverUri, UpdateMode mode, Func data, UpdateMode mode) - { - var body = mode.IsStreaming ? StreamingData(data) : PollingData(data); - return async ctx => - { - if (mode.IsStreaming) - { - ctx.SetHeader("Content-Type", "text/event-stream"); - var bytes = Encoding.UTF8.GetBytes(body); - await ctx.WriteChunkedDataAsync(bytes); - await Task.Delay(Timeout.Infinite, ctx.CancellationToken); - } - else - { - await Handlers.JsonResponse(body)(ctx); - } - }; - } + private Handler SetupResponse(IDictionary data, UpdateMode mode) => + mode.IsStreaming + ? Handlers.SSE.Start() + .Then(Handlers.SSE.Event("put", PollingData(data))) + .Then(Handlers.SSE.LeaveOpen()) + : Handlers.BodyJson(PollingData(data)); private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) { @@ -528,7 +518,7 @@ private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) Assert.Equal("", req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); - Assert.Null(req.Body); + Assert.Equal("", req.Body); return req; } @@ -569,11 +559,6 @@ private static string PollingData(IDictionary flags) } return LdValue.ObjectFrom(d).ToJsonString(); } - - private static string StreamingData(IDictionary flags) - { - return "event: put\ndata: " + PollingData(flags) + "\n\n"; - } } public class UpdateMode diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index d1c19137..83bdc2bf 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -4,9 +4,9 @@ LaunchDarkly.XamarinSdk.Tests + - From faca1438e3681975bfda5ff23914fc43e0bc7e4d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 27 Apr 2021 12:32:19 -0700 Subject: [PATCH 338/499] fix project files --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 3 --- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 3 --- 2 files changed, 6 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 1c27c78e..281f3377 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -82,9 +82,6 @@ SharedTestCode\FlagChangedEventTests.cs - - SharedTestCode\HttpHelpers.cs - SharedTestCode\ILdClientExtensionsTest.cs diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index 571e5e85..e2d5d655 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -149,9 +149,6 @@ SharedTestCode\FlagChangedEventTests.cs - - SharedTestCode\HttpHelpers.cs - SharedTestCode\ILdClientExtensionsTest.cs From e4a4b6a3b8c8a310466716c28d3cd3420c3ee8a3 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 27 Apr 2021 12:45:59 -0700 Subject: [PATCH 339/499] fix project files --- .../LaunchDarkly.XamarinSdk.Android.Tests.csproj | 2 +- .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 281f3377..777f0154 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -145,7 +145,7 @@ - + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index e2d5d655..bf28c1d0 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -69,10 +69,10 @@ + - From 57b8ec41612f065311bab82a862a3c634f5bbca4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 28 Apr 2021 13:18:59 -0700 Subject: [PATCH 340/499] update to latest InternalSdk, misc cleanup, better test code sharing (#114) --- src/LaunchDarkly.XamarinSdk/Extensions.cs | 42 -------------- src/LaunchDarkly.XamarinSdk/Factory.cs | 1 + .../FeatureFlagRequestor.cs | 5 +- .../Internal/Base64.cs | 13 +++++ .../LaunchDarkly.XamarinSdk.csproj | 2 +- .../MobilePollingProcessor.cs | 2 +- .../MobileStreamingProcessor.cs | 8 +-- ...unchDarkly.XamarinSdk.Android.Tests.csproj | 51 +--------------- .../Base64Test.cs} | 4 +- .../LaunchDarkly.XamarinSdk.Tests.csproj | 3 + .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 58 ++----------------- 11 files changed, 35 insertions(+), 154 deletions(-) delete mode 100644 src/LaunchDarkly.XamarinSdk/Extensions.cs create mode 100644 src/LaunchDarkly.XamarinSdk/Internal/Base64.cs rename tests/LaunchDarkly.XamarinSdk.Tests/{ExtensionsTest.cs => Internal/Base64Test.cs} (65%) diff --git a/src/LaunchDarkly.XamarinSdk/Extensions.cs b/src/LaunchDarkly.XamarinSdk/Extensions.cs deleted file mode 100644 index 6e3ddb51..00000000 --- a/src/LaunchDarkly.XamarinSdk/Extensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using LaunchDarkly.Sdk.Json; - -namespace LaunchDarkly.Sdk.Xamarin -{ - internal static class Extensions - { - public static string UrlSafeBase64Encode(this string plainText) - { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_'); - } - - public static string AsJson(this User user) - { - return LdJsonSerialization.SerializeObject(user); - } - - // This differs from "new Uri(baseUri, path)" in that it always assumes a trailing "/" in - // baseUri. For instance, if baseUri is http://hostname/basepath and path is "relativepath", - // baseUri.AddPath(path) will return http://hostname/basepath/relativepath, whereas - // new Uri(baseUri, path) would return http://hostname/relativepath (since, with no trailing - // slash, it would treat "basepath" as the equivalent of a filename rather than the - // equivalent of a directory name). We should assume that if an application has specified a - // base URL with a non-empty path, the intention is to use that as a prefix for everything. - public static Uri AddPath(this Uri baseUri, string path) - { - var ub = new UriBuilder(baseUri); - ub.Path = ub.Path.TrimEnd('/') + "/" + path.TrimStart('/'); - return ub.Uri; - } - - public static Uri AddQuery(this Uri baseUri, string query) - { - var ub = new UriBuilder(baseUri); - ub.Query = string.IsNullOrEmpty(ub.Query) ? - ("?" + query) : - ub.Query + "&" + query; - return ub.Uri; - } - } -} diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Factory.cs index 3eb50d49..7b499b87 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Factory.cs @@ -1,5 +1,6 @@ using System; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Xamarin.Internal; using LaunchDarkly.Sdk.Xamarin.Internal.Events; diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs index e9c4776a..6cba6e03 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs @@ -8,6 +8,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Xamarin.Internal; namespace LaunchDarkly.Sdk.Xamarin { @@ -58,14 +59,14 @@ public async Task FeatureFlagsAsync() private HttpRequestMessage GetRequestMessage() { - var path = Constants.FLAG_REQUEST_PATH_GET + _currentUser.AsJson().UrlSafeBase64Encode(); + var path = Constants.FLAG_REQUEST_PATH_GET + Base64.UrlSafeEncode(JsonUtil.EncodeJson(_currentUser)); return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } private HttpRequestMessage ReportRequestMessage() { var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(Constants.FLAG_REQUEST_PATH_REPORT)); - request.Content = new StringContent(_currentUser.AsJson(), Encoding.UTF8, Constants.APPLICATION_JSON); + request.Content = new StringContent(JsonUtil.EncodeJson(_currentUser), Encoding.UTF8, Constants.APPLICATION_JSON); return request; } diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Base64.cs b/src/LaunchDarkly.XamarinSdk/Internal/Base64.cs new file mode 100644 index 00000000..4a92a5b0 --- /dev/null +++ b/src/LaunchDarkly.XamarinSdk/Internal/Base64.cs @@ -0,0 +1,13 @@ +using System; + +namespace LaunchDarkly.Sdk.Xamarin.Internal +{ + internal static class Base64 + { + public static string UrlSafeEncode(this string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_'); + } + } +} diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 73d94e51..1ac2d3d3 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs index e1ca1dbb..0ba56d8b 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Xamarin { diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs index cc19b17d..a817558c 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using System.Net.Http; @@ -9,6 +7,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Xamarin.Internal; namespace LaunchDarkly.Sdk.Xamarin { @@ -68,7 +67,7 @@ Task IMobileUpdateProcessor.Start() _configuration.HttpProperties, ReportMethod, MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), - _user.AsJson() + JsonUtil.EncodeJson(_user) ); } else @@ -76,7 +75,8 @@ Task IMobileUpdateProcessor.Start() _eventSource = _eventSourceCreator( _configuration.HttpProperties, HttpMethod.Get, - MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + _user.AsJson().UrlSafeBase64Encode()), + MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + + Base64.UrlSafeEncode(JsonUtil.EncodeJson(_user))), null ); } diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index 777f0154..c3cbd0ed 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -64,54 +64,7 @@ - - SharedTestCode\BaseTest.cs - - - SharedTestCode\ConfigurationTest.cs - - - SharedTestCode\FeatureFlagBuilder.cs - - - SharedTestCode\FeatureFlagRequestorTests.cs - - - SharedTestCode\FlagCacheManagerTests.cs - - - SharedTestCode\FlagChangedEventTests.cs - - - SharedTestCode\ILdClientExtensionsTest.cs - - - SharedTestCode\LDClientEndToEndTests.cs - - - SharedTestCode\LdClientEvaluationTests.cs - - - SharedTestCode\LdClientEventTests.cs - - - SharedTestCode\LdClientTests.cs - - - SharedTestCode\MobilePollingProcessorTests.cs - - - SharedTestCode\MockComponents.cs - - - SharedTestCode\TestLogging.cs - - - SharedTestCode\TestUtil.cs - - - SharedTestCode\UserFlagCacheTests.cs - + @@ -156,7 +109,7 @@ - {9197B4B6-DE72-4A1B-AB8D-F31FE4AC2BE3} + {7717A2B2-9905-40A7-989F-790139D69543} LaunchDarkly.XamarinSdk diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs similarity index 65% rename from tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs index 7c08f1b3..5bbf460d 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ExtensionsTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs @@ -1,6 +1,6 @@ using Xunit; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { public class ExtensionsTest { @@ -8,7 +8,7 @@ public class ExtensionsTest public void TestUrlSafeBase64Encode() { Assert.Equal("eyJrZXkiOiJmb28-YmFyX18_In0=", - @"{""key"":""foo>bar__?""}".UrlSafeBase64Encode()); + Base64.UrlSafeEncode(@"{""key"":""foo>bar__?""}")); } } } diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj index 83bdc2bf..c69c79bb 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj @@ -24,4 +24,7 @@ + + + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index bf28c1d0..b8277cbf 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -15,6 +15,7 @@ true NSUrlSessionHandler true + iPhoneSimulator;iPhone;AnyCPU true @@ -70,9 +71,9 @@ - - - + + + @@ -131,59 +132,10 @@ - - SharedTestCode\BaseTest.cs - - - SharedTestCode\ConfigurationTest.cs - - - SharedTestCode\FeatureFlagBuilder.cs - - - SharedTestCode\FeatureFlagRequestorTests.cs - - - SharedTestCode\FlagCacheManagerTests.cs - - - SharedTestCode\FlagChangedEventTests.cs - - - SharedTestCode\ILdClientExtensionsTest.cs - - - SharedTestCode\LdClientEndToEndTests.cs - - - SharedTestCode\LdClientEvaluationTests.cs - - - SharedTestCode\LdClientEventTests.cs - - - SharedTestCode\LdClientTests.cs - - - SharedTestCode\MobilePollingProcessorTests.cs - - - SharedTestCode\MockComponents.cs - - - SharedTestCode\TestLogging.cs - - - SharedTestCode\TestUtil.cs - - - SharedTestCode\UserFlagCacheTests.cs - + - {7717A2B2-9905-40A7-989F-790139D69543} - LaunchDarkly.XamarinSdk \ No newline at end of file From 17b5d70feb1252ef21cf845432b72affc8e6757d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 4 May 2021 13:59:19 -0700 Subject: [PATCH 341/499] better workaround for iOS storyboard build problem (#115) --- .circleci/config.yml | 18 +++--------------- .../scripts}/macos-install-android-sdk.sh | 0 .../scripts}/macos-install-xamarin.sh | 12 ++++++++++++ 3 files changed, 15 insertions(+), 15 deletions(-) rename {scripts => .circleci/scripts}/macos-install-android-sdk.sh (100%) rename {scripts => .circleci/scripts}/macos-install-xamarin.sh (87%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 682e43ea..9537f00c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,11 +34,11 @@ jobs: - run: name: Install .NET/Xamarin build tools - command: ./scripts/macos-install-xamarin.sh android + command: .circleci/scripts/macos-install-xamarin.sh android - run: name: Install Android SDK - command: ./scripts/macos-install-android-sdk.sh 27 + command: .circleci/scripts/macos-install-android-sdk.sh 27 - run: name: Set up emulator @@ -104,24 +104,12 @@ jobs: - run: name: Install .NET/Xamarin build tools - command: ./scripts/macos-install-xamarin.sh ios + command: .circleci/scripts/macos-install-xamarin.sh ios - run: name: Build SDK command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj - - run: - name: Pre-build storyboard - command: >- - /Applications/Xcode.app/Contents/Developer/usr/bin/ibtool --errors --warnings --notices --output-format xml1 --minimum-deployment-target 10.0 - --target-device iphone --target-device ipad --auto-activate-custom-fonts - --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.4.sdk - --compilation-directory /Users/distiller/project/tests/LaunchDarkly.XamarinSdk.iOS.Tests/obj/iPhoneSimulator/Debug/xamarin.ios10/ibtool - /Users/distiller/project/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchScreen.storyboard - # This is the exact ibtool command that msbuild runs to build the first storyboard. The difference is that msbuild sets some environment variables - # which cause this command to fail. By pre-running this command, some unknown state gets set up that allows future calls to ibtool to succeed. - # It is unclear where this state resides or how this works... - - run: name: Build test project command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj diff --git a/scripts/macos-install-android-sdk.sh b/.circleci/scripts/macos-install-android-sdk.sh similarity index 100% rename from scripts/macos-install-android-sdk.sh rename to .circleci/scripts/macos-install-android-sdk.sh diff --git a/scripts/macos-install-xamarin.sh b/.circleci/scripts/macos-install-xamarin.sh similarity index 87% rename from scripts/macos-install-xamarin.sh rename to .circleci/scripts/macos-install-xamarin.sh index 3771b35d..0ec57028 100755 --- a/scripts/macos-install-xamarin.sh +++ b/.circleci/scripts/macos-install-xamarin.sh @@ -72,3 +72,15 @@ do exit 1 esac done + +# In the CircleCI environment, /Applications/Xcode.app might be a symlink. The build +# tools can be confused by this (https://github.com/xamarin/xamarin-macios/issues/11006) +# so we'll just move things around so that is a real file. + +if [[ -L /Applications/Xcode.app ]]; then + cd /Applications + real_xcode=`readlink Xcode.app` + rm Xcode.app + mv "$real_xcode" Xcode.app + sudo xcode-select --switch /Applications/Xcode.app +fi From 2e9eee955ee0d2a6d194b09a5c2c63d764bed936 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 9 Jun 2021 19:15:57 -0700 Subject: [PATCH 342/499] update dependencies to latest releases --- .../LaunchDarkly.XamarinSdk.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 1ac2d3d3..102db422 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -37,10 +37,10 @@ - - - - + + + + From 04461492140f28550efd5268f485861ce0c5bbef Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 10 Jun 2021 18:08:22 -0700 Subject: [PATCH 343/499] drop support for Android 7.1 and 8.0 --- CONTRIBUTING.md | 2 +- README.md | 2 +- src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6fa0311..777474f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj Currently this command can only be run on MacOS, because that is the only platform that allows building for all of the targets (.NET Standard, Android, and iOS). -To build the SDK for only one of the supported platforms, add `-f X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: +To build the SDK for only one of the supported platforms, add `-f X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid81` for Android 8.1, etc.: ``` msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj -f netstandard2.0 diff --git a/README.md b/README.md index 4e3ff093..4c290cd4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ LaunchDarkly overview Supported platforms ------------------- -This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. +This beta release is built for the following targets: Android 8.1; iOS 10; .NET Standard 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. Note that if you are targeting .NET Framework, there is a [known issue](https://stackoverflow.com/questions/46788323/installing-a-netstandard-2-0-nuget-package-into-a-vs2015-net-4-6-1-project) in Visual Studio 2015 where it does not correctly detect compatibility with .NET Standard packages. This is not a problem in later versions of Visual Studio. diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 102db422..3a028998 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -3,7 +3,7 @@ 2.0.0-alpha.1 - netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 + netstandard2.0;xamarin.ios10;monoandroid81 $(BaseTargetFrameworks);net452 $(BaseTargetFrameworks) $(LD_TARGET_FRAMEWORKS) From faf65c1042d31fd12373edb7c8eb51fb462876ec Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 6 Jul 2021 10:14:26 -0700 Subject: [PATCH 344/499] (#1) code reorganization, standardize namespaces (#117) --- CONTRIBUTING.md | 10 ++-- src/LaunchDarkly.XamarinSdk/Configuration.cs | 3 +- .../ConfigurationBuilder.cs | 3 +- .../FlagChangedEvent.cs | 3 +- .../ILdClientExtensions.cs | 1 + .../{ => Interfaces}/ILdClient.cs | 2 +- .../{ => Internal}/Constants.cs | 2 +- .../DataSources}/ConnectionManager.cs | 5 +- .../DefaultBackgroundModeManager.cs | 3 +- .../DefaultConnectivityStateManager.cs | 3 +- .../DataSources}/FeatureFlagRequestor.cs | 2 +- .../DataSources}/MobilePollingProcessor.cs | 7 +-- .../DataSources}/MobileStreamingProcessor.cs | 14 ++--- .../DataStores}/DefaultPersistentStorage.cs | 3 +- .../DataStores}/FlagCacheManager.cs | 3 +- .../DataStores}/UserFlagDeviceCache.cs | 3 +- .../DataStores}/UserFlagInMemoryCache.cs | 3 +- .../{ => Internal}/DefaultDeviceInfo.cs | 3 +- .../{ => Internal}/Factory.cs | 6 ++- .../{ => Internal}/FeatureFlag.cs | 2 +- .../Interfaces}/IBackgroundModeManager.cs | 2 +- .../Interfaces}/IConnectivityStateManager.cs | 2 +- .../{ => Internal/Interfaces}/IDeviceInfo.cs | 2 +- .../Interfaces}/IFlagCacheManager.cs | 2 +- .../Interfaces}/IMobileUpdateProcessor.cs | 2 +- .../Interfaces}/IPersistentStorage.cs | 2 +- .../Interfaces}/IUserFlagCache.cs | 2 +- .../{ => Internal}/JsonUtil.cs | 2 +- .../{ => Internal}/LockUtils.cs | 2 +- .../LaunchDarkly.XamarinSdk.csproj | 3 +- src/LaunchDarkly.XamarinSdk/LdClient.cs | 3 ++ ...unchDarkly.XamarinSdk.Android.Tests.csproj | 51 ++++++++++++++----- .../MainActivity.cs | 1 + .../LaunchDarkly.XamarinSdk.Tests/BaseTest.cs | 1 + .../FeatureFlagBuilder.cs | 2 +- .../ILdClientExtensionsTest.cs | 1 + .../Internal/Base64Test.cs | 2 +- .../DataSources}/FeatureFlagRequestorTests.cs | 2 +- .../MobilePollingProcessorTests.cs | 4 +- .../MobileStreamingProcessorTests.cs | 4 +- .../DataStores}/FlagCacheManagerTests.cs | 6 ++- .../DataStores}/UserFlagCacheTests.cs | 5 +- .../{ => Internal}/DefaultDeviceInfoTests.cs | 2 +- .../LDClientEndToEndTests.cs | 1 + .../LdClientTests.cs | 3 +- .../MockComponents.cs | 3 ++ .../LaunchDarkly.XamarinSdk.Tests/TestUtil.cs | 3 ++ .../LaunchDarkly.XamarinSdk.iOS.Tests.csproj | 33 +++++++++--- .../LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs | 1 + 49 files changed, 156 insertions(+), 74 deletions(-) rename src/LaunchDarkly.XamarinSdk/{ => Interfaces}/ILdClient.cs (99%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/Constants.cs (95%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/ConnectionManager.cs (98%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/DefaultBackgroundModeManager.cs (81%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/DefaultConnectivityStateManager.cs (89%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/FeatureFlagRequestor.cs (98%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/MobilePollingProcessor.cs (97%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataSources}/MobileStreamingProcessor.cs (98%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataStores}/DefaultPersistentStorage.cs (82%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataStores}/FlagCacheManager.cs (98%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataStores}/UserFlagDeviceCache.cs (92%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/DataStores}/UserFlagInMemoryCache.cs (92%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/DefaultDeviceInfo.cs (86%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/Factory.cs (95%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/FeatureFlag.cs (98%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IBackgroundModeManager.cs (79%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IConnectivityStateManager.cs (76%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IDeviceInfo.cs (58%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IFlagCacheManager.cs (89%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IMobileUpdateProcessor.cs (93%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IPersistentStorage.cs (84%) rename src/LaunchDarkly.XamarinSdk/{ => Internal/Interfaces}/IUserFlagCache.cs (91%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/JsonUtil.cs (97%) rename src/LaunchDarkly.XamarinSdk/{ => Internal}/LockUtils.cs (95%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal/DataSources}/FeatureFlagRequestorTests.cs (98%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal/DataSources}/MobilePollingProcessorTests.cs (90%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal/DataSources}/MobileStreamingProcessorTests.cs (96%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal/DataStores}/FlagCacheManagerTests.cs (94%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal/DataStores}/UserFlagCacheTests.cs (86%) rename tests/LaunchDarkly.XamarinSdk.Tests/{ => Internal}/DefaultDeviceInfoTests.cs (95%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6fa0311..dd1116b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We encourage pull requests and other contributions from the community. Before su ### Prerequisites -The .NET Standard 1.6 and 2.0 targets require only the .NET Core 2.0 SDK, while the iOS and Android targets require the corresponding Xamarin SDKs. +The .NET Standard target requires only the .NET Core 2.1 SDK, while the iOS and Android targets require the corresponding Xamarin SDKs. ### Building @@ -26,10 +26,10 @@ msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj Currently this command can only be run on MacOS, because that is the only platform that allows building for all of the targets (.NET Standard, Android, and iOS). -To build the SDK for only one of the supported platforms, add `-f X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: +To build the SDK for only one of the supported platforms, add `/p:TargetFramework=X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: ``` -msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj -f netstandard2.0 +msbuild /restore /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj ``` Note that the main project, `src/LaunchDarkly.XamarinSdk`, contains source files that are built for all platforms (ending in just `.cs`, or `.shared.cs`), and also a smaller amount of code that is conditionally compiled for platform-specific functionality. The latter is all in the `PlatformSpecific` folder. We use `#ifdef` directives only for small sections that differ slightly between platform versions; otherwise the conditional compilation is done according to filename suffix (`.android.cs`, etc.) based on rules in the `.csproj` file. @@ -39,8 +39,8 @@ Note that the main project, `src/LaunchDarkly.XamarinSdk`, contains source files The .NET Standard unit tests cover all of the non-platform-specific functionality, as well as behavior specific to .NET Standard (e.g. caching flags in the filesystem). They can be run with only the basic Xamarin framework installed, via the `dotnet` tool: ``` -dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 -dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 +msbuild /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk +dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj ``` The equivalent test suites in Android or iOS must be run in an Android or iOS emulator. The projects `tests/LaunchDarkly.XamarinSdk.Android.Tests` and `tests/LaunchDarkly.XamarinSdk.iOS.Tests` consist of applications based on the `xunit.runner.devices` tool, which show the test results visually in the emulator and also write the results to the emulator's system log. The actual unit test code is just the same tests from the main `tests/LaunchDarkly.XamarinSdk.Tests` project, but running them in this way exercises the mobile-specific behavior for those platforms (e.g. caching flags in user preferences). diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.XamarinSdk/Configuration.cs index 23868153..fcbc95b2 100644 --- a/src/LaunchDarkly.XamarinSdk/Configuration.cs +++ b/src/LaunchDarkly.XamarinSdk/Configuration.cs @@ -5,6 +5,7 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; namespace LaunchDarkly.Sdk.Xamarin { @@ -96,7 +97,7 @@ public sealed class Configuration /// /// /// The additional information will then be available through the client's "detail" - /// methods such as . Since this + /// methods such as . Since this /// increases the size of network requests, such information is not sent unless you set this option /// to . /// diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs index ff681e97..11bad377 100644 --- a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs @@ -3,6 +3,7 @@ using System.Net.Http; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; namespace LaunchDarkly.Sdk.Xamarin { @@ -93,7 +94,7 @@ public interface IConfigurationBuilder /// /// /// The additional information will then be available through the client's "detail" - /// methods such as . Since this + /// methods such as . Since this /// increases the size of network requests, such information is not sent unless you set this option /// to . /// diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs index bc740c21..a6e2bac2 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs @@ -2,11 +2,12 @@ using System.Linq; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Xamarin.Interfaces; namespace LaunchDarkly.Sdk.Xamarin { /// - /// An event object that is sent to handlers for the event. + /// An event object that is sent to handlers for the event. /// public sealed class FlagChangedEventArgs { diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs index 5daf96d7..6809703d 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs +++ b/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs @@ -1,4 +1,5 @@ using System; +using LaunchDarkly.Sdk.Xamarin.Interfaces; namespace LaunchDarkly.Sdk.Xamarin { diff --git a/src/LaunchDarkly.XamarinSdk/ILdClient.cs b/src/LaunchDarkly.XamarinSdk/Interfaces/ILdClient.cs similarity index 99% rename from src/LaunchDarkly.XamarinSdk/ILdClient.cs rename to src/LaunchDarkly.XamarinSdk/Interfaces/ILdClient.cs index 145b7a42..ca9647c0 100644 --- a/src/LaunchDarkly.XamarinSdk/ILdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/Interfaces/ILdClient.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Interfaces { /// /// Interface for the standard SDK client methods and properties. The only implementation of this is . diff --git a/src/LaunchDarkly.XamarinSdk/Constants.cs b/src/LaunchDarkly.XamarinSdk/Internal/Constants.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/Constants.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Constants.cs index e02f46d5..35819d87 100644 --- a/src/LaunchDarkly.XamarinSdk/Constants.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Constants.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { internal static class Constants { diff --git a/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/ConnectionManager.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/ConnectionManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/ConnectionManager.cs index e31dd792..7b9156aa 100644 --- a/src/LaunchDarkly.XamarinSdk/ConnectionManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/ConnectionManager.cs @@ -3,8 +3,9 @@ using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { /// /// Manages our connection to LaunchDarkly, if any, and encapsulates all of the state that @@ -48,7 +49,7 @@ internal sealed class ConnectionManager : IDisposable /// /// True if we made a successful LaunchDarkly connection or do not need to make one (see - /// ). + /// ). /// public bool Initialized => LockUtils.WithReadLock(_lock, () => _initialized); diff --git a/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultBackgroundModeManager.cs similarity index 81% rename from src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultBackgroundModeManager.cs index 2531ac0c..b791894c 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultBackgroundModeManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultBackgroundModeManager.cs @@ -1,7 +1,8 @@ using System; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { internal class DefaultBackgroundModeManager : IBackgroundModeManager { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultConnectivityStateManager.cs similarity index 89% rename from src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultConnectivityStateManager.cs index f011106a..128f703d 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultConnectivityStateManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultConnectivityStateManager.cs @@ -1,7 +1,8 @@ using System; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { internal sealed class DefaultConnectivityStateManager : IConnectivityStateManager { diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/FeatureFlagRequestor.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/FeatureFlagRequestor.cs index 6cba6e03..d03ccb7b 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -10,7 +10,7 @@ using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Xamarin.Internal; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { internal struct WebResponse { diff --git a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobilePollingProcessor.cs similarity index 97% rename from src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobilePollingProcessor.cs index 0ba56d8b..416358c4 100644 --- a/src/LaunchDarkly.XamarinSdk/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobilePollingProcessor.cs @@ -4,8 +4,9 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { internal sealed class MobilePollingProcessor : IMobileUpdateProcessor { @@ -99,8 +100,8 @@ private async Task UpdateTaskAsync() _log.Error("Error Updating features: '{0}'", LogValues.ExceptionSummary(ex)); _log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); if (_initialized == UNINITIALIZED) - { - _startTask.SetException(ex); + { + _startTask.SetException(ex); } ((IDisposable)this).Dispose(); } diff --git a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobileStreamingProcessor.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobileStreamingProcessor.cs index a817558c..c7896404 100644 --- a/src/LaunchDarkly.XamarinSdk/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobileStreamingProcessor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using System.Net.Http; @@ -8,8 +9,9 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor { @@ -108,11 +110,11 @@ string jsonBody .Logger(_log); return new EventSource.EventSource(configBuilder.Build()); } - - private Uri MakeRequestUriWithPath(string path) - { - var uri = _configuration.StreamUri.AddPath(path); - return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; + + private Uri MakeRequestUriWithPath(string path) + { + var uri = _configuration.StreamUri.AddPath(path); + return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; } private void OnOpen(object sender, EventSource.StateChangedEventArgs e) diff --git a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/DefaultPersistentStorage.cs similarity index 82% rename from src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataStores/DefaultPersistentStorage.cs index 57d8598a..2ecc6da6 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultPersistentStorage.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/DefaultPersistentStorage.cs @@ -1,7 +1,8 @@ using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { internal sealed class DefaultPersistentStorage : IPersistentStorage { diff --git a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/FlagCacheManager.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataStores/FlagCacheManager.cs index 8afd4e33..5186d9e3 100644 --- a/src/LaunchDarkly.XamarinSdk/FlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/FlagCacheManager.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { internal sealed class FlagCacheManager : IFlagCacheManager { diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagDeviceCache.cs similarity index 92% rename from src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagDeviceCache.cs index 4da6c091..c0a34691 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagDeviceCache.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Immutable; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { internal sealed class UserFlagDeviceCache : IUserFlagCache { diff --git a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagInMemoryCache.cs similarity index 92% rename from src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagInMemoryCache.cs index 4bd8442d..672763bd 100644 --- a/src/LaunchDarkly.XamarinSdk/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagInMemoryCache.cs @@ -1,7 +1,8 @@ using System.Collections.Concurrent; using System.Collections.Immutable; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { internal sealed class UserFlagInMemoryCache : IUserFlagCache { diff --git a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/Internal/DefaultDeviceInfo.cs similarity index 86% rename from src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs rename to src/LaunchDarkly.XamarinSdk/Internal/DefaultDeviceInfo.cs index 28830a39..61e71486 100644 --- a/src/LaunchDarkly.XamarinSdk/DefaultDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/DefaultDeviceInfo.cs @@ -1,7 +1,8 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { // This just delegates to the conditionally-compiled code in LaunchDarkly.Xamarin.PlatformSpecific. // The only reason it is a pluggable component is for unit tests; we don't currently expose IDeviceInfo. diff --git a/src/LaunchDarkly.XamarinSdk/Factory.cs b/src/LaunchDarkly.XamarinSdk/Internal/Factory.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/Factory.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Factory.cs index 7b499b87..a87379ce 100644 --- a/src/LaunchDarkly.XamarinSdk/Factory.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Factory.cs @@ -2,10 +2,12 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; -using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.DataSources; +using LaunchDarkly.Sdk.Xamarin.Internal.DataStores; using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { internal static class Factory { diff --git a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs b/src/LaunchDarkly.XamarinSdk/Internal/FeatureFlag.cs similarity index 98% rename from src/LaunchDarkly.XamarinSdk/FeatureFlag.cs rename to src/LaunchDarkly.XamarinSdk/Internal/FeatureFlag.cs index 2c78d6c1..3b679763 100644 --- a/src/LaunchDarkly.XamarinSdk/FeatureFlag.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/FeatureFlag.cs @@ -2,7 +2,7 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { [JsonStreamConverter(typeof(FeatureFlag.JsonConverter))] internal sealed class FeatureFlag : IEquatable, IJsonSerializable diff --git a/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IBackgroundModeManager.cs similarity index 79% rename from src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IBackgroundModeManager.cs index c144a038..4cfbd542 100644 --- a/src/LaunchDarkly.XamarinSdk/IBackgroundModeManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IBackgroundModeManager.cs @@ -1,7 +1,7 @@ using System; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IBackgroundModeManager { diff --git a/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IConnectivityStateManager.cs similarity index 76% rename from src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IConnectivityStateManager.cs index 076127bf..faebc0b9 100644 --- a/src/LaunchDarkly.XamarinSdk/IConnectivityStateManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IConnectivityStateManager.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IConnectivityStateManager { diff --git a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IDeviceInfo.cs similarity index 58% rename from src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IDeviceInfo.cs index 4682663b..9ad1e862 100644 --- a/src/LaunchDarkly.XamarinSdk/IDeviceInfo.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IDeviceInfo.cs @@ -1,4 +1,4 @@ -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IDeviceInfo { diff --git a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IFlagCacheManager.cs similarity index 89% rename from src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IFlagCacheManager.cs index 9e25a5f1..62ceb8cd 100644 --- a/src/LaunchDarkly.XamarinSdk/IFlagCacheManager.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IFlagCacheManager.cs @@ -1,6 +1,6 @@ using System.Collections.Immutable; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IFlagCacheManager { diff --git a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IMobileUpdateProcessor.cs similarity index 93% rename from src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IMobileUpdateProcessor.cs index 725229d6..7d4cd311 100644 --- a/src/LaunchDarkly.XamarinSdk/IMobileUpdateProcessor.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IMobileUpdateProcessor.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { /// /// Interface for an object that receives updates to feature flags, user segments, and anything diff --git a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IPersistentStorage.cs similarity index 84% rename from src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IPersistentStorage.cs index eb83ee4e..4236b6a8 100644 --- a/src/LaunchDarkly.XamarinSdk/IPersistentStorage.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IPersistentStorage.cs @@ -1,4 +1,4 @@ -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IPersistentStorage { diff --git a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IUserFlagCache.cs similarity index 91% rename from src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs rename to src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IUserFlagCache.cs index 6c046d71..77967427 100644 --- a/src/LaunchDarkly.XamarinSdk/IUserFlagCache.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IUserFlagCache.cs @@ -1,6 +1,6 @@ using System.Collections.Immutable; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.Interfaces { internal interface IUserFlagCache { diff --git a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs b/src/LaunchDarkly.XamarinSdk/Internal/JsonUtil.cs similarity index 97% rename from src/LaunchDarkly.XamarinSdk/JsonUtil.cs rename to src/LaunchDarkly.XamarinSdk/Internal/JsonUtil.cs index f825eedd..e916a835 100644 --- a/src/LaunchDarkly.XamarinSdk/JsonUtil.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/JsonUtil.cs @@ -4,7 +4,7 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { internal class JsonUtil { diff --git a/src/LaunchDarkly.XamarinSdk/LockUtils.cs b/src/LaunchDarkly.XamarinSdk/Internal/LockUtils.cs similarity index 95% rename from src/LaunchDarkly.XamarinSdk/LockUtils.cs rename to src/LaunchDarkly.XamarinSdk/Internal/LockUtils.cs index 09346a42..f5b9f508 100644 --- a/src/LaunchDarkly.XamarinSdk/LockUtils.cs +++ b/src/LaunchDarkly.XamarinSdk/Internal/LockUtils.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { internal static class LockUtils { diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj index 102db422..c844acc8 100644 --- a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +++ b/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj @@ -34,7 +34,7 @@ - + @@ -45,6 +45,7 @@ + diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.XamarinSdk/LdClient.cs index 65f38256..44aa4ca0 100644 --- a/src/LaunchDarkly.XamarinSdk/LdClient.cs +++ b/src/LaunchDarkly.XamarinSdk/LdClient.cs @@ -5,8 +5,11 @@ using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Xamarin.Interfaces; using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.DataSources; using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; namespace LaunchDarkly.Sdk.Xamarin diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj index c3cbd0ed..cd85555e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj @@ -1,4 +1,9 @@  + + + + Debug @@ -51,6 +56,7 @@ True armeabi-v7a;arm64-v8a + @@ -59,16 +65,44 @@ + + + + + + + + + + + + + + + - + + + Designer @@ -94,24 +128,13 @@ - - - - - - - - - - - - - + {7717A2B2-9905-40A7-989F-790139D69543} LaunchDarkly.XamarinSdk + diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs index d511c86c..17be6ffe 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs +++ b/tests/LaunchDarkly.XamarinSdk.Android.Tests/MainActivity.cs @@ -5,6 +5,7 @@ using Xunit.Runners.UI; using Xunit.Sdk; +// For more details about how this test project works, see CONTRIBUTING.md namespace LaunchDarkly.Xamarin.Android.Tests { [Activity(Label = "LaunchDarkly.Xamarin.Android.Tests", MainLauncher = true)] diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs index a0dea2cc..ed64233e 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/BaseTest.cs @@ -1,5 +1,6 @@ using System; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Xamarin.Internal; using Xunit; using Xunit.Abstractions; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs index 9043916e..03a06a30 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagBuilder.cs @@ -1,4 +1,4 @@ -using System; +using LaunchDarkly.Sdk.Xamarin.Internal; namespace LaunchDarkly.Sdk.Xamarin { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs index f875b663..a4571f40 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/ILdClientExtensionsTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Xamarin.Interfaces; using Xunit; namespace LaunchDarkly.Sdk.Xamarin diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs index 5bbf460d..450ed917 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/Base64Test.cs @@ -2,7 +2,7 @@ namespace LaunchDarkly.Sdk.Xamarin.Internal { - public class ExtensionsTest + public class Base64Test { [Fact] public void TestUrlSafeBase64Encode() diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs similarity index 98% rename from tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 18f8389e..bf3352a1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { // End-to-end tests of this component against an embedded HTTP server. public class FeatureFlagRequestorTests : BaseTest diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs similarity index 90% rename from tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs index 4c504f43..59fcfda5 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs @@ -1,8 +1,10 @@ using System; +using LaunchDarkly.Sdk.Xamarin.Internal.DataStores; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { public class MobilePollingProcessorTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs similarity index 96% rename from tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs index f9b450d2..8e59d0ad 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs @@ -3,10 +3,12 @@ using System.Threading.Tasks; using LaunchDarkly.EventSource; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Xamarin.Internal.DataStores; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataSources { public class MobileStreamingProcessorTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs similarity index 94% rename from tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs index e0e51aa4..8f879118 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/FlagCacheManagerTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs @@ -1,7 +1,9 @@ -using Xunit; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; +using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { public class FlagCacheManagerTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs similarity index 86% rename from tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs index b043c21d..c29ebeb7 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/UserFlagCacheTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs @@ -1,6 +1,7 @@ -using Xunit; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; +using Xunit; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal.DataStores { public class UserFlagCacheTests : BaseTest { diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DefaultDeviceInfoTests.cs similarity index 95% rename from tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs rename to tests/LaunchDarkly.XamarinSdk.Tests/Internal/DefaultDeviceInfoTests.cs index b5aab2c9..ddd09380 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/DefaultDeviceInfoTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/Internal/DefaultDeviceInfoTests.cs @@ -2,7 +2,7 @@ using Xunit; using Xunit.Abstractions; -namespace LaunchDarkly.Sdk.Xamarin +namespace LaunchDarkly.Sdk.Xamarin.Internal { // The DefaultDeviceInfo functionality is also tested by LdClientEndToEndTests.InitWithKeylessAnonUserAddsKeyAndReusesIt(), // which is a more realistic test since it uses a full client instance. However, currently LdClientEndToEndTests can't be diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs index 7f11e975..bc888274 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LDClientEndToEndTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Xamarin.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs index 74c999de..f2ea7480 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/LdClientTests.cs @@ -1,7 +1,8 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using Xunit; using Xunit.Abstractions; diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs index 37c58555..f5cb9610 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/MockComponents.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.DataSources; using LaunchDarkly.Sdk.Xamarin.Internal.Events; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using LaunchDarkly.Sdk.Xamarin.PlatformSpecific; namespace LaunchDarkly.Sdk.Xamarin diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs index b9bc367c..10c7a431 100644 --- a/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.XamarinSdk.Tests/TestUtil.cs @@ -2,6 +2,9 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Xamarin.Internal; +using LaunchDarkly.Sdk.Xamarin.Internal.DataStores; +using LaunchDarkly.Sdk.Xamarin.Internal.Interfaces; using Xunit; namespace LaunchDarkly.Sdk.Xamarin diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj index b8277cbf..18b151ee 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj @@ -1,4 +1,7 @@ + + + Debug @@ -61,6 +64,7 @@ false iPhone Developer + @@ -69,12 +73,32 @@ + + + + + + + + + false @@ -119,10 +143,7 @@ false - - - - + @@ -131,9 +152,7 @@ - - - + diff --git a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs index fa24461b..6e9928ee 100644 --- a/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs +++ b/tests/LaunchDarkly.XamarinSdk.iOS.Tests/Main.cs @@ -1,5 +1,6 @@ using UIKit; +// For more details about how this test project works, see CONTRIBUTING.md namespace LaunchDarkly.Xamarin.iOS.Tests { public class Application From bdb748f448953983595232525942949da7f03b0f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 6 Jul 2021 10:18:33 -0700 Subject: [PATCH 345/499] (#2) rename SDK in the readme and project names (#118) --- .circleci/config.yml | 24 +++--- .ldrelease/mac-build.sh | 2 +- .ldrelease/mac-publish.sh | 2 +- .ldrelease/mac-test.sh | 2 +- .ldrelease/update-version.sh | 2 +- .ldrelease/windows-build.ps1 | 2 +- CONTRIBUTING.md | 14 ++-- ...marinSdk.sln => LaunchDarkly.ClientSdk.sln | 8 +- README.md | 72 ++++++++++++------ docs/project.shfbproj | 18 ++--- scripts/build-docs.ps1 | 10 +-- scripts/build-test-package.sh | 4 +- scripts/package.sh | 8 +- scripts/publish-docs.sh | 2 +- scripts/release.sh | 2 +- scripts/update-version.sh | 2 +- .../Configuration.cs | 0 .../ConfigurationBuilder.cs | 0 .../FlagChangedEvent.cs | 0 .../ILdClientExtensions.cs | 0 .../Interfaces/ILdClient.cs | 0 .../Internal/Base64.cs | 0 .../Internal/Constants.cs | 0 .../Internal/DataSources/ConnectionManager.cs | 0 .../DefaultBackgroundModeManager.cs | 0 .../DefaultConnectivityStateManager.cs | 0 .../DataSources/FeatureFlagRequestor.cs | 0 .../DataSources/MobilePollingProcessor.cs | 0 .../DataSources/MobileStreamingProcessor.cs | 0 .../DataStores/DefaultPersistentStorage.cs | 0 .../Internal/DataStores/FlagCacheManager.cs | 0 .../DataStores/UserFlagDeviceCache.cs | 0 .../DataStores/UserFlagInMemoryCache.cs | 0 .../Internal/DefaultDeviceInfo.cs | 0 .../Events/DefaultEventProcessorWrapper.cs | 0 .../Internal/Events/EventFactory.cs | 0 .../Internal/Events/EventProcessorTypes.cs | 0 .../Internal/Events/IEventProcessor.cs | 0 .../Internal/Factory.cs | 0 .../Internal/FeatureFlag.cs | 0 .../Interfaces/IBackgroundModeManager.cs | 0 .../Interfaces/IConnectivityStateManager.cs | 0 .../Internal/Interfaces/IDeviceInfo.cs | 0 .../Internal/Interfaces/IFlagCacheManager.cs | 0 .../Interfaces/IMobileUpdateProcessor.cs | 0 .../Internal/Interfaces/IPersistentStorage.cs | 0 .../Internal/Interfaces/IUserFlagCache.cs | 0 .../Internal/JsonUtil.cs | 0 .../Internal/LockUtils.cs | 0 .../Internal/LogNames.cs | 0 .../LaunchDarkly.ClientSdk.csproj} | 0 .../LdClient.cs | 0 .../NamespaceDoc.cs | 0 .../AsyncScheduler.android.cs | 0 .../PlatformSpecific/AsyncScheduler.ios.cs | 0 .../AsyncScheduler.netstandard.cs | 0 .../PlatformSpecific/AsyncScheduler.shared.cs | 0 .../BackgroundDetection.android.cs | 0 .../BackgroundDetection.ios.cs | 0 .../BackgroundDetection.netstandard.cs | 0 .../BackgroundDetection.shared.cs | 0 .../ClientIdentifier.android.cs | 0 .../PlatformSpecific/ClientIdentifier.ios.cs | 0 .../ClientIdentifier.netstandard.cs | 0 .../ClientIdentifier.shared.cs | 0 .../PlatformSpecific/Connectivity.android.cs | 0 .../PlatformSpecific/Connectivity.ios.cs | 0 .../Connectivity.netstandard.cs | 0 .../PlatformSpecific/Connectivity.shared.cs | 0 .../PlatformSpecific/Http.android.cs | 0 .../PlatformSpecific/Http.ios.cs | 0 .../PlatformSpecific/Http.netstandard.cs | 0 .../PlatformSpecific/Http.shared.cs | 0 .../PlatformSpecific/Permissions.android.cs | 0 .../PlatformSpecific/Permissions.shared.cs | 0 .../PlatformSpecific/Platform.android.cs | 0 .../PlatformSpecific/Platform.shared.cs | 0 .../PlatformSpecific/Preferences.android.cs | 0 .../PlatformSpecific/Preferences.ios.cs | 0 .../Preferences.netstandard.cs | 0 .../PlatformSpecific/Preferences.shared.cs | 0 .../PlatformSpecific/UserMetadata.android.cs | 0 .../PlatformSpecific/UserMetadata.ios.cs | 0 .../UserMetadata.netstandard.cs | 0 .../PlatformSpecific/UserMetadata.shared.cs | 0 .../PlatformType.cs | 0 .../Properties/AssemblyInfo.cs | 6 ++ .../Properties/AssemblyInfo.cs | 6 -- .../AndroidSpecificTests.cs | 0 ...unchDarkly.ClientSdk.Android.Tests.csproj} | 8 +- .../MainActivity.cs | 0 .../Properties/AndroidManifest.xml | 0 .../Properties/AssemblyInfo.cs | 0 .../Resources/Resource.designer.cs | 0 .../Resources/layout/activity_main.axml | 0 .../mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../Resources/mipmap-hdpi/ic_launcher.png | Bin .../mipmap-hdpi/ic_launcher_foreground.png | Bin .../mipmap-hdpi/ic_launcher_round.png | Bin .../Resources/mipmap-mdpi/ic_launcher.png | Bin .../mipmap-mdpi/ic_launcher_foreground.png | Bin .../mipmap-mdpi/ic_launcher_round.png | Bin .../Resources/mipmap-xhdpi/ic_launcher.png | Bin .../mipmap-xhdpi/ic_launcher_foreground.png | Bin .../mipmap-xhdpi/ic_launcher_round.png | Bin .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxhdpi/ic_launcher_round.png | Bin .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxxhdpi/ic_launcher_round.png | Bin .../Resources/values/colors.xml | 0 .../values/ic_launcher_background.xml | 0 .../Resources/values/strings.xml | 0 .../BaseTest.cs | 0 .../ConfigurationTest.cs | 0 .../FeatureFlagBuilder.cs | 0 .../FlagChangedEventTests.cs | 0 .../ILdClientExtensionsTest.cs | 0 .../Internal/Base64Test.cs | 0 .../DataSources/FeatureFlagRequestorTests.cs | 0 .../MobilePollingProcessorTests.cs | 0 .../MobileStreamingProcessorTests.cs | 0 .../DataStores/FlagCacheManagerTests.cs | 0 .../Internal/DataStores/UserFlagCacheTests.cs | 0 .../Internal/DefaultDeviceInfoTests.cs | 0 .../LDClientEndToEndTests.cs | 0 .../LaunchDarkly.ClientSdk.Tests.csproj} | 4 +- .../LdClientEvaluationTests.cs | 0 .../LdClientEventTests.cs | 0 .../LdClientTests.cs | 0 .../MockComponents.cs | 0 .../TestLogging.cs | 0 .../TestUtil.cs | 0 .../xunit-to-junit.xslt | 0 .../AppDelegate.cs | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon1024.png | Bin .../AppIcon.appiconset/Icon120.png | Bin .../AppIcon.appiconset/Icon152.png | Bin .../AppIcon.appiconset/Icon167.png | Bin .../AppIcon.appiconset/Icon180.png | Bin .../AppIcon.appiconset/Icon20.png | Bin .../AppIcon.appiconset/Icon29.png | Bin .../AppIcon.appiconset/Icon40.png | Bin .../AppIcon.appiconset/Icon58.png | Bin .../AppIcon.appiconset/Icon60.png | Bin .../AppIcon.appiconset/Icon76.png | Bin .../AppIcon.appiconset/Icon80.png | Bin .../AppIcon.appiconset/Icon87.png | Bin .../Entitlements.plist | 0 .../IOsSpecificTests.cs | 0 .../Info.plist | 6 +- .../LaunchDarkly.ClientSdk.iOS.Tests.csproj} | 6 +- .../LaunchScreen.storyboard | 0 .../Main.cs | 0 .../Main.storyboard | 0 .../Properties/AssemblyInfo.cs | 0 .../Resources/LaunchScreen.xib | 0 160 files changed, 117 insertions(+), 93 deletions(-) rename LaunchDarkly.XamarinSdk.sln => LaunchDarkly.ClientSdk.sln (88%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Configuration.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/ConfigurationBuilder.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/FlagChangedEvent.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/ILdClientExtensions.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Interfaces/ILdClient.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Base64.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Constants.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/ConnectionManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/DefaultBackgroundModeManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/DefaultConnectivityStateManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/FeatureFlagRequestor.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/MobilePollingProcessor.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataSources/MobileStreamingProcessor.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataStores/DefaultPersistentStorage.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataStores/FlagCacheManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataStores/UserFlagDeviceCache.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DataStores/UserFlagInMemoryCache.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/DefaultDeviceInfo.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Events/DefaultEventProcessorWrapper.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Events/EventFactory.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Events/EventProcessorTypes.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Events/IEventProcessor.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Factory.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/FeatureFlag.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IBackgroundModeManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IConnectivityStateManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IDeviceInfo.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IFlagCacheManager.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IMobileUpdateProcessor.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IPersistentStorage.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/Interfaces/IUserFlagCache.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/JsonUtil.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/LockUtils.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/Internal/LogNames.cs (100%) rename src/{LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj => LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj} (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/LdClient.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/NamespaceDoc.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/AsyncScheduler.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/AsyncScheduler.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/AsyncScheduler.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/AsyncScheduler.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/BackgroundDetection.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/BackgroundDetection.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/BackgroundDetection.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/BackgroundDetection.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/ClientIdentifier.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/ClientIdentifier.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/ClientIdentifier.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/ClientIdentifier.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Connectivity.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Connectivity.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Connectivity.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Connectivity.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Http.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Http.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Http.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Http.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Permissions.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Permissions.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Platform.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Platform.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Preferences.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Preferences.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Preferences.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/Preferences.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/UserMetadata.android.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/UserMetadata.ios.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/UserMetadata.netstandard.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformSpecific/UserMetadata.shared.cs (100%) rename src/{LaunchDarkly.XamarinSdk => LaunchDarkly.ClientSdk}/PlatformType.cs (100%) create mode 100644 src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs delete mode 100644 src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/AndroidSpecificTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj => LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj} (94%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/MainActivity.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Properties/AndroidManifest.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Properties/AssemblyInfo.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/Resource.designer.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/layout/activity_main.axml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-anydpi-v26/ic_launcher.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-hdpi/ic_launcher.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-hdpi/ic_launcher_foreground.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-hdpi/ic_launcher_round.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-mdpi/ic_launcher.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-mdpi/ic_launcher_foreground.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-mdpi/ic_launcher_round.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xhdpi/ic_launcher.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xhdpi/ic_launcher_foreground.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xhdpi/ic_launcher_round.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxhdpi/ic_launcher.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxhdpi/ic_launcher_foreground.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxhdpi/ic_launcher_round.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxxhdpi/ic_launcher.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/values/colors.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/values/ic_launcher_background.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Android.Tests => LaunchDarkly.ClientSdk.Android.Tests}/Resources/values/strings.xml (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/BaseTest.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/ConfigurationTest.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/FeatureFlagBuilder.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/FlagChangedEventTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/ILdClientExtensionsTest.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/Base64Test.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DataSources/FeatureFlagRequestorTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DataSources/MobilePollingProcessorTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DataSources/MobileStreamingProcessorTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DataStores/FlagCacheManagerTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DataStores/UserFlagCacheTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/Internal/DefaultDeviceInfoTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/LDClientEndToEndTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj => LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj} (83%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/LdClientEvaluationTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/LdClientEventTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/LdClientTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/MockComponents.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/TestLogging.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/TestUtil.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.Tests => LaunchDarkly.ClientSdk.Tests}/xunit-to-junit.xslt (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/AppDelegate.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon1024.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon120.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon152.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon167.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon180.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon20.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon29.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon40.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon58.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon60.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon76.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon80.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Assets.xcassets/AppIcon.appiconset/Icon87.png (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Entitlements.plist (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/IOsSpecificTests.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Info.plist (91%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj => LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj} (95%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/LaunchScreen.storyboard (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Main.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Main.storyboard (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Properties/AssemblyInfo.cs (100%) rename tests/{LaunchDarkly.XamarinSdk.iOS.Tests => LaunchDarkly.ClientSdk.iOS.Tests}/Resources/LaunchScreen.xib (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9537f00c..c7d10dc3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,10 +16,10 @@ jobs: ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests steps: - checkout - - run: dotnet restore src/LaunchDarkly.XamarinSdk - - run: dotnet build src/LaunchDarkly.XamarinSdk -f netstandard2.0 - - run: dotnet restore tests/LaunchDarkly.XamarinSdk.Tests - - run: dotnet test -v=normal tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj + - run: dotnet restore src/LaunchDarkly.ClientSdk + - run: dotnet build src/LaunchDarkly.ClientSdk -f netstandard2.0 + - run: dotnet restore tests/LaunchDarkly.ClientSdk.Tests + - run: dotnet test -v=normal tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj test-android: macos: @@ -55,13 +55,13 @@ jobs: name: Build SDK command: | msbuild /restore /p:TargetFramework=MonoAndroid81 \ - src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - run: name: Build test project command: | msbuild /restore /t:SignAndroidPackage \ - tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj + tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj - run: name: Wait for emulator @@ -76,7 +76,7 @@ jobs: - run: name: Deploy app to emulator - command: adb install tests/LaunchDarkly.XamarinSdk.Android.Tests/bin/Debug/com.launchdarkly.xamarinandroidtests-Signed.apk + command: adb install tests/LaunchDarkly.ClientSdk.Android.Tests/bin/Debug/com.launchdarkly.xamarinandroidtests-Signed.apk - run: name: Start app in emulator @@ -108,11 +108,11 @@ jobs: - run: name: Build SDK - command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj + command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - run: name: Build test project - command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.XamarinSdk.iOS.Tests/LaunchDarkly.XamarinSdk.iOS.Tests.csproj + command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj # Note that we must specify Platform=iPhoneSimulator here explicitly because, when using a current # version of msbuild with a project file that uses MSBuild.Sdk.Extras, it seems like Platform does *not* # default to an empty string (I think it defaults to "AnyCPU"), therefore it will try to build it for a @@ -127,16 +127,16 @@ jobs: - run: name: Load test app into simulator - command: xcrun simctl install "xm-ios" tests/LaunchDarkly.XamarinSdk.iOS.Tests/bin/Debug/xamarin.ios10/LaunchDarkly.XamarinSdk.iOS.Tests.app + command: xcrun simctl install "xm-ios" tests/LaunchDarkly.ClientSdk.iOS.Tests/bin/Debug/xamarin.ios10/LaunchDarkly.ClientSdk.iOS.Tests.app - run: name: Start capturing log output - command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.XamarinSdk.iOS.Tests"' | tee test-run.log + command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.ClientSdk.iOS.Tests"' | tee test-run.log background: true - run: name: Launch test app in simulator - command: xcrun simctl launch "xm-ios" com.launchdarkly.XamarinSdkTests + command: xcrun simctl launch "xm-ios" com.launchdarkly.ClientSdkTests - run: name: Wait for tests to finish running diff --git a/.ldrelease/mac-build.sh b/.ldrelease/mac-build.sh index a2ab24e8..92c4149c 100755 --- a/.ldrelease/mac-build.sh +++ b/.ldrelease/mac-build.sh @@ -5,4 +5,4 @@ set -eu # Build the project for all target frameworks. This includes building the .nupkg, because of # the directive in our project file. -msbuild /restore /p:Configuration=Debug src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore /p:Configuration=Debug src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj diff --git a/.ldrelease/mac-publish.sh b/.ldrelease/mac-publish.sh index 6d04971d..e4908416 100755 --- a/.ldrelease/mac-publish.sh +++ b/.ldrelease/mac-publish.sh @@ -7,5 +7,5 @@ set -eu export AWS_DEFAULT_REGION=us-east-1 NUGET_KEY=$(aws ssm get-parameter --name /production/common/services/nuget/api_key --with-decryption --query "Parameter.Value" --output text) -nuget push "./src/LaunchDarkly.XamarinSdk/bin/Debug/LaunchDarkly.XamarinSdk.${LD_RELEASE_VERSION}.nupkg" \ +nuget push "./src/LaunchDarkly.ClientSdk/bin/Debug/LaunchDarkly.XamarinSdk.${LD_RELEASE_VERSION}.nupkg" \ -ApiKey "${NUGET_KEY}" -Source https://www.nuget.org diff --git a/.ldrelease/mac-test.sh b/.ldrelease/mac-test.sh index 19f45074..6f2d49f9 100755 --- a/.ldrelease/mac-test.sh +++ b/.ldrelease/mac-test.sh @@ -4,4 +4,4 @@ set -eu # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by regular CI jobs) -dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 +dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f netcoreapp2.0 diff --git a/.ldrelease/update-version.sh b/.ldrelease/update-version.sh index a27e8fa1..fc7dcda0 100755 --- a/.ldrelease/update-version.sh +++ b/.ldrelease/update-version.sh @@ -4,7 +4,7 @@ set -eu -PROJECT_FILE=./src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +PROJECT_FILE=./src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj TEMP_FILE="${PROJECT_FILE}.tmp" sed "s#^\( *\)[^<]*#\1${LD_RELEASE_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" diff --git a/.ldrelease/windows-build.ps1 b/.ldrelease/windows-build.ps1 index 5b34ab96..3d550745 100644 --- a/.ldrelease/windows-build.ps1 +++ b/.ldrelease/windows-build.ps1 @@ -8,4 +8,4 @@ $ErrorActionPreference = "Stop" $scriptDir = split-path -parent $MyInvocation.MyCommand.Definition Import-Module "$scriptDir/circleci/template/helpers.psm1" -Force -ExecuteOrFail { msbuild /restore /p:TargetFramework=netstandard2.0 /p:Configuration=Debug src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj } +ExecuteOrFail { msbuild /restore /p:TargetFramework=netstandard2.0 /p:Configuration=Debug src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd1116b4..3de20e71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,29 +21,29 @@ The .NET Standard target requires only the .NET Core 2.1 SDK, while the iOS and To build the SDK (for all target platforms) without running any tests: ``` -msbuild /restore src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj ``` Currently this command can only be run on MacOS, because that is the only platform that allows building for all of the targets (.NET Standard, Android, and iOS). -To build the SDK for only one of the supported platforms, add `/p:TargetFramework=X` where `X` is one of the items in the `` list of `LaunchDarkly.XamarinSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: +To build the SDK for only one of the supported platforms, add `/p:TargetFramework=X` where `X` is one of the items in the `` list of `LaunchDarkly.ClientSdk.csproj`: `netstandard2.0` for .NET Standard 2.0, `MonoAndroid80` for Android 8.0, etc.: ``` -msbuild /restore /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj ``` -Note that the main project, `src/LaunchDarkly.XamarinSdk`, contains source files that are built for all platforms (ending in just `.cs`, or `.shared.cs`), and also a smaller amount of code that is conditionally compiled for platform-specific functionality. The latter is all in the `PlatformSpecific` folder. We use `#ifdef` directives only for small sections that differ slightly between platform versions; otherwise the conditional compilation is done according to filename suffix (`.android.cs`, etc.) based on rules in the `.csproj` file. +Note that the main project, `src/LaunchDarkly.ClientSdk`, contains source files that are built for all platforms (ending in just `.cs`, or `.shared.cs`), and also a smaller amount of code that is conditionally compiled for platform-specific functionality. The latter is all in the `PlatformSpecific` folder. We use `#ifdef` directives only for small sections that differ slightly between platform versions; otherwise the conditional compilation is done according to filename suffix (`.android.cs`, etc.) based on rules in the `.csproj` file. ### Testing The .NET Standard unit tests cover all of the non-platform-specific functionality, as well as behavior specific to .NET Standard (e.g. caching flags in the filesystem). They can be run with only the basic Xamarin framework installed, via the `dotnet` tool: ``` -msbuild /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk -dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +msbuild /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk +dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj ``` -The equivalent test suites in Android or iOS must be run in an Android or iOS emulator. The projects `tests/LaunchDarkly.XamarinSdk.Android.Tests` and `tests/LaunchDarkly.XamarinSdk.iOS.Tests` consist of applications based on the `xunit.runner.devices` tool, which show the test results visually in the emulator and also write the results to the emulator's system log. The actual unit test code is just the same tests from the main `tests/LaunchDarkly.XamarinSdk.Tests` project, but running them in this way exercises the mobile-specific behavior for those platforms (e.g. caching flags in user preferences). +The equivalent test suites in Android or iOS must be run in an Android or iOS emulator. The projects `tests/LaunchDarkly.ClientSdk.Android.Tests` and `tests/LaunchDarkly.ClientSdk.iOS.Tests` consist of applications based on the `xunit.runner.devices` tool, which show the test results visually in the emulator and also write the results to the emulator's system log. The actual unit test code is just the same tests from the main `tests/LaunchDarkly.ClientSdk.Tests` project, but running them in this way exercises the mobile-specific behavior for those platforms (e.g. caching flags in user preferences). You can run the mobile test projects from Visual Studio (the iOS tests require MacOS); there is also a somewhat complicated process for running them from the command line, which is what the CI build does (see `.circleci/config.yml`). diff --git a/LaunchDarkly.XamarinSdk.sln b/LaunchDarkly.ClientSdk.sln similarity index 88% rename from LaunchDarkly.XamarinSdk.sln rename to LaunchDarkly.ClientSdk.sln index 98809c03..abe5b558 100644 --- a/LaunchDarkly.XamarinSdk.sln +++ b/LaunchDarkly.ClientSdk.sln @@ -2,13 +2,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk", "src\LaunchDarkly.XamarinSdk\LaunchDarkly.XamarinSdk.csproj", "{7717A2B2-9905-40A7-989F-790139D69543}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk", "src\LaunchDarkly.ClientSdk\LaunchDarkly.ClientSdk.csproj", "{7717A2B2-9905-40A7-989F-790139D69543}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Tests", "tests\LaunchDarkly.XamarinSdk.Tests\LaunchDarkly.XamarinSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Tests", "tests\LaunchDarkly.ClientSdk.Tests\LaunchDarkly.ClientSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.iOS.Tests", "tests\LaunchDarkly.XamarinSdk.iOS.Tests\LaunchDarkly.XamarinSdk.iOS.Tests.csproj", "{5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.iOS.Tests", "tests\LaunchDarkly.ClientSdk.iOS.Tests\LaunchDarkly.ClientSdk.iOS.Tests.csproj", "{5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.XamarinSdk.Android.Tests", "tests\LaunchDarkly.XamarinSdk.Android.Tests\LaunchDarkly.XamarinSdk.Android.Tests.csproj", "{2E7720E4-01A0-403B-863C-C6C596DF5926}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Android.Tests", "tests\LaunchDarkly.ClientSdk.Android.Tests\LaunchDarkly.ClientSdk.Android.Tests.csproj", "{2E7720E4-01A0-403B-863C-C6C596DF5926}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index 4e3ff093..f6e12dd7 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,68 @@ -LaunchDarkly Client-Side SDK for Xamarin -=========================== +# LaunchDarkly Client-Side SDK for .NET -[![CircleCI](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/xamarin-client-sdk/tree/master) +[![NuGet](https://img.shields.io/nuget/v/LaunchDarkly.ClientSdk.svg?style=flat-square)](https://www.nuget.org/packages/LaunchDarkly.ClientSdk/) +[![CircleCI](https://circleci.com/gh/launchdarkly/dotnet-client-sdk.svg?style=shield)](https://circleci.com/gh/launchdarkly/dotnet-client-sdk) +[![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/dotnet-client-sdk) -LaunchDarkly overview -------------------------- -[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/docs/getting-started) using LaunchDarkly today! +The LaunchDarkly Client-Side SDK for .NET is designed primarily for use by code that is deployed to an end user, such as in a desktop application or a smart device. It follows the client-side LaunchDarkly model for single-user contexts (much like our mobile or JavaScript SDKs). It is not intended for use in multi-user systems such as web servers and applications. +On supported mobile platforms (Android and iOS), the SDK uses the Xamarin framework which allows .NET code to run on those devices. For that reason, its name was previously "LaunchDarkly Xamarin SDK". However, Xamarin is not the only way to run .NET code in a client-side context (see "Supported platforms" below), so the SDK now has a more general name. + +For using LaunchDarkly in *server-side* .NET applications, refer to our [Server-Side .NET SDK](https://github.com/launchdarkly/dotnet-server-sdk). + +## LaunchDarkly overview + +[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today! + [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly) -Supported platforms -------------------- +## Supported platforms -This beta release is built for the following targets: Android 7.1, 8.0, 8.1; iOS 10; .NET Standard 2.0. It has also been tested with Android 9/API 28 and iOS 12.1. +This version of the SDK is built for the following targets: -Note that if you are targeting .NET Framework, there is a [known issue](https://stackoverflow.com/questions/46788323/installing-a-netstandard-2-0-nuget-package-into-a-vs2015-net-4-6-1-project) in Visual Studio 2015 where it does not correctly detect compatibility with .NET Standard packages. This is not a problem in later versions of Visual Studio. +* Xamarin Android 8.1, for use with Android 8.1 (Android API 27) and higher. +* Xamarin iOS 10, for use with iOS 10 and higher. +* .NET Standard 2.0, for use with any runtime platform that supports .NET Standard 2.0, or in portable .NET Standard library code. -Getting started ------------ +The .NET Standard 2.0 target does not use any Xamarin packages and has no OS-specific code. This allows the SDK to be used in a desktop .NET Framework or .NET 5.0 application, or in a Xamarin MacOS application. However, due to the lack of OS-specific integration, SDK functionality will be limited in those environments: for instance, the SDK will not be able to detect whether networking is turned on or off. -Refer to the [SDK documentation](https://docs.launchdarkly.com/docs/xamarin-sdk-reference#section-getting-started) for instructions on getting started with using the SDK. +The .NET build tools should automatically load the most appropriate build of the SDK for whatever platform your application or library is targeted to. -Learn more ------------ +## Getting started -Check out our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/docs/xamarin-sdk-reference) or our [code-generated API documentation](https://launchdarkly.github.io/xamarin-client-sdk/). +Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/client-side/dotnet) for instructions on getting started with using the SDK. -Testing -------- +## Signing -We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly. +The published version of this assembly is digitally signed with Authenticode and [strong-named](https://docs.microsoft.com/en-us/dotnet/framework/app-domains/strong-named-assemblies). Building the code locally in the default Debug configuration does not use strong-naming and does not require a key file. The public key file is in this repository at `LaunchDarkly.pk` as well as here: -Contributing ------------- +``` +Public Key: +0024000004800000940000000602000000240000525341310004000001000100f121bbf427e4d7 +edc64131a9efeefd20978dc58c285aa6f548a4282fc6d871fbebeacc13160e88566f427497b625 +56bf7ff01017b0f7c9de36869cc681b236bc0df0c85927ac8a439ecb7a6a07ae4111034e03042c +4b1569ebc6d3ed945878cca97e1592f864ba7cc81a56b8668a6d7bbe6e44c1279db088b0fdcc35 +52f746b4 -We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. +Public Key Token: f86add69004e6885 +``` -About LaunchDarkly ------------ +## Learn more + +Check out our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/sdk/client-side/dotnet). + +The authoritative description of all types, properties, and methods is in the [generated API documentation](https://launchdarkly.github.io/dotnet-client-sdk/). + +## Testing + +We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly. + +## Contributing + +We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. +## About LaunchDarkly + * LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: * Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases. * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). diff --git a/docs/project.shfbproj b/docs/project.shfbproj index ea52968c..e25de397 100644 --- a/docs/project.shfbproj +++ b/docs/project.shfbproj @@ -19,10 +19,10 @@ en-US - - - - + + + + LaunchDarkly Client-Side SDK for Xamarin $(LD_RELEASE_VERSION) 1.0.0.0 @@ -73,19 +73,19 @@ - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Common.Logging.dll + ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Common.Logging.dll - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Common.Logging.Core.dll + ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Common.Logging.Core.dll - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\LaunchDarkly.Cache.dll + ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\LaunchDarkly.Cache.dll - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\LaunchDarkly.EventSource.dll + ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\LaunchDarkly.EventSource.dll - ..\src\LaunchDarkly.XamarinSdk\bin\Debug\netstandard2.0\Newtonsoft.Json.dll + ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Newtonsoft.Json.dll \ No newline at end of file diff --git a/scripts/build-docs.ps1 b/scripts/build-docs.ps1 index a6714b7a..44b792b9 100644 --- a/scripts/build-docs.ps1 +++ b/scripts/build-docs.ps1 @@ -28,14 +28,14 @@ function ExecuteOrFail { } } -ExecuteOrFail { msbuild /restore /p:Configuration=Debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj } +ExecuteOrFail { msbuild /restore /p:Configuration=Debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj } # Building the SDK causes the assemblies for all its package dependencies to be copied into bin\Debug\netstandard2.0. # The .shfbproj is configured to expect them to be there. However, we also need the XML documentation file # for LaunchDarkly.CommonSdk, which isn't automatically copied. We can get it out of the NuGet package # cache, but first we need to determine what version of it we're using. $match = Select-String ` - -Path src\LaunchDarkly.XamarinSdk\LaunchDarkly.XamarinSdk.csproj ` + -Path src\LaunchDarkly.ClientSdk\LaunchDarkly.ClientSdk.csproj ` -Pattern "([^<]*)" if ($match.Matches.Length -ne 1) { throw "Could not find SDK version string in project file" diff --git a/scripts/build-test-package.sh b/scripts/build-test-package.sh index 73482a2b..62871341 100755 --- a/scripts/build-test-package.sh +++ b/scripts/build-test-package.sh @@ -8,7 +8,7 @@ set -e # the test-packages directory as a package source to use this package in our testing tools. TEST_VERSION="0.0.1-$(date +%Y%m%d.%H%M%S)" -PROJECT_FILE=src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +PROJECT_FILE=src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj SAVE_PROJECT_FILE="${PROJECT_FILE}.orig" TEST_PACKAGE_DIR=./test-packages @@ -22,7 +22,7 @@ trap 'mv "${SAVE_PROJECT_FILE}" "${PROJECT_FILE}"' EXIT msbuild /restore -NUPKG_FILE="src/LaunchDarkly.XamarinSdk/bin/Debug/LaunchDarkly.XamarinSdk.${TEST_VERSION}.nupkg" +NUPKG_FILE="src/LaunchDarkly.ClientSdk/bin/Debug/LaunchDarkly.ClientSdk.${TEST_VERSION}.nupkg" if [ -f "${NUPKG_FILE}" ]; then mv "${NUPKG_FILE}" "${TEST_PACKAGE_DIR}" echo; echo; echo "Success! Created test package version ${TEST_VERSION} in ${TEST_PACKAGE_DIR}" diff --git a/scripts/package.sh b/scripts/package.sh index 710d2b4b..ba822d94 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -16,15 +16,15 @@ fi # Remove any existing build products. msbuild /t:clean -rm -f ./src/LaunchDarkly.XamarinSdk/bin/Debug/*.nupkg -rm -f ./src/LaunchDarkly.XamarinSdk/bin/Release/*.nupkg +rm -f ./src/LaunchDarkly.ClientSdk/bin/Debug/*.nupkg +rm -f ./src/LaunchDarkly.ClientSdk/bin/Release/*.nupkg # Build the project for all target frameworks. This includes building the .nupkg, because of # the directive in our project file. -msbuild /restore /p:Configuration=$CONFIG src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +msbuild /restore /p:Configuration=$CONFIG src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by CI jobs in config.yml) export ASPNETCORE_SUPPRESSSTATUSMESSAGES=true # suppresses some annoying test output -dotnet test tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj -f netcoreapp2.0 +dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f netcoreapp2.0 diff --git a/scripts/publish-docs.sh b/scripts/publish-docs.sh index ebdd2fd0..56c36876 100755 --- a/scripts/publish-docs.sh +++ b/scripts/publish-docs.sh @@ -14,7 +14,7 @@ if [ ! -d ./docs/build/html ]; then fi if [ -z "${LD_RELEASE_VERSION:-}" ]; then - LD_RELEASE_VERSION=$(sed -n -e "s%.*\([^<]*\).*%\1%p" src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj) + LD_RELEASE_VERSION=$(sed -n -e "s%.*\([^<]*\).*%\1%p" src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj) if [ -z "${LD_RELEASE_VERSION}" ]; then echo "Could not find SDK version string in project file" exit 1 diff --git a/scripts/release.sh b/scripts/release.sh index a1ede956..9ef9d21e 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -17,4 +17,4 @@ fi # Since package.sh does a clean build, whichever .nupkg file now exists in the output directory # is the one we want to upload. -nuget push ./src/LaunchDarkly.XamarinSdk/bin/$CONFIG/LaunchDarkly.XamarinSdk.*.nupkg -Source https://www.nuget.org +nuget push ./src/LaunchDarkly.ClientSdk/bin/$CONFIG/LaunchDarkly.ClientSdk.*.nupkg -Source https://www.nuget.org diff --git a/scripts/update-version.sh b/scripts/update-version.sh index cbe23cdc..45418e5a 100755 --- a/scripts/update-version.sh +++ b/scripts/update-version.sh @@ -5,7 +5,7 @@ NEW_VERSION="$1" -PROJECT_FILE=./src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj +PROJECT_FILE=./src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj TEMP_FILE="${PROJECT_FILE}.tmp" sed "s#^\( *\)[^<]*#\1${NEW_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" diff --git a/src/LaunchDarkly.XamarinSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Configuration.cs rename to src/LaunchDarkly.ClientSdk/Configuration.cs diff --git a/src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/ConfigurationBuilder.cs rename to src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs diff --git a/src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs b/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/FlagChangedEvent.cs rename to src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs diff --git a/src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs b/src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/ILdClientExtensions.cs rename to src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs diff --git a/src/LaunchDarkly.XamarinSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Interfaces/ILdClient.cs rename to src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Base64.cs b/src/LaunchDarkly.ClientSdk/Internal/Base64.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Base64.cs rename to src/LaunchDarkly.ClientSdk/Internal/Base64.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Constants.cs b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Constants.cs rename to src/LaunchDarkly.ClientSdk/Internal/Constants.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/ConnectionManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultBackgroundModeManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultBackgroundModeManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultBackgroundModeManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultBackgroundModeManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultConnectivityStateManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/DefaultConnectivityStateManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/FeatureFlagRequestor.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobilePollingProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobilePollingProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobilePollingProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/MobilePollingProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobileStreamingProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobileStreamingProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataSources/MobileStreamingProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/MobileStreamingProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataStores/DefaultPersistentStorage.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataStores/DefaultPersistentStorage.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataStores/FlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataStores/FlagCacheManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagDeviceCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagDeviceCache.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagInMemoryCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DataStores/UserFlagInMemoryCache.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/DefaultDeviceInfo.cs b/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/DefaultDeviceInfo.cs rename to src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Events/DefaultEventProcessorWrapper.cs rename to src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Events/EventFactory.cs rename to src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Events/EventProcessorTypes.cs rename to src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Events/IEventProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Factory.cs rename to src/LaunchDarkly.ClientSdk/Internal/Factory.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/FeatureFlag.cs b/src/LaunchDarkly.ClientSdk/Internal/FeatureFlag.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/FeatureFlag.cs rename to src/LaunchDarkly.ClientSdk/Internal/FeatureFlag.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IBackgroundModeManager.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IBackgroundModeManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IBackgroundModeManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IBackgroundModeManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IConnectivityStateManager.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IConnectivityStateManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IConnectivityStateManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IConnectivityStateManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IDeviceInfo.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IDeviceInfo.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IFlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IFlagCacheManager.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IMobileUpdateProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IMobileUpdateProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IPersistentStorage.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IPersistentStorage.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IUserFlagCache.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/Interfaces/IUserFlagCache.cs rename to src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/JsonUtil.cs b/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/JsonUtil.cs rename to src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/LockUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/LockUtils.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/LockUtils.cs rename to src/LaunchDarkly.ClientSdk/Internal/LockUtils.cs diff --git a/src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs b/src/LaunchDarkly.ClientSdk/Internal/LogNames.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/Internal/LogNames.cs rename to src/LaunchDarkly.ClientSdk/Internal/LogNames.cs diff --git a/src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj similarity index 100% rename from src/LaunchDarkly.XamarinSdk/LaunchDarkly.XamarinSdk.csproj rename to src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj diff --git a/src/LaunchDarkly.XamarinSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/LdClient.cs rename to src/LaunchDarkly.ClientSdk/LdClient.cs diff --git a/src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs b/src/LaunchDarkly.ClientSdk/NamespaceDoc.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/NamespaceDoc.cs rename to src/LaunchDarkly.ClientSdk/NamespaceDoc.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/AsyncScheduler.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/BackgroundDetection.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/ClientIdentifier.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Connectivity.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Http.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Permissions.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Platform.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/Preferences.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.android.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.ios.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.netstandard.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformSpecific/UserMetadata.shared.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs diff --git a/src/LaunchDarkly.XamarinSdk/PlatformType.cs b/src/LaunchDarkly.ClientSdk/PlatformType.cs similarity index 100% rename from src/LaunchDarkly.XamarinSdk/PlatformType.cs rename to src/LaunchDarkly.ClientSdk/PlatformType.cs diff --git a/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2d8a378b --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.iOS.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Android.Tests")] diff --git a/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs b/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs deleted file mode 100644 index ea362dc7..00000000 --- a/src/LaunchDarkly.XamarinSdk/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.iOS.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.XamarinSdk.Android.Tests")] diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs similarity index 100% rename from tests/LaunchDarkly.XamarinSdk.Android.Tests/AndroidSpecificTests.cs rename to tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs diff --git a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj similarity index 94% rename from tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj rename to tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index cd85555e..80db37d1 100644 --- a/tests/LaunchDarkly.XamarinSdk.Android.Tests/LaunchDarkly.XamarinSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -15,7 +15,7 @@ Library Properties LaunchDarkly.XamarinSdk.Android.Tests - LaunchDarkly.XamarinSdk.Android.Tests + LaunchDarkly.ClientSdk.Android.Tests 512 True Resources\Resource.designer.cs @@ -87,7 +87,7 @@ conditional compilation, rather than by changing these includes; when we used to use individual file links, it was too easy to forget to update them. --> - + - + @@ -72,4 +69,16 @@ + + + + + + + + + + + + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index fa8c416e..d30f4ba3 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -129,7 +129,10 @@ public event EventHandler FlagChanged _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - _log = configuration.LogAdapter.Logger(LogNames.Base); + var logConfig = (configuration.LoggingConfigurationFactory ?? Components.Logging()) + .CreateLoggingConfiguration(); + var logAdapter = logConfig.LogAdapter ?? Logs.None; + _log = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); _log.Info("Starting LaunchDarkly Client {0}", Version); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs new file mode 100644 index 00000000..7d6e220f --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs @@ -0,0 +1,122 @@ +using System; +using LaunchDarkly.Logging; + +using AndroidLog = Android.Util.Log; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Logging + { + internal static ILogAdapter PlatformDefaultAdapter => + AndroidLogAdapter.Instance; + + // Implementation of the LaunchDarkly.Logging API for sending output to Android's standard + // logging framework. Notes: + // 1. This sets the log tag to be the same as the LaunchDarkly logger name (see LogNames and + // LoggingConfigurationBuilder.BaseLoggerName). + // 2. The underlying Android API is a little different: Log has methods called "d", "i", "w", + // and "e" rather than Debug, Info, Warn, and Error, and they always take a message string + // rather than a format string plus variables. Xamarin.Android adds some decoration of its + // own so that we can use .NET-style format strings. + private sealed class AndroidLogAdapter : ILogAdapter + { + internal static readonly AndroidLogAdapter Instance = + new AndroidLogAdapter(); + + public IChannel NewChannel(string name) => new ChannelImpl(name); + + private sealed class ChannelImpl : IChannel + { + private readonly string _name; + + internal ChannelImpl(string name) + { + _name = name; + } + + // As defined in IChannel, IsEnabled really means "is it *potentially* + // enabled" - it's a shortcut to make it easier to skip computing any + // debug-level output if we know for sure that debug is disabled. But + // we don't have a way to find that out here. + public bool IsEnabled(LogLevel level) => true; + + public void Log(LogLevel level, object message) + { + var s = message.ToString(); + switch (level) + { + case LogLevel.Debug: + AndroidLog.Debug(_name, s); + break; + case LogLevel.Info: + AndroidLog.Info(_name, s); + break; + case LogLevel.Warn: + AndroidLog.Warn(_name, s); + break; + case LogLevel.Error: + AndroidLog.Error(_name, s); + break; + } + } + + public void Log(LogLevel level, string format, object param) + { + switch (level) + { + case LogLevel.Debug: + AndroidLog.Debug(_name, format, param); + break; + case LogLevel.Info: + AndroidLog.Info(_name, format, param); + break; + case LogLevel.Warn: + AndroidLog.Warn(_name, format, param); + break; + case LogLevel.Error: + AndroidLog.Error(_name, format, param); + break; + } + } + + public void Log(LogLevel level, string format, object param1, object param2) + { + switch (level) + { + case LogLevel.Debug: + AndroidLog.Debug(_name, format, param1, param2); + break; + case LogLevel.Info: + AndroidLog.Info(_name, format, param1, param2); + break; + case LogLevel.Warn: + AndroidLog.Warn(_name, format, param1, param2); + break; + case LogLevel.Error: + AndroidLog.Error(_name, format, param1, param2); + break; + } + } + + public void Log(LogLevel level, string format, params object[] allParams) + { + switch (level) + { + case LogLevel.Debug: + AndroidLog.Debug(_name, format, allParams); + break; + case LogLevel.Info: + AndroidLog.Info(_name, format, allParams); + break; + case LogLevel.Warn: + AndroidLog.Warn(_name, format, allParams); + break; + case LogLevel.Error: + AndroidLog.Error(_name, format, allParams); + break; + } + } + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs new file mode 100644 index 00000000..01cfc5fa --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs @@ -0,0 +1,84 @@ +using System; +using CoreFoundation; +using LaunchDarkly.Logging; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Logging + { + internal static ILogAdapter PlatformDefaultAdapter => + IOsLogAdapter.Instance; + + // Implementation of the LaunchDarkly.Logging API for sending output to iOS's standard + // logging framework, OSLog. + // + // OSLog uses logger names slightly differently: it has two name-like properties, "subsystem" + // and "category", with "category" being the more specific one. We're handling this by + // splitting up our logger name as described in the docs for LoggingConfigurationBuilder. + private sealed class IOsLogAdapter : ILogAdapter + { + internal static readonly IOsLogAdapter Instance = + new IOsLogAdapter(); + + public IChannel NewChannel(string name) => new ChannelImpl(name); + + private sealed class ChannelImpl : IChannel + { + private readonly OSLog _log; + + internal ChannelImpl(string name) + { + string subsystem, category; + int pos = name.IndexOf('.'); + if (pos > 0) + { + subsystem = name.Substring(0, pos); + category = name.Substring(pos + 1); + } else + { + subsystem = name; + category = ""; + } + _log = new OSLog(subsystem, category); + } + + // As defined in IChannel, IsEnabled really means "is it *potentially* + // enabled" - it's a shortcut to make it easier to skip computing any + // debug-level output if we know for sure that debug is disabled. But + // we don't have a way to find that out here. + public bool IsEnabled(LogLevel level) => true; + + private void LogString(LogLevel level, string s) + { + switch (level) + { + case LogLevel.Debug: + _log.Log(OSLogLevel.Debug, s); + break; + case LogLevel.Info: + _log.Log(OSLogLevel.Info, s); + break; + case LogLevel.Warn: + _log.Log(OSLogLevel.Default, s); + break; + case LogLevel.Error: + _log.Log(OSLogLevel.Error, s); + break; + } + } + + public void Log(LogLevel level, object message) => + LogString(level, message.ToString()); + + public void Log(LogLevel level, string format, object param) => + LogString(level, string.Format(format, param)); + + public void Log(LogLevel level, string format, object param1, object param2) => + LogString(level, string.Format(format, param1, param2)); + + public void Log(LogLevel level, string format, params object[] allParams) => + LogString(level, string.Format(format, allParams)); + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.netstandard.cs new file mode 100644 index 00000000..39fb27ae --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.netstandard.cs @@ -0,0 +1,9 @@ +using LaunchDarkly.Logging; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Logging + { + internal static ILogAdapter PlatformDefaultAdapter => Logs.ToConsole; + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.shared.cs new file mode 100644 index 00000000..6d08a3a5 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.shared.cs @@ -0,0 +1,9 @@ +using LaunchDarkly.Logging; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Logging + { + public static ILogAdapter DefaultAdapter => PlatformDefaultAdapter; + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/LoggingConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/LoggingConfigurationBuilderTest.cs new file mode 100644 index 00000000..9f92a9a1 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/LoggingConfigurationBuilderTest.cs @@ -0,0 +1,74 @@ +using LaunchDarkly.Logging; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class LoggingConfigurationBuilderTest + { + [Fact] + public void HasNonNullDefaultLogAdapter() + { + var logConfig = Components.Logging().CreateLoggingConfiguration(); + Assert.NotNull(logConfig.LogAdapter); + } + + [Fact] + public void CanSpecifyAdapter() + { + var adapter = Logs.ToMultiple(Logs.None); + + var logConfig1 = Components.Logging() + .Adapter(adapter) + .CreateLoggingConfiguration(); + Assert.Same(adapter, logConfig1.LogAdapter); + + var logConfig2 = Components.Logging(adapter) + .CreateLoggingConfiguration(); + Assert.Same(adapter, logConfig2.LogAdapter); + } + + [Fact] + public void CanSpecifyBaseLoggerName() + { + var logConfig1 = Components.Logging().CreateLoggingConfiguration(); + Assert.Null(logConfig1.BaseLoggerName); + + var logConfig2 = Components.Logging().BaseLoggerName("xyz").CreateLoggingConfiguration(); + Assert.Equal("xyz", logConfig2.BaseLoggerName); + } + + [Fact] + public void DoesNotSetDefaultLevelForCustomAdapter() + { + var logCapture = Logs.Capture(); + var logConfig = Components.Logging(logCapture) + .CreateLoggingConfiguration(); + var logger = logConfig.LogAdapter.Logger(""); + logger.Debug("hi"); + Assert.True(logCapture.HasMessageWithText(LogLevel.Debug, "hi")); + } + + [Fact] + public void CanOverrideLevel() + { + var logCapture = Logs.Capture(); + var logConfig = Components.Logging(logCapture) + .Level(LogLevel.Warn) + .CreateLoggingConfiguration(); + var logger = logConfig.LogAdapter.Logger(""); + logger.Debug("d"); + logger.Info("i"); + logger.Warn("w"); + Assert.False(logCapture.HasMessageWithText(LogLevel.Debug, "d")); + Assert.False(logCapture.HasMessageWithText(LogLevel.Info, "i")); + Assert.True(logCapture.HasMessageWithText(LogLevel.Warn, "w")); + } + + [Fact] + public void NoLoggingIsShortcutForLogsNone() + { + var logConfig = Components.NoLogging.CreateLoggingConfiguration(); + Assert.Same(Logs.None, logConfig.LogAdapter); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index abbceb59..2a2ffcc4 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -20,12 +20,4 @@ - - - - - - - - diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 10f59cf8..38f24e62 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -188,8 +188,8 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) - .EventProcessor(eventProcessor); - config.EventProcessor(eventProcessor); + .EventProcessor(eventProcessor) + .Logging(testLogging); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { @@ -296,8 +296,8 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) - .EventProcessor(eventProcessor); - config.EventProcessor(eventProcessor); + .EventProcessor(eventProcessor) + .Logging(testLogging); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { From 037a76455b8d1f42428240bde5355be34ebe9ede Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Aug 2021 16:19:43 -0700 Subject: [PATCH 348/499] implement Alias method and auto-aliasing --- src/LaunchDarkly.ClientSdk/Configuration.cs | 15 ++++ .../ConfigurationBuilder.cs | 24 ++++++ .../Interfaces/ILdClient.cs | 13 +++ .../Events/DefaultEventProcessorWrapper.cs | 14 ++++ .../Internal/Events/EventProcessorTypes.cs | 22 +++++ .../Internal/Events/IEventProcessor.cs | 2 + src/LaunchDarkly.ClientSdk/LdClient.cs | 32 ++++++++ .../ConfigurationTest.cs | 3 + .../ILdClientExtensionsTest.cs | 3 + .../LdClientEventTests.cs | 80 ++++++++++++++++++- .../MockComponents.cs | 3 + 11 files changed, 210 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 6f67d05a..97f9f352 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -65,6 +65,20 @@ public sealed class Configuration /// public bool AllAttributesPrivate => _allAttributesPrivate; + /// + /// Whether to disable the automatic sending of an alias event when the current user is changed + /// to a non-anonymous user andthe previous user was anonymous. + /// + /// + /// By default, if you call or + /// with a non-anonymous user, and the current user + /// (previously specified either with one of those methods or when creating the ) + /// was anonymous, the SDK assumes the two users should be correlated and sends an analytics + /// event equivalent to calling . Setting + /// AutoAliasingOptOut to disables this behavior. + /// + public bool AutoAliasingOptOut { get; } + /// /// The interval between feature flag updates when the application is running in the background. /// @@ -316,6 +330,7 @@ public static IConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { _allAttributesPrivate = builder._allAttributesPrivate; + AutoAliasingOptOut = builder._autoAliasingOptOut; _backgroundPollingInterval = builder._backgroundPollingInterval; _baseUri = builder._baseUri; _connectionTimeout = builder._connectionTimeout; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 176d05f8..b2644f13 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -47,6 +47,22 @@ public interface IConfigurationBuilder /// the same builder IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); + /// + /// Whether to disable the automatic sending of an alias event when the current user is changed + /// to a non-anonymous user andthe previous user was anonymous. + /// + /// + /// By default, if you call or + /// with a non-anonymous user, and the current user + /// (previously specified either with one of those methods or when creating the ) + /// was anonymous, the SDK assumes the two users should be correlated and sends an analytics + /// event equivalent to calling . Setting + /// AutoAliasingOptOut to disables this behavior. + /// + /// true to disable automatic user aliasing + /// the same builder + IConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut); + /// /// Sets the interval between feature flag updates when the application is running in the background. /// @@ -334,6 +350,7 @@ internal sealed class ConfigurationBuilder : IConfigurationBuilder // will replace it with a platform-specific implementation. internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); + internal bool _autoAliasingOptOut = false; internal bool _allAttributesPrivate = false; internal TimeSpan _backgroundPollingInterval = Configuration.DefaultBackgroundPollingInterval; internal Uri _baseUri = Configuration.DefaultUri; @@ -377,6 +394,7 @@ internal ConfigurationBuilder(string mobileKey) internal ConfigurationBuilder(Configuration copyFrom) { _allAttributesPrivate = copyFrom.AllAttributesPrivate; + _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; _baseUri = copyFrom.BaseUri; _connectionTimeout = copyFrom.ConnectionTimeout; @@ -414,6 +432,12 @@ public IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) return this; } + public IConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) + { + _autoAliasingOptOut = autoAliasingOptOut; + return this; + } + public IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval) { if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index d50aea87..fec6daee 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -340,6 +340,19 @@ public interface ILdClient : IDisposable /// a task that yields true if new flag values were obtained Task IdentifyAsync(User user); + /// + /// Associates two users for analytics purposes. + /// + /// + /// This can be helpful in the situation where a person is represented by multiple + /// LaunchDarkly users. This may happen, for example, when a person initially logs into + /// an application-- the person might be represented by an anonymous user prior to logging + /// in and a different user after logging in, as denoted by a different user key. + /// + /// the newly identified user + /// the previously identified user + void Alias(User user, User previousUser); + /// /// Tells the client that all pending analytics events should be delivered as soon as possible. /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs index fcb7d1dc..0eb51cf7 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -49,6 +49,20 @@ public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) }); } + public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) + { + _eventProcessor.RecordAliasEvent(new EventTypes.AliasEvent + { + Timestamp = e.Timestamp, + Key = e.User.Key, + ContextKind = e.User.Anonymous ? EventTypes.ContextKind.AnonymousUser : + EventTypes.ContextKind.User, + PreviousKey = e.PreviousUser.Key, + PreviousContextKind = e.PreviousUser.Anonymous ? EventTypes.ContextKind.AnonymousUser : + EventTypes.ContextKind.User + }); + } + public void SetOffline(bool offline) => _eventProcessor.SetOffline(offline); diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs index ef535dcc..a5d7459b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs @@ -121,5 +121,27 @@ public struct CustomEvent /// public double? MetricValue { get; set; } } + + /// + /// Parameters for . + /// + public struct AliasEvent + { + /// + /// Date/timestamp of the event. + /// + public UnixMillisecondTime Timestamp { get; set; } + + /// + /// Attributes of the user who generated the event. Some attributes may not be sent + /// to LaunchDarkly if they are private. + /// + public User User { get; set; } + + /// + /// Attributes of the previous user that should be considered equivalent to this user. + /// + public User PreviousUser { get; set; } + } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs index fc503edd..bc25f7b4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs @@ -10,6 +10,8 @@ internal interface IEventProcessor : IDisposable void RecordCustomEvent(EventProcessorTypes.CustomEvent e); + void RecordAliasEvent(EventProcessorTypes.AliasEvent e); + void SetOffline(bool offline); void Flush(); diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index d30f4ba3..82597c4a 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -541,9 +541,11 @@ public async Task IdentifyAsync(User user) } User newUser = DecorateUser(user); + User oldUser = newUser; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => { + oldUser = _user; _user = newUser; }); @@ -554,6 +556,15 @@ public async Task IdentifyAsync(User user) Timestamp = UnixMillisecondTime.Now, User = user }); + if (oldUser.Anonymous && !newUser.Anonymous && !_config.AutoAliasingOptOut) + { + eventProcessor.RecordAliasEvent(new EventProcessorTypes.AliasEvent + { + Timestamp = UnixMillisecondTime.Now, + User = user, + PreviousUser = oldUser + }); + } } return await _connectionManager.SetUpdateProcessorFactory( @@ -562,6 +573,27 @@ public async Task IdentifyAsync(User user) ); } + /// + public void Alias(User user, User previousUser) + { + if (user is null) + { + throw new ArgumentNullException(nameof(user)); + } + if (previousUser is null) + { + throw new ArgumentNullException(nameof(previousUser)); + } + if (!_connectionManager.ForceOffline) + { + eventProcessor.RecordAliasEvent(new EventProcessorTypes.AliasEvent + { + User = user, + PreviousUser = previousUser + }); + } + } + User DecorateUser(User user) { IUserBuilder buildUser = null; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index b3804200..bfa4d9b9 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -24,6 +24,7 @@ public void TestDefaultsFromBuilder() private void VerifyDefaults(Configuration c) { Assert.False(c.AllAttributesPrivate); + Assert.False(c.AutoAliasingOptOut); Assert.Equal(Configuration.DefaultBackgroundPollingInterval, c.BackgroundPollingInterval); Assert.Equal(Configuration.DefaultUri, c.BaseUri); Assert.Equal(Configuration.DefaultConnectionTimeout, c.ConnectionTimeout); @@ -50,11 +51,13 @@ private void VerifyDefaults(Configuration c) public void CanOverrideConfiguration() { var config = Configuration.Builder("AnyOtherSdkKey") + .AutoAliasingOptOut(true) .BaseUri(new Uri("https://app.AnyOtherEndpoint.com")) .EventCapacity(99) .PollingInterval(TimeSpan.FromMinutes(45)) .Build(); + Assert.True(config.AutoAliasingOptOut); Assert.Equal(new Uri("https://app.AnyOtherEndpoint.com"), config.BaseUri); Assert.Equal("AnyOtherSdkKey", config.MobileKey); Assert.Equal(99, config.EventCapacity); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index 4d0ccfcf..b98c1f8d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -171,6 +171,9 @@ public void Track(string eventName, LdValue data) => public void Track(string eventName, LdValue data, double metricValue) => throw new System.NotImplementedException(); + + public void Alias(User user, User previousUser) => + throw new System.NotImplementedException(); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 38f24e62..73b68d60 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -19,7 +19,7 @@ public LdClient MakeClient(User user, string flagsJson) config.EventProcessor(eventProcessor).Logging(testLogging); return TestUtil.CreateClient(config.Build(), user); } - + [Fact] public void IdentifySendsIdentifyEvent() { @@ -90,6 +90,84 @@ public void TrackWithMetricValueSendsCustomEvent() } } + [Fact] + public void AliasSendsAliasEvent() + { + User oldUser = User.Builder("xxx").Anonymous(true).Build(); + using (LdClient client = MakeClient(user, "{}")) + { + client.Alias(user, oldUser); + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, user), + e => { + AliasEvent ae = Assert.IsType(e); + Assert.Equal(user, ae.User); + Assert.Equal(oldUser, ae.PreviousUser); + }); + } + } + + [Fact] + public void IdentifySendsAliasEventFromAnonUserToNonAnonUserIfNotOptedOut() + { + User oldUser = User.Builder("anon-key").Anonymous(true).Build(); + User newUser = User.WithKey("real-key"); + + using (LdClient client = MakeClient(oldUser, "{}")) + { + client.Identify(newUser, TimeSpan.FromSeconds(1)); + + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, oldUser), + e => CheckIdentifyEvent(e, newUser), + e => { + AliasEvent ae = Assert.IsType(e); + Assert.Equal(newUser, ae.User); + Assert.Equal(oldUser, ae.PreviousUser); + }); + } + } + + [Fact] + public void IdentifyDoesNotSendAliasEventIfOptedOUt() + { + User oldUser = User.Builder("anon-key").Anonymous(true).Build(); + User newUser = User.WithKey("real-key"); + + var config = TestUtil.ConfigWithFlagsJson(oldUser, "appkey", "{}"); + config.EventProcessor(eventProcessor).Logging(testLogging); + config.AutoAliasingOptOut(true); + + using (LdClient client = TestUtil.CreateClient(config.Build(), oldUser)) + { + client.Identify(newUser, TimeSpan.FromSeconds(1)); + + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, oldUser), + e => CheckIdentifyEvent(e, newUser)); + } + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public void IdentifyDoesNotSendAliasEventIfNewUserIsAnonymousOrOldUserIsNot( + bool oldAnon, bool newAnon) + { + User oldUser = User.Builder("old-key").Anonymous(oldAnon).Build(); + User newUser = User.Builder("new-key").Anonymous(newAnon).Build(); + + using (LdClient client = MakeClient(oldUser, "{}")) + { + client.Identify(newUser, TimeSpan.FromSeconds(1)); + + Assert.Collection(eventProcessor.Events, + e => CheckIdentifyEvent(e, oldUser), + e => CheckIdentifyEvent(e, newUser)); + } + } + [Fact] public void VariationSendsFeatureEventForValidFlag() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 4cbf81ee..eea844d6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -91,6 +91,9 @@ public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) => public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) => Events.Add(e); + + public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) => + Events.Add(e); } internal class MockFeatureFlagRequestor : IFeatureFlagRequestor From edf6f5cd7a2a917cf41731a9f718147147cb90f7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Aug 2021 16:29:17 -0700 Subject: [PATCH 349/499] misc test fixes --- .../LdClientEventTests.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 73b68d60..2153ff6a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -93,7 +93,9 @@ public void TrackWithMetricValueSendsCustomEvent() [Fact] public void AliasSendsAliasEvent() { - User oldUser = User.Builder("xxx").Anonymous(true).Build(); + User oldUser = User.Builder("anon-key").Anonymous(true).Build(); + User newUser = User.WithKey("real-key"); + using (LdClient client = MakeClient(user, "{}")) { client.Alias(user, oldUser); @@ -115,15 +117,16 @@ public void IdentifySendsAliasEventFromAnonUserToNonAnonUserIfNotOptedOut() using (LdClient client = MakeClient(oldUser, "{}")) { + User actualOldUser = client.User; // so we can get any automatic properties that the client added client.Identify(newUser, TimeSpan.FromSeconds(1)); Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, oldUser), + e => CheckIdentifyEvent(e, actualOldUser), e => CheckIdentifyEvent(e, newUser), e => { AliasEvent ae = Assert.IsType(e); Assert.Equal(newUser, ae.User); - Assert.Equal(oldUser, ae.PreviousUser); + Assert.Equal(actualOldUser, ae.PreviousUser); }); } } @@ -140,10 +143,11 @@ public void IdentifyDoesNotSendAliasEventIfOptedOUt() using (LdClient client = TestUtil.CreateClient(config.Build(), oldUser)) { + User actualOldUser = client.User; // so we can get any automatic properties that the client added client.Identify(newUser, TimeSpan.FromSeconds(1)); Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, oldUser), + e => CheckIdentifyEvent(e, actualOldUser), e => CheckIdentifyEvent(e, newUser)); } } From a6ed9712df0bf67f4d2a7e2e15e7403751a15361 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Aug 2021 17:55:28 -0700 Subject: [PATCH 350/499] remove IConfigurationBuilder interface --- src/LaunchDarkly.ClientSdk/Configuration.cs | 177 +++--- .../ConfigurationBuilder.cs | 503 ++++++++---------- .../Interfaces/ILdClient.cs | 4 +- .../Internal/Factory.cs | 20 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 2 +- .../MobileStreamingProcessorTests.cs | 4 +- .../LDClientEndToEndTests.cs | 8 +- .../LdClientTests.cs | 2 +- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 2 +- 9 files changed, 314 insertions(+), 408 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 97f9f352..c44ca8ab 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -20,39 +20,15 @@ namespace LaunchDarkly.Sdk.Client /// public sealed class Configuration { - private readonly bool _allAttributesPrivate; - private readonly TimeSpan _backgroundPollingInterval; - private readonly Uri _baseUri; - private readonly TimeSpan _connectionTimeout; - private readonly bool _enableBackgroundUpdating; - private readonly bool _evaluationReasons; - private readonly TimeSpan _eventFlushInterval; - private readonly int _eventCapacity; - private readonly Uri _eventsUri; - private readonly HttpMessageHandler _httpMessageHandler; - private readonly bool _inlineUsersInEvents; - private readonly bool _isStreamingEnabled; - private readonly string _mobileKey; - private readonly bool _offline; - private readonly bool _persistFlagValues; - private readonly TimeSpan _pollingInterval; - private readonly ImmutableHashSet _privateAttributeNames; - private readonly TimeSpan _readTimeout; - private readonly TimeSpan _reconnectTime; - private readonly Uri _streamUri; - private readonly bool _useReport; - private readonly int _userKeysCapacity; - private readonly TimeSpan _userKeysFlushInterval; - // Settable only for testing - internal readonly IBackgroundModeManager _backgroundModeManager; - internal readonly IConnectivityStateManager _connectivityStateManager; - internal readonly IDeviceInfo _deviceInfo; - internal readonly IEventProcessor _eventProcessor; - internal readonly IFlagCacheManager _flagCacheManager; - internal readonly IFlagChangedEventManager _flagChangedEventManager; - internal readonly IPersistentStorage _persistentStorage; - internal readonly Func _updateProcessorFactory; + internal IBackgroundModeManager BackgroundModeManager { get; } + internal IConnectivityStateManager ConnectivityStateManager { get; } + internal IDeviceInfo DeviceInfo { get; } + internal IEventProcessor EventProcessor { get; } + internal IFlagCacheManager FlagCacheManager { get; } + internal IFlagChangedEventManager FlagChangedEventManager { get; } + internal IPersistentStorage PersistentStorage { get; } + internal Func UpdateProcessorFactory { get; } /// /// Whether or not user attributes (other than the key) should be private (not sent to @@ -60,10 +36,10 @@ public sealed class Configuration /// /// /// By default, this is . If , all of the user attributes - /// will be private, not just the attributes specified with + /// will be private, not just the attributes specified with /// or with the method. /// - public bool AllAttributesPrivate => _allAttributesPrivate; + public bool AllAttributesPrivate { get; } /// /// Whether to disable the automatic sending of an alias event when the current user is changed @@ -85,17 +61,17 @@ public sealed class Configuration /// /// This is only relevant on mobile platforms. /// - public TimeSpan BackgroundPollingInterval => _backgroundPollingInterval; + public TimeSpan BackgroundPollingInterval { get; } /// /// The base URI of the LaunchDarkly server. /// - public Uri BaseUri => _baseUri; + public Uri BaseUri { get; } /// /// The connection timeout to the LaunchDarkly server. /// - public TimeSpan ConnectionTimeout => _connectionTimeout; + public TimeSpan ConnectionTimeout { get; } /// /// Whether to enable feature flag updates when the application is running in the background. @@ -103,7 +79,7 @@ public sealed class Configuration /// /// This is only relevant on mobile platforms. /// - public bool EnableBackgroundUpdating => _enableBackgroundUpdating; + public bool EnableBackgroundUpdating { get; } /// /// True if LaunchDarkly should provide additional information about how flag values were @@ -115,7 +91,7 @@ public sealed class Configuration /// increases the size of network requests, such information is not sent unless you set this option /// to . /// - public bool EvaluationReasons => _evaluationReasons; + public bool EvaluationReasons { get; } /// /// The capacity of the event buffer. @@ -125,7 +101,7 @@ public sealed class Configuration /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events /// are less likely to be discarded, at the cost of consuming more memory. /// - public int EventCapacity => _eventCapacity; + public int EventCapacity { get; } /// /// The time between flushes of the event buffer. @@ -133,17 +109,17 @@ public sealed class Configuration /// /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. /// - public TimeSpan EventFlushInterval => _eventFlushInterval; + public TimeSpan EventFlushInterval { get; } /// /// The base URL of the LaunchDarkly analytics event server. /// - public Uri EventsUri => _eventsUri; - + public Uri EventsUri { get; } + /// /// The object to be used for sending HTTP requests, if a specific implementation is desired. /// - public HttpMessageHandler HttpMessageHandler => _httpMessageHandler; + public HttpMessageHandler HttpMessageHandler { get; } /// /// Sets whether to include full user details in every analytics event. @@ -152,7 +128,7 @@ public sealed class Configuration /// The default is : events will only include the user key, except for one /// "index" event that provides the full details for the user. /// - public bool InlineUsersInEvents => _inlineUsersInEvents; + public bool InlineUsersInEvents { get; } /// /// Whether or not the streaming API should be used to receive flag updates. @@ -160,7 +136,7 @@ public sealed class Configuration /// /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. /// - public bool IsStreamingEnabled => _isStreamingEnabled; + public bool IsStreamingEnabled { get; } internal ILoggingConfigurationFactory LoggingConfigurationFactory { get; } @@ -170,12 +146,12 @@ public sealed class Configuration /// /// This should be the "mobile key" field for the environment on your LaunchDarkly dashboard. /// - public string MobileKey => _mobileKey; + public string MobileKey { get; } /// /// Whether or not this client is offline. If , no calls to LaunchDarkly will be made. /// - public bool Offline => _offline; + public bool Offline { get; } /// /// Whether the SDK should save flag values for each user in persistent storage, so they will be @@ -184,12 +160,12 @@ public sealed class Configuration /// /// The default is . /// - public bool PersistFlagValues => _persistFlagValues; + public bool PersistFlagValues { get; } /// /// The polling interval (when streaming is disabled). /// - public TimeSpan PollingInterval => _pollingInterval; + public TimeSpan PollingInterval { get; } /// /// Attribute names that have been marked as private for all users. @@ -199,12 +175,12 @@ public sealed class Configuration /// removed, even if you did not use the /// method when building the user. /// - public IImmutableSet PrivateAttributeNames => _privateAttributeNames; + public IImmutableSet PrivateAttributeNames { get; } /// /// The timeout when reading data from the streaming connection. /// - public TimeSpan ReadTimeout => _readTimeout; + public TimeSpan ReadTimeout { get; } /// /// The reconnect base time for the streaming connection. @@ -213,12 +189,12 @@ public sealed class Configuration /// The streaming connection uses an exponential backoff algorithm (with jitter) for reconnects, but /// will start the backoff with a value near the value specified here. The default value is 1 second. /// - public TimeSpan ReconnectTime => _reconnectTime; + public TimeSpan ReconnectTime { get; } /// /// The base URL of the LaunchDarkly streaming server. /// - public Uri StreamUri => _streamUri; + public Uri StreamUri { get; } /// /// Whether to use the HTTP REPORT method for feature flag requests. @@ -228,7 +204,7 @@ public sealed class Configuration /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. /// However, some network gateways do not support REPORT. /// - internal bool UseReport => _useReport; + internal bool UseReport { get; } // UseReport is currently disabled due to Android HTTP issues (ch47341), but it's still implemented internally /// @@ -238,12 +214,12 @@ public sealed class Configuration /// The event processor keeps track of recently seen user keys so that duplicate user details will not /// be sent in analytics events. /// - public int UserKeysCapacity => _userKeysCapacity; + public int UserKeysCapacity { get; } /// /// The interval at which the event processor will reset its set of known user keys. /// - public TimeSpan UserKeysFlushInterval => _userKeysFlushInterval; + public TimeSpan UserKeysFlushInterval { get; } /// /// Default value for . @@ -279,12 +255,12 @@ public static Configuration Default(string mobileKey) } /// - /// Creates an for constructing a configuration object using a fluent syntax. + /// Creates a for constructing a configuration object using a fluent syntax. /// /// /// This is the only method for building a if you are setting properties - /// besides the MobileKey. The has methods for setting any number of - /// properties, after which you call to get the resulting + /// besides the MobileKey. The has methods for setting any number of + /// properties, after which you call to get the resulting /// Configuration instance. /// /// @@ -297,7 +273,7 @@ public static Configuration Default(string mobileKey) /// /// the mobile SDK key for your LaunchDarkly environment /// a builder object - public static IConfigurationBuilder Builder(string mobileKey) + public static ConfigurationBuilder Builder(string mobileKey) { if (String.IsNullOrEmpty(mobileKey)) { @@ -307,65 +283,54 @@ public static IConfigurationBuilder Builder(string mobileKey) } /// - /// Exposed for test code that needs to access the internal methods of that - /// are not in . - /// - /// the mobile SDK key - /// a builder object - internal static ConfigurationBuilder BuilderInternal(string mobileKey) - { - return new ConfigurationBuilder(mobileKey); - } - - /// - /// Creates an starting with the properties of an existing . + /// Creates a starting with the properties of an existing . /// /// the configuration to copy /// a builder object - public static IConfigurationBuilder Builder(Configuration fromConfiguration) + public static ConfigurationBuilder Builder(Configuration fromConfiguration) { return new ConfigurationBuilder(fromConfiguration); } internal Configuration(ConfigurationBuilder builder) { - _allAttributesPrivate = builder._allAttributesPrivate; + AllAttributesPrivate = builder._allAttributesPrivate; AutoAliasingOptOut = builder._autoAliasingOptOut; - _backgroundPollingInterval = builder._backgroundPollingInterval; - _baseUri = builder._baseUri; - _connectionTimeout = builder._connectionTimeout; - _enableBackgroundUpdating = builder._enableBackgroundUpdating; - _evaluationReasons = builder._evaluationReasons; - _eventFlushInterval = builder._eventFlushInterval; - _eventCapacity = builder._eventCapacity; - _eventsUri = builder._eventsUri; - _httpMessageHandler = object.ReferenceEquals(builder._httpMessageHandler, ConfigurationBuilder.DefaultHttpMessageHandlerInstance) ? + BackgroundPollingInterval = builder._backgroundPollingInterval; + BaseUri = builder._baseUri; + ConnectionTimeout = builder._connectionTimeout; + EnableBackgroundUpdating = builder._enableBackgroundUpdating; + EvaluationReasons = builder._evaluationReasons; + EventFlushInterval = builder._eventFlushInterval; + EventCapacity = builder._eventCapacity; + EventsUri = builder._eventsUri; + HttpMessageHandler = object.ReferenceEquals(builder._httpMessageHandler, ConfigurationBuilder.DefaultHttpMessageHandlerInstance) ? PlatformSpecific.Http.CreateHttpMessageHandler(builder._connectionTimeout, builder._readTimeout) : builder._httpMessageHandler; - _inlineUsersInEvents = builder._inlineUsersInEvents; - _isStreamingEnabled = builder._isStreamingEnabled; + InlineUsersInEvents = builder._inlineUsersInEvents; + IsStreamingEnabled = builder._isStreamingEnabled; LoggingConfigurationFactory = builder._loggingConfigurationFactory; - _mobileKey = builder._mobileKey; - _offline = builder._offline; - _persistFlagValues = builder._persistFlagValues; - _pollingInterval = builder._pollingInterval; - _privateAttributeNames = builder._privateAttributeNames is null ? null : + MobileKey = builder._mobileKey; + Offline = builder._offline; + PersistFlagValues = builder._persistFlagValues; + PollingInterval = builder._pollingInterval; + PrivateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); - _readTimeout = builder._readTimeout; - _reconnectTime = builder._reconnectTime; - _streamUri = builder._streamUri; - _useReport = builder._useReport; - _userKeysCapacity = builder._userKeysCapacity; - _userKeysFlushInterval = builder._userKeysFlushInterval; - - _backgroundModeManager = builder._backgroundModeManager; - _connectivityStateManager = builder._connectivityStateManager; - _deviceInfo = builder._deviceInfo; - _eventProcessor = builder._eventProcessor; - _flagCacheManager = builder._flagCacheManager; - _flagChangedEventManager = builder._flagChangedEventManager; - _persistentStorage = builder._persistentStorage; - _updateProcessorFactory = builder._updateProcessorFactory; + ReadTimeout = builder._readTimeout; + ReconnectTime = builder._reconnectTime; + StreamUri = builder._streamUri; + UseReport = builder._useReport; + UserKeysCapacity = builder._userKeysCapacity; + UserKeysFlushInterval = builder._userKeysFlushInterval; + + BackgroundModeManager = builder._backgroundModeManager; + ConnectivityStateManager = builder._connectivityStateManager; + DeviceInfo = builder._deviceInfo; + EventProcessor = builder._eventProcessor; + FlagCacheManager = builder._flagCacheManager; + FlagChangedEventManager = builder._flagChangedEventManager; + PersistentStorage = builder._persistentStorage; + UpdateProcessorFactory = builder._updateProcessorFactory; } internal HttpProperties HttpProperties => HttpProperties.Default diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index b2644f13..96f41899 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -25,14 +25,93 @@ namespace LaunchDarkly.Sdk.Client /// var config = Configuration.Builder("my-mobile-key").AllAttributesPrivate(true).EventCapacity(1000).Build(); /// /// - public interface IConfigurationBuilder + public sealed class ConfigurationBuilder { + // This exists so that we can distinguish between leaving the HttpMessageHandler property unchanged + // and explicitly setting it to null. If the property value is the exact same instance as this, we + // will replace it with a platform-specific implementation. + internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); + + internal bool _autoAliasingOptOut = false; + internal bool _allAttributesPrivate = false; + internal TimeSpan _backgroundPollingInterval = Configuration.DefaultBackgroundPollingInterval; + internal Uri _baseUri = Configuration.DefaultUri; + internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; + internal bool _enableBackgroundUpdating = true; + internal bool _evaluationReasons = false; + internal int _eventCapacity = Configuration.DefaultEventCapacity; + internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; + internal Uri _eventsUri = Configuration.DefaultEventsUri; + internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; + internal bool _inlineUsersInEvents = false; + internal bool _isStreamingEnabled = true; + internal ILoggingConfigurationFactory _loggingConfigurationFactory = null; + internal string _mobileKey; + internal bool _offline = false; + internal bool _persistFlagValues = true; + internal TimeSpan _pollingInterval = Configuration.DefaultPollingInterval; + internal HashSet _privateAttributeNames = null; + internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; + internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; + internal Uri _streamUri = Configuration.DefaultStreamUri; + internal bool _useReport = false; + internal int _userKeysCapacity = Configuration.DefaultUserKeysCapacity; + internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; + + // Internal properties only settable for testing + internal IBackgroundModeManager _backgroundModeManager; + internal IConnectivityStateManager _connectivityStateManager; + internal IDeviceInfo _deviceInfo; + internal IEventProcessor _eventProcessor; + internal IFlagCacheManager _flagCacheManager; + internal IFlagChangedEventManager _flagChangedEventManager; + internal IPersistentStorage _persistentStorage; + internal Func _updateProcessorFactory; + + internal ConfigurationBuilder(string mobileKey) + { + _mobileKey = mobileKey; + } + + internal ConfigurationBuilder(Configuration copyFrom) + { + _allAttributesPrivate = copyFrom.AllAttributesPrivate; + _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; + _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; + _baseUri = copyFrom.BaseUri; + _connectionTimeout = copyFrom.ConnectionTimeout; + _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; + _evaluationReasons = copyFrom.EvaluationReasons; + _eventCapacity = copyFrom.EventCapacity; + _eventFlushInterval = copyFrom.EventFlushInterval; + _eventsUri = copyFrom.EventsUri; + _httpMessageHandler = copyFrom.HttpMessageHandler; + _inlineUsersInEvents = copyFrom.InlineUsersInEvents; + _isStreamingEnabled = copyFrom.IsStreamingEnabled; + _loggingConfigurationFactory = copyFrom.LoggingConfigurationFactory; + _mobileKey = copyFrom.MobileKey; + _offline = copyFrom.Offline; + _persistFlagValues = copyFrom.PersistFlagValues; + _pollingInterval = copyFrom.PollingInterval; + _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : + new HashSet(copyFrom.PrivateAttributeNames); + _readTimeout = copyFrom.ReadTimeout; + _reconnectTime = copyFrom.ReconnectTime; + _streamUri = copyFrom.StreamUri; + _useReport = copyFrom.UseReport; + _userKeysCapacity = copyFrom.UserKeysCapacity; + _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; + } + /// /// Creates a based on the properties that have been set on the builder. /// Modifying the builder after this point does not affect the returned . /// /// the configured Configuration object - Configuration Build(); + public Configuration Build() + { + return new Configuration(this); + } /// /// Sets whether or not user attributes (other than the key) should be private (not sent to @@ -45,8 +124,12 @@ public interface IConfigurationBuilder /// /// true if all attributes should be private /// the same builder - IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate); - + public ConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) + { + _allAttributesPrivate = allAttributesPrivate; + return this; + } + /// /// Whether to disable the automatic sending of an alias event when the current user is changed /// to a non-anonymous user andthe previous user was anonymous. @@ -61,7 +144,11 @@ public interface IConfigurationBuilder /// /// true to disable automatic user aliasing /// the same builder - IConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut); + public ConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) + { + _autoAliasingOptOut = autoAliasingOptOut; + return this; + } /// /// Sets the interval between feature flag updates when the application is running in the background. @@ -72,14 +159,29 @@ public interface IConfigurationBuilder /// /// the background polling interval /// the same builder - IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval); + public ConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval) + { + if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) + { + _backgroundPollingInterval = Configuration.MinimumBackgroundPollingInterval; + } + else + { + _backgroundPollingInterval = backgroundPollingInterval; + } + return this; + } /// /// Sets the base URI of the LaunchDarkly server. /// /// the base URI /// the same builder - IConfigurationBuilder BaseUri(Uri baseUri); + public ConfigurationBuilder BaseUri(Uri baseUri) + { + _baseUri = baseUri; + return this; + } /// /// Sets the connection timeout for all HTTP requests. @@ -89,7 +191,11 @@ public interface IConfigurationBuilder /// /// the connection timeout /// the same builder - IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout); + public ConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) + { + _connectionTimeout = connectionTimeout; + return this; + } /// /// Sets whether to enable feature flag polling when the application is in the background. @@ -103,7 +209,11 @@ public interface IConfigurationBuilder /// /// if background updating should be allowed /// the same builder - IConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating); + public ConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating) + { + _enableBackgroundUpdating = enableBackgroundUpdating; + return this; + } /// /// Set to if LaunchDarkly should provide additional information about how flag values were @@ -117,8 +227,12 @@ public interface IConfigurationBuilder /// /// if evaluation reasons are desired /// the same builder - IConfigurationBuilder EvaluationReasons(bool evaluationReasons); - + public ConfigurationBuilder EvaluationReasons(bool evaluationReasons) + { + _evaluationReasons = evaluationReasons; + return this; + } + /// /// Sets the capacity of the event buffer. /// @@ -129,7 +243,11 @@ public interface IConfigurationBuilder /// /// the capacity of the event buffer /// the same builder - IConfigurationBuilder EventCapacity(int eventCapacity); + public ConfigurationBuilder EventCapacity(int eventCapacity) + { + _eventCapacity = eventCapacity; + return this; + } /// /// Sets the time between flushes of the event buffer. @@ -140,14 +258,22 @@ public interface IConfigurationBuilder /// /// the flush interval /// the same builder - IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval); - + public ConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) + { + _eventFlushInterval = eventflushInterval; + return this; + } + /// /// Sets the base URL of the LaunchDarkly analytics event server. /// /// the events URI /// the same builder - IConfigurationBuilder EventsUri(Uri eventsUri); + public ConfigurationBuilder EventsUri(Uri eventsUri) + { + _eventsUri = eventsUri; + return this; + } /// /// Sets the object to be used for sending HTTP requests, if a specific implementation is desired. @@ -163,7 +289,11 @@ public interface IConfigurationBuilder /// /// the to use /// the same builder - IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler); + public ConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler) + { + _httpMessageHandler = httpMessageHandler; + return this; + } /// /// Sets whether to include full user details in every analytics event. @@ -174,7 +304,11 @@ public interface IConfigurationBuilder /// /// true or false /// the same builder - IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents); + public ConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) + { + _inlineUsersInEvents = inlineUsersInEvents; + return this; + } /// /// Sets whether or not the streaming API should be used to receive flag updates. @@ -184,7 +318,11 @@ public interface IConfigurationBuilder /// /// true if the streaming API should be used /// the same builder - IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled); + public ConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) + { + _isStreamingEnabled = isStreamingEnabled; + return this; + } /// /// Sets the SDK's logging destination. @@ -206,7 +344,11 @@ public interface IConfigurationBuilder /// /// an ILogAdapter for the desired logging implementation /// the same builder - IConfigurationBuilder Logging(ILogAdapter logAdapter); + public ConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) + { + _loggingConfigurationFactory = loggingConfigurationFactory; + return this; + } /// /// Sets the SDK's logging configuration, using a factory object. @@ -235,7 +377,8 @@ public interface IConfigurationBuilder /// /// /// - IConfigurationBuilder Logging(ILoggingConfigurationFactory logAdapter); + public ConfigurationBuilder Logging(ILogAdapter logAdapter) => + Logging(Components.Logging(logAdapter)); /// /// Sets the key for your LaunchDarkly environment. @@ -245,14 +388,22 @@ public interface IConfigurationBuilder /// /// /// the same builder - IConfigurationBuilder MobileKey(string mobileKey); + public ConfigurationBuilder MobileKey(string mobileKey) + { + _mobileKey = mobileKey; + return this; + } /// /// Sets whether or not this client is offline. If , no calls to LaunchDarkly will be made. /// /// if the client should remain offline /// the same builder - IConfigurationBuilder Offline(bool offline); + public ConfigurationBuilder Offline(bool offline) + { + _offline = offline; + return this; + } /// /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be @@ -263,7 +414,11 @@ public interface IConfigurationBuilder /// /// to save flag values /// the same builder - IConfigurationBuilder PersistFlagValues(bool persistFlagValues); + public ConfigurationBuilder PersistFlagValues(bool persistFlagValues) + { + _persistFlagValues = persistFlagValues; + return this; + } /// /// Sets the polling interval (when streaming is disabled). @@ -274,7 +429,18 @@ public interface IConfigurationBuilder /// /// the rule update polling interval /// the same builder - IConfigurationBuilder PollingInterval(TimeSpan pollingInterval); + public ConfigurationBuilder PollingInterval(TimeSpan pollingInterval) + { + if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) + { + _pollingInterval = Configuration.MinimumPollingInterval; + } + else + { + _pollingInterval = pollingInterval; + } + return this; + } /// /// Marks an attribute name as private for all users. @@ -291,7 +457,15 @@ public interface IConfigurationBuilder /// /// the attribute /// the same builder - IConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute); + public ConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute) + { + if (_privateAttributeNames is null) + { + _privateAttributeNames = new HashSet(); + } + _privateAttributeNames.Add(privateAttribute); + return this; + } /// /// Sets the timeout when reading data from the streaming connection. @@ -301,7 +475,11 @@ public interface IConfigurationBuilder /// /// the read timeout /// the same builder - IConfigurationBuilder ReadTimeout(TimeSpan readTimeout); + public ConfigurationBuilder ReadTimeout(TimeSpan readTimeout) + { + _readTimeout = readTimeout; + return this; + } /// /// Sets the reconnect base time for the streaming connection. @@ -312,14 +490,22 @@ public interface IConfigurationBuilder /// /// the reconnect time base value /// the same builder - IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime); + public ConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) + { + _reconnectTime = reconnectTime; + return this; + } /// /// Sets the base URI of the LaunchDarkly streaming server. /// /// the stream URI /// the same builder - IConfigurationBuilder StreamUri(Uri streamUri); + public ConfigurationBuilder StreamUri(Uri streamUri) + { + _streamUri = streamUri; + return this; + } /// /// Sets the number of user keys that the event processor can remember at any one time. @@ -330,7 +516,11 @@ public interface IConfigurationBuilder /// /// the user key cache capacity /// the same builder - IConfigurationBuilder UserKeysCapacity(int userKeysCapacity); + public ConfigurationBuilder UserKeysCapacity(int userKeysCapacity) + { + _userKeysCapacity = userKeysCapacity; + return this; + } /// /// Sets the interval at which the event processor will clear its cache of known user keys. @@ -340,262 +530,13 @@ public interface IConfigurationBuilder /// /// the flush interval /// the same builder - IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval); - } - - internal sealed class ConfigurationBuilder : IConfigurationBuilder - { - // This exists so that we can distinguish between leaving the HttpMessageHandler property unchanged - // and explicitly setting it to null. If the property value is the exact same instance as this, we - // will replace it with a platform-specific implementation. - internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); - - internal bool _autoAliasingOptOut = false; - internal bool _allAttributesPrivate = false; - internal TimeSpan _backgroundPollingInterval = Configuration.DefaultBackgroundPollingInterval; - internal Uri _baseUri = Configuration.DefaultUri; - internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; - internal bool _enableBackgroundUpdating = true; - internal bool _evaluationReasons = false; - internal int _eventCapacity = Configuration.DefaultEventCapacity; - internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; - internal Uri _eventsUri = Configuration.DefaultEventsUri; - internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; - internal bool _inlineUsersInEvents = false; - internal bool _isStreamingEnabled = true; - internal ILoggingConfigurationFactory _loggingConfigurationFactory = null; - internal string _mobileKey; - internal bool _offline = false; - internal bool _persistFlagValues = true; - internal TimeSpan _pollingInterval = Configuration.DefaultPollingInterval; - internal HashSet _privateAttributeNames = null; - internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; - internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; - internal Uri _streamUri = Configuration.DefaultStreamUri; - internal bool _useReport = false; - internal int _userKeysCapacity = Configuration.DefaultUserKeysCapacity; - internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; - - // Internal properties only settable for testing - internal IBackgroundModeManager _backgroundModeManager; - internal IConnectivityStateManager _connectivityStateManager; - internal IDeviceInfo _deviceInfo; - internal IEventProcessor _eventProcessor; - internal IFlagCacheManager _flagCacheManager; - internal IFlagChangedEventManager _flagChangedEventManager; - internal IPersistentStorage _persistentStorage; - internal Func _updateProcessorFactory; - - internal ConfigurationBuilder(string mobileKey) - { - _mobileKey = mobileKey; - } - - internal ConfigurationBuilder(Configuration copyFrom) - { - _allAttributesPrivate = copyFrom.AllAttributesPrivate; - _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; - _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; - _baseUri = copyFrom.BaseUri; - _connectionTimeout = copyFrom.ConnectionTimeout; - _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; - _evaluationReasons = copyFrom.EvaluationReasons; - _eventCapacity = copyFrom.EventCapacity; - _eventFlushInterval = copyFrom.EventFlushInterval; - _eventsUri = copyFrom.EventsUri; - _httpMessageHandler = copyFrom.HttpMessageHandler; - _inlineUsersInEvents = copyFrom.InlineUsersInEvents; - _isStreamingEnabled = copyFrom.IsStreamingEnabled; - _loggingConfigurationFactory = copyFrom.LoggingConfigurationFactory; - _mobileKey = copyFrom.MobileKey; - _offline = copyFrom.Offline; - _persistFlagValues = copyFrom.PersistFlagValues; - _pollingInterval = copyFrom.PollingInterval; - _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : - new HashSet(copyFrom.PrivateAttributeNames); - _readTimeout = copyFrom.ReadTimeout; - _reconnectTime = copyFrom.ReconnectTime; - _streamUri = copyFrom.StreamUri; - _useReport = copyFrom.UseReport; - _userKeysCapacity = copyFrom.UserKeysCapacity; - _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; - } - - public Configuration Build() - { - return new Configuration(this); - } - - public IConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) - { - _allAttributesPrivate = allAttributesPrivate; - return this; - } - - public IConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) - { - _autoAliasingOptOut = autoAliasingOptOut; - return this; - } - - public IConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval) - { - if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) - { - _backgroundPollingInterval = Configuration.MinimumBackgroundPollingInterval; - } - else - { - _backgroundPollingInterval = backgroundPollingInterval; - } - return this; - } - - public IConfigurationBuilder BaseUri(Uri baseUri) - { - _baseUri = baseUri; - return this; - } - - public IConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) - { - _connectionTimeout = connectionTimeout; - return this; - } - - public IConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating) - { - _enableBackgroundUpdating = enableBackgroundUpdating; - return this; - } - - public IConfigurationBuilder EvaluationReasons(bool evaluationReasons) - { - _evaluationReasons = evaluationReasons; - return this; - } - - public IConfigurationBuilder EventCapacity(int eventCapacity) - { - _eventCapacity = eventCapacity; - return this; - } - - public IConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) - { - _eventFlushInterval = eventflushInterval; - return this; - } - - public IConfigurationBuilder EventsUri(Uri eventsUri) - { - _eventsUri = eventsUri; - return this; - } - - public IConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler) - { - _httpMessageHandler = httpMessageHandler; - return this; - } - - public IConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) - { - _inlineUsersInEvents = inlineUsersInEvents; - return this; - } - - public IConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) - { - _isStreamingEnabled = isStreamingEnabled; - return this; - } - - public IConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) - { - _loggingConfigurationFactory = loggingConfigurationFactory; - return this; - } - - public IConfigurationBuilder Logging(ILogAdapter logAdapter) => - Logging(Components.Logging(logAdapter)); - - public IConfigurationBuilder MobileKey(string mobileKey) - { - _mobileKey = mobileKey; - return this; - } - - public IConfigurationBuilder Offline(bool offline) - { - _offline = offline; - return this; - } - - public IConfigurationBuilder PersistFlagValues(bool persistFlagValues) - { - _persistFlagValues = persistFlagValues; - return this; - } - - public IConfigurationBuilder PollingInterval(TimeSpan pollingInterval) - { - if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) - { - _pollingInterval = Configuration.MinimumPollingInterval; - } - else - { - _pollingInterval = pollingInterval; - } - return this; - } - - public IConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute) - { - if (_privateAttributeNames is null) - { - _privateAttributeNames = new HashSet(); - } - _privateAttributeNames.Add(privateAttribute); - return this; - } - - public IConfigurationBuilder ReadTimeout(TimeSpan readTimeout) - { - _readTimeout = readTimeout; - return this; - } - - public IConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) - { - _reconnectTime = reconnectTime; - return this; - } - - public IConfigurationBuilder StreamUri(Uri streamUri) - { - _streamUri = streamUri; - return this; - } - - public IConfigurationBuilder UserKeysCapacity(int userKeysCapacity) - { - _userKeysCapacity = userKeysCapacity; - return this; - } - - public IConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) + public ConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) { _userKeysFlushInterval = userKeysFlushInterval; return this; } - // The following properties are internal and settable only for testing. They are not part - // of the IConfigurationBuilder interface, so you must call the internal method - // Configuration.BuilderInternal() which exposes the internal ConfigurationBuilder, - // and then call these methods before you have called any of the public methods (since - // only these methods return ConfigurationBuilder rather than IConfigurationBuilder). + // The following properties are internal and settable only for testing. internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backgroundModeManager) { @@ -603,7 +544,7 @@ internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backg return this; } - internal IConfigurationBuilder BackgroundPollingIntervalWithoutMinimum(TimeSpan backgroundPollingInterval) + internal ConfigurationBuilder BackgroundPollingIntervalWithoutMinimum(TimeSpan backgroundPollingInterval) { _backgroundPollingInterval = backgroundPollingInterval; return this; diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index fec6daee..d2ebbd30 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -39,7 +39,7 @@ public interface ILdClient : IDisposable /// /// /// This is initially if you set it to in the configuration with - /// . However, you can change it at any time to allow the client + /// . However, you can change it at any time to allow the client /// to go online, or force it to go offline, using or /// . /// @@ -361,7 +361,7 @@ public interface ILdClient : IDisposable /// When the LaunchDarkly client generates analytics events (from flag evaluations, or from /// or ), they are queued on a worker thread. /// The event thread normally sends all queued events to LaunchDarkly at regular intervals, controlled by the - /// option. Calling triggers a send + /// option. Calling triggers a send /// without waiting for the next interval. /// /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index f5ecb4ab..5d6549b5 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -17,9 +17,9 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura User user, Logger log) { - if (configuration._flagCacheManager != null) + if (configuration.FlagCacheManager != null) { - return configuration._flagCacheManager; + return configuration.FlagCacheManager; } else { @@ -31,7 +31,7 @@ internal static IFlagCacheManager CreateFlagCacheManager(Configuration configura internal static IConnectivityStateManager CreateConnectivityStateManager(Configuration configuration) { - return configuration._connectivityStateManager ?? new DefaultConnectivityStateManager(); + return configuration.ConnectivityStateManager ?? new DefaultConnectivityStateManager(); } internal static Func CreateUpdateProcessorFactory(Configuration configuration, User user, @@ -40,9 +40,9 @@ internal static Func CreateUpdateProcessorFactory(Config Logger log = baseLog.SubLogger(LogNames.DataSourceSubLog); return () => { - if (configuration._updateProcessorFactory != null) + if (configuration.UpdateProcessorFactory != null) { - return configuration._updateProcessorFactory(configuration, flagCacheManager, user); + return configuration.UpdateProcessorFactory(configuration, flagCacheManager, user); } var featureFlagRequestor = new FeatureFlagRequestor(configuration, user, log); @@ -64,9 +64,9 @@ internal static Func CreateUpdateProcessorFactory(Config internal static IEventProcessor CreateEventProcessor(Configuration configuration, Logger baseLog) { - if (configuration._eventProcessor != null) + if (configuration.EventProcessor != null) { - return configuration._eventProcessor; + return configuration.EventProcessor; } var log = baseLog.SubLogger(LogNames.EventsSubLog); @@ -99,17 +99,17 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration internal static IPersistentStorage CreatePersistentStorage(Configuration configuration, Logger log) { - return configuration._persistentStorage ?? new DefaultPersistentStorage(log); + return configuration.PersistentStorage ?? new DefaultPersistentStorage(log); } internal static IDeviceInfo CreateDeviceInfo(Configuration configuration, Logger log) { - return configuration._deviceInfo ?? new DefaultDeviceInfo(log); + return configuration.DeviceInfo ?? new DefaultDeviceInfo(log); } internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration, Logger log) { - return configuration._flagChangedEventManager ?? new FlagChangedEventManager(log); + return configuration.FlagChangedEventManager ?? new FlagChangedEventManager(log); } } } diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 82597c4a..6959b68f 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -178,7 +178,7 @@ public event EventHandler FlagChanged }); } - _backgroundModeManager = _config._backgroundModeManager ?? new DefaultBackgroundModeManager(); + _backgroundModeManager = _config.BackgroundModeManager ?? new DefaultBackgroundModeManager(); _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs index 59d97730..f28f195a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs @@ -25,7 +25,7 @@ public class MobileStreamingProcessorTests : BaseTest private TestEventSourceFactory eventSourceFactory; private IFlagCacheManager mockFlagCacheMgr; private IFeatureFlagRequestor mockRequestor; - private IConfigurationBuilder configBuilder; + private ConfigurationBuilder configBuilder; public MobileStreamingProcessorTests(ITestOutputHelper testOutput) : base(testOutput) { @@ -33,7 +33,7 @@ public MobileStreamingProcessorTests(ITestOutputHelper testOutput) : base(testOu eventSourceFactory = new TestEventSourceFactory(mockEventSource); mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); mockRequestor = new MockFeatureFlagRequestor(initialFlagsJson); - configBuilder = Configuration.BuilderInternal("someKey") + configBuilder = Configuration.Builder("someKey") .ConnectivityStateManager(new MockConnectivityStateManager(true)) .FlagCacheManager(mockFlagCacheMgr) .IsStreamingEnabled(true) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index dbf82829..4096d567 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -264,7 +264,7 @@ string expectedPath { using (var server = HttpServer.Start(Handlers.Status(202))) { - var config = Configuration.BuilderInternal(_mobileKey) + var config = Configuration.Builder(_mobileKey) .UpdateProcessorFactory(MockPollingProcessor.Factory("{}")) .EventsUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .PersistFlagValues(false) @@ -477,9 +477,9 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) } } - private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) + private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) { - var builderInternal = Configuration.BuilderInternal(_mobileKey) + var builderInternal = Configuration.Builder(_mobileKey) .EventProcessor(new MockEventProcessor()); builderInternal .Logging(testLogging) @@ -490,7 +490,7 @@ private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) + private Configuration BaseConfig(Uri serverUri, UpdateMode mode, Func extraConfig = null) { return BaseConfig(serverUri, builder => { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 1c0441cf..c44ec1e5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -15,7 +15,7 @@ public class LdClientTests : BaseTest public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } - IConfigurationBuilder BaseConfig() => + ConfigurationBuilder BaseConfig() => TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Logging(testLogging); LdClient Client() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 9e00ca42..4bd7f85b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -139,7 +139,7 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe stubbedFlagCache.CacheFlagsForUser(flags, user); } - return Configuration.BuilderInternal(appKey) + return Configuration.Builder(appKey) .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) .ConnectivityStateManager(new MockConnectivityStateManager(true)) .EventProcessor(new MockEventProcessor()) From c7ee17f1729a26c8189a012a53313c00f5b16404 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Aug 2021 18:40:08 -0700 Subject: [PATCH 351/499] doc comment fixes + add files for new doc generator --- docs-src/README.md | 14 ++++++++++++++ docs-src/index.md | 4 ++++ .../LaunchDarkly.Sdk.Client.Integrations.md | 3 +++ .../LaunchDarkly.Sdk.Client.Interfaces.md | 5 +++++ docs-src/namespaces/LaunchDarkly.Sdk.Client.md | 3 +++ docs-src/namespaces/LaunchDarkly.Sdk.Json.md | 11 +++++++++++ docs-src/namespaces/LaunchDarkly.Sdk.md | 5 +++++ src/LaunchDarkly.ClientSdk/Components.cs | 18 ++++++++++++++++-- .../ConfigurationBuilder.cs | 14 +++++++------- .../LoggingConfigurationBuilder.cs | 2 +- 10 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 docs-src/README.md create mode 100644 docs-src/index.md create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.Client.Integrations.md create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.Client.md create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.Json.md create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.md diff --git a/docs-src/README.md b/docs-src/README.md new file mode 100644 index 00000000..39e06eff --- /dev/null +++ b/docs-src/README.md @@ -0,0 +1,14 @@ +# Notes on in-code documentation for this project + +All public types, methods, and properties should have documentation comments in the standard C# XML comment format. These will be automatically included in the [HTML documentation](https://launchdarkly.github.io/dotnet-client-sdk) that is generated on release. + +Non-public items may have documentation comments as well, since those may be helpful to other developers working on this project, but they will not be included in the HTML documentation. + +The HTML documentation also includes documentation comments from `LaunchDarkly.CommonSdk` (see "Prerequisites" above). These are included automatically when the documentation is built on release. + +The `docs-src` subdirectory contains additional Markdown content that is included in the documentation build, as follows: + +* `index.md`: This text appears on the landing page of the documentation. +* `namespaces/.md`: A file that is used as the description of a specific namespace. The first line is the summary, which will appear on both the landing page and the API page for the namespace; the rest of the file is the full description, which will appear on the API page for the namespace. + +Markdown text can include hyperlinks to namespaces, types, etc. using the syntax ``. diff --git a/docs-src/index.md b/docs-src/index.md new file mode 100644 index 00000000..98d739ac --- /dev/null +++ b/docs-src/index.md @@ -0,0 +1,4 @@ + +This site contains the full API reference for the [`LaunchDarkly.ClientSdk`](https://www.nuget.org/packages/LaunchDarkly.ClientSdk) package, as well as the `LaunchDarkly.CommonSdk` package that is included automatically as a dependency of the SDK. + +For source code, see the [GitHub repository](https://github.com/launchdarkly/dotnet-client-sdk). The [developer notes](https://github.com/launchdarkly/dotnet-client-sdk/blob/master/CONTRIBUTING.md) there include links to other repositories used in the SDK. diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Client.Integrations.md b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Integrations.md new file mode 100644 index 00000000..a847c21d --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Integrations.md @@ -0,0 +1,3 @@ +Tools for configuring how the SDK connects to LaunchDarkly, or to other software components. + +This package contains the configuration builders for standard SDK components such as . diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md new file mode 100644 index 00000000..5ae08ab9 --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md @@ -0,0 +1,5 @@ +Interfaces that provide advanced SDK features or allow customization of LaunchDarkly components. + +Most applications will not need to refer to these types. You will use them if you are creating a plug-in component, such as a data store integration, or if you use advanced features such as . + +The namespace also includes concrete types that are used as parameters within these interfaces. diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Client.md b/docs-src/namespaces/LaunchDarkly.Sdk.Client.md new file mode 100644 index 00000000..f82372fd --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Client.md @@ -0,0 +1,3 @@ +The main namespace for the LaunchDarkly client-side .NET SDK. + +You will most often use **** (the SDK client) and **** (configuration options for the client), as well as the **** type from . diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Json.md b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md new file mode 100644 index 00000000..05828f78 --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md @@ -0,0 +1,11 @@ +Helper classes and methods for interoperability with JSON. + +The NuGet package containing these types is [`LaunchDarkly.CommonSdk`](https://www.nuget.org/packages/LaunchDarkly.CommonSdk). Normally you should not need to reference that package directly; it is loaded automatically as a dependency of the main SDK package. + +Any LaunchDarkly SDK type that has the marker interface has a canonical JSON encoding that is consistent across all LaunchDarkly SDKs. There are three ways to convert any such type to or from JSON: + +* On platforms that support the `System.Text.Json` API, these types already have the necessary attributes to behave correctly with that API. +* You may use the methods and to convert to or from a JSON-encoded string. +* You may use the lower-level `LaunchDarkly.JsonStream` API (https://github.com/launchdarkly/dotnet-jsonstream) in conjunction with the converters in . + +Earlier versions of the LaunchDarkly SDKs used `Newtonsoft.Json` for JSON serialization, but current versions have no such third-party dependency. Therefore, these types will not work correctly with the reflection-based `JsonConvert` methods in `Newtonsoft.Json` without some extra logic. There is an add-on package, [`LaunchDarkly.CommonSdk.JsonNet`](https://github.com/launchdarkly/dotnet-sdk-common/tree/master/src/LaunchDarkly.CommonSdk.JsonNet), that provides an adapter to make this work; alternatively, you can call and put the resulting JSON output into a `Newtonsoft.Json.Linq.JRaw` value. diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.md b/docs-src/namespaces/LaunchDarkly.Sdk.md new file mode 100644 index 00000000..e80c63e3 --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.md @@ -0,0 +1,5 @@ +The base namespace for all LaunchDarkly .NET-based SDKs, containing common types. + +Types in this namespace are part of the overall LaunchDarkly model, shared by both server-side and client-side SDKs. The one you will use most often is ****. + +The NuGet package containing these types is [`LaunchDarkly.CommonSdk`](https://www.nuget.org/packages/LaunchDarkly.CommonSdk). Normally you should not need to reference that package directly; it is loaded automatically as a dependency of the main SDK package. diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index 2352295d..dc48cd67 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -1,9 +1,23 @@ -using System; -using LaunchDarkly.Logging; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client { + /// + /// Provides configurable factories for the standard implementations of LaunchDarkly component interfaces. + /// + /// + /// Some of the configuration options in affect the entire SDK, + /// but others are specific to one area of functionality, such as how the SDK receives feature flag + /// updates or processes analytics events. For the latter, the standard way to specify a configuration + /// is to call one of the static methods in (such as ), + /// apply any desired configuration change to the object that that method returns (such as + /// ), and then use the + /// corresponding method in (such as + /// ) to use that + /// configured component in the SDK. + /// public static class Components { /// diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 96f41899..d0e72857 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -344,11 +344,8 @@ public ConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) /// /// an ILogAdapter for the desired logging implementation /// the same builder - public ConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) - { - _loggingConfigurationFactory = loggingConfigurationFactory; - return this; - } + public ConfigurationBuilder Logging(ILogAdapter logAdapter) => + Logging(Components.Logging(logAdapter)); /// /// Sets the SDK's logging configuration, using a factory object. @@ -377,8 +374,11 @@ public ConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigur /// /// /// - public ConfigurationBuilder Logging(ILogAdapter logAdapter) => - Logging(Components.Logging(logAdapter)); + public ConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) + { + _loggingConfigurationFactory = loggingConfigurationFactory; + return this; + } /// /// Sets the key for your LaunchDarkly environment. diff --git a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs index 35f07a2e..6bb4c75f 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs @@ -31,7 +31,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// The base logger name is normally LaunchDarkly.Sdk (on iOS, this corresponds to a /// "subsystem" name of "LaunchDarkly" and a "category" name of "Sdk"). See - /// for more about logger names and how to change the name. + /// for more about logger names and how to change the name. /// /// /// From 8be90d4e05d1bf04d12206db355481f37a072bde Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 30 Aug 2021 23:00:08 -0700 Subject: [PATCH 352/499] rm obsolete file --- src/LaunchDarkly.ClientSdk/NamespaceDoc.cs | 23 ---------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/NamespaceDoc.cs diff --git a/src/LaunchDarkly.ClientSdk/NamespaceDoc.cs b/src/LaunchDarkly.ClientSdk/NamespaceDoc.cs deleted file mode 100644 index 5ef60e2a..00000000 --- a/src/LaunchDarkly.ClientSdk/NamespaceDoc.cs +++ /dev/null @@ -1,23 +0,0 @@ -// This file is not a real class but is used by Sandcastle Help File Builder to provide documentation -// for the namespace. The other way to document a namespace is to add this XML to the .shfbproj file: -// -// -// -// ...summary here... -// -// -// However, currently Sandcastle does not correctly resolve links if you use that method. - -namespace LaunchDarkly.Sdk.Client -{ - /// - /// This is the main namespace for the LaunchDarkly Client-Side .NET SDK. You will most often use - /// (the SDK client) and (configuration options for the client). The SDK also uses types - /// from , such as . - /// - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - class NamespaceDoc - { - } -} From c1a8c0fa42384d80e0ec85ff433f2db55a1eb947 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 31 Aug 2021 16:00:52 -0700 Subject: [PATCH 353/499] Apply suggestions from code review Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> --- src/LaunchDarkly.ClientSdk/Configuration.cs | 2 +- src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 97f9f352..dffb3aa4 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -67,7 +67,7 @@ public sealed class Configuration /// /// Whether to disable the automatic sending of an alias event when the current user is changed - /// to a non-anonymous user andthe previous user was anonymous. + /// to a non-anonymous user and the previous user was anonymous. /// /// /// By default, if you call or diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index b2644f13..87b8a130 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -49,7 +49,7 @@ public interface IConfigurationBuilder /// /// Whether to disable the automatic sending of an alias event when the current user is changed - /// to a non-anonymous user andthe previous user was anonymous. + /// to a non-anonymous user and the previous user was anonymous. /// /// /// By default, if you call or From 91f0a565f30f1faa9c41a39548be73c42878fc0c Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 3 Sep 2021 20:21:08 -0700 Subject: [PATCH 354/499] clarify CommonSdk documentation --- docs-src/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-src/README.md b/docs-src/README.md index 39e06eff..f21b0c0a 100644 --- a/docs-src/README.md +++ b/docs-src/README.md @@ -4,7 +4,7 @@ All public types, methods, and properties should have documentation comments in Non-public items may have documentation comments as well, since those may be helpful to other developers working on this project, but they will not be included in the HTML documentation. -The HTML documentation also includes documentation comments from `LaunchDarkly.CommonSdk` (see "Prerequisites" above). These are included automatically when the documentation is built on release. +The HTML documentation also includes documentation comments from `LaunchDarkly.CommonSdk`. These are included automatically when the documentation is built on release, so that developers can see a single unified API in the documentation rather than having to look in two packages. The `docs-src` subdirectory contains additional Markdown content that is included in the documentation build, as follows: From cf38a018c9812ce4f56fff6c3b0f48eba39279db Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 7 Sep 2021 09:46:10 -0700 Subject: [PATCH 355/499] (#4) add IDataSource interface & more component infrastructure similar to dotnet-server-sdk (#124) --- src/LaunchDarkly.ClientSdk/Components.cs | 73 ++++++ src/LaunchDarkly.ClientSdk/Configuration.cs | 98 ++------- .../ConfigurationBuilder.cs | 185 +++------------- .../{Internal/FeatureFlag.cs => DataModel.cs} | 103 ++++++--- .../Integrations/PollingDataSourceBuilder.cs | 149 +++++++++++++ .../StreamingDataSourceBuilder.cs | 208 ++++++++++++++++++ .../Interfaces/DataStoreTypes.cs | 32 +++ .../Interfaces/IDataSource.cs | 34 +++ .../Interfaces/IDataSourceFactory.cs | 27 +++ .../Interfaces/IDataSourceUpdateSink.cs | 43 ++++ .../Interfaces/LdClientContext.cs | 55 +++++ .../Internal/ComponentsImpl.cs | 29 +++ .../Internal/DataSources/ConnectionManager.cs | 40 ++-- .../DataSources/DataSourceUpdateSinkImpl.cs | 58 +++++ .../DataSources/FeatureFlagRequestor.cs | 32 ++- ...llingProcessor.cs => PollingDataSource.cs} | 42 ++-- ...ingProcessor.cs => StreamingDataSource.cs} | 116 ++++------ .../Internal/DataStores/FlagCacheManager.cs | 2 + .../DataStores/UserFlagDeviceCache.cs | 2 + .../DataStores/UserFlagInMemoryCache.cs | 2 + .../Internal/Events/EventFactory.cs | 1 + .../Internal/Factory.cs | 32 +-- .../Internal/Interfaces/IFlagCacheManager.cs | 4 +- .../Interfaces/IMobileUpdateProcessor.cs | 42 ---- .../Internal/Interfaces/IUserFlagCache.cs | 2 + .../Internal/JsonUtil.cs | 6 +- .../LaunchDarkly.ClientSdk.csproj | 12 - src/LaunchDarkly.ClientSdk/LdClient.cs | 36 ++- .../BuilderTestUtil.cs | 148 +++++++++++++ .../ConfigurationTest.cs | 172 +++++++++------ .../FeatureFlagBuilder.cs | 4 +- .../ILdClientExtensionsTest.cs | 2 + .../PollingDataSourceBuilderTest.cs | 39 ++++ .../StreamingDataSourceBuilderTest.cs | 44 ++++ .../DataSources/FeatureFlagRequestorTests.cs | 14 +- ...essorTests.cs => PollingDataSourceTest.cs} | 24 +- ...sorTests.cs => StreamingDataSourceTest.cs} | 57 +++-- .../LDClientEndToEndTests.cs | 31 +-- .../LdClientEventTests.cs | 4 +- .../LdClientTests.cs | 29 +-- .../MockComponents.cs | 81 ++++--- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 4 +- 42 files changed, 1455 insertions(+), 663 deletions(-) rename src/LaunchDarkly.ClientSdk/{Internal/FeatureFlag.cs => DataModel.cs} (57%) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs rename src/LaunchDarkly.ClientSdk/Internal/DataSources/{MobilePollingProcessor.cs => PollingDataSource.cs} (73%) rename src/LaunchDarkly.ClientSdk/Internal/DataSources/{MobileStreamingProcessor.cs => StreamingDataSource.cs} (73%) delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs rename tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/{MobilePollingProcessorTests.cs => PollingDataSourceTest.cs} (61%) rename tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/{MobileStreamingProcessorTests.cs => StreamingDataSourceTest.cs} (85%) diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index dc48cd67..aeb07a74 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -103,5 +103,78 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// public static LoggingConfigurationBuilder NoLogging => new LoggingConfigurationBuilder().Adapter(Logs.None); + + /// + /// Returns a configurable factory for using polling mode to get feature flag data. + /// + /// + /// + /// This is not the default behavior; by default, the SDK uses a streaming connection to receive feature flag + /// data from LaunchDarkly. In polling mode, the SDK instead makes a new HTTP request to LaunchDarkly at regular + /// intervals. HTTP caching allows it to avoid redundantly downloading data if there have been no changes, but + /// polling is still less efficient than streaming and should only be used on the advice of LaunchDarkly support. + /// + /// + /// The SDK may still use polling mode sometimes even when streaming mode is enabled, such as + /// when an application is in the background. You do not need to specifically select polling + /// mode in order for that to happen. + /// + /// + /// To use only polling mode, call this method to obtain a builder, change its properties with the + /// methods, and pass it to + /// . + /// + /// + /// Setting to will superseded this + /// setting and completely disable network requests. + /// + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .DataSource(Components.PollingDataSource() + /// .PollInterval(TimeSpan.FromSeconds(45))) + /// .Build(); + /// + /// + /// a builder for setting polling connection properties + /// + /// + public static PollingDataSourceBuilder PollingDataSource() => + new PollingDataSourceBuilder(); + + /// + /// Returns a configurable factory for using streaming mode to get feature flag data. + /// + /// + /// + /// By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. To use + /// the default behavior, you do not need to call this method. However, if you want to customize the behavior + /// of the connection, call this method to obtain a builder, change its properties with the + /// methods, and pass it to + /// . + /// + /// + /// The SDK may still use polling mode sometimes even when streaming mode is enabled, such as + /// when an application is in the background. + /// + /// + /// Setting to will superseded this + /// setting and completely disable network requests. + /// + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .DataSource(Components.StreamingDataSource() + /// .InitialReconnectDelay(TimeSpan.FromMilliseconds(500))) + /// .Build(); + /// + /// + /// a builder for setting streaming connection properties + /// + /// + public static StreamingDataSourceBuilder StreamingDataSource() => + new StreamingDataSourceBuilder(); } } diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index ee69f517..d53737d7 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Immutable; using System.Net.Http; -using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; @@ -20,6 +20,18 @@ namespace LaunchDarkly.Sdk.Client /// public sealed class Configuration { + /// + /// Default value for and + /// . + /// + public static readonly TimeSpan DefaultBackgroundPollInterval = TimeSpan.FromMinutes(60); + + /// + /// Minimum value for and + /// . + /// + public static readonly TimeSpan MinimumBackgroundPollInterval = TimeSpan.FromMinutes(15); + // Settable only for testing internal IBackgroundModeManager BackgroundModeManager { get; } internal IConnectivityStateManager ConnectivityStateManager { get; } @@ -28,7 +40,6 @@ public sealed class Configuration internal IFlagCacheManager FlagCacheManager { get; } internal IFlagChangedEventManager FlagChangedEventManager { get; } internal IPersistentStorage PersistentStorage { get; } - internal Func UpdateProcessorFactory { get; } /// /// Whether or not user attributes (other than the key) should be private (not sent to @@ -56,22 +67,15 @@ public sealed class Configuration public bool AutoAliasingOptOut { get; } /// - /// The interval between feature flag updates when the application is running in the background. - /// - /// - /// This is only relevant on mobile platforms. - /// - public TimeSpan BackgroundPollingInterval { get; } - - /// - /// The base URI of the LaunchDarkly server. + /// The connection timeout to the LaunchDarkly server. /// - public Uri BaseUri { get; } + public TimeSpan ConnectionTimeout { get; } /// - /// The connection timeout to the LaunchDarkly server. + /// A factory object that creates an implementation of , which will + /// receive feature flag data. /// - public TimeSpan ConnectionTimeout { get; } + public IDataSourceFactory DataSourceFactory { get; } /// /// Whether to enable feature flag updates when the application is running in the background. @@ -130,14 +134,6 @@ public sealed class Configuration /// public bool InlineUsersInEvents { get; } - /// - /// Whether or not the streaming API should be used to receive flag updates. - /// - /// - /// This is true by default. Streaming should only be disabled on the advice of LaunchDarkly support. - /// - public bool IsStreamingEnabled { get; } - internal ILoggingConfigurationFactory LoggingConfigurationFactory { get; } /// @@ -162,11 +158,6 @@ public sealed class Configuration /// public bool PersistFlagValues { get; } - /// - /// The polling interval (when streaming is disabled). - /// - public TimeSpan PollingInterval { get; } - /// /// Attribute names that have been marked as private for all users. /// @@ -182,20 +173,6 @@ public sealed class Configuration /// public TimeSpan ReadTimeout { get; } - /// - /// The reconnect base time for the streaming connection. - /// - /// - /// The streaming connection uses an exponential backoff algorithm (with jitter) for reconnects, but - /// will start the backoff with a value near the value specified here. The default value is 1 second. - /// - public TimeSpan ReconnectTime { get; } - - /// - /// The base URL of the LaunchDarkly streaming server. - /// - public Uri StreamUri { get; } - /// /// Whether to use the HTTP REPORT method for feature flag requests. /// @@ -207,30 +184,6 @@ public sealed class Configuration internal bool UseReport { get; } // UseReport is currently disabled due to Android HTTP issues (ch47341), but it's still implemented internally - /// - /// The number of user keys that the event processor can remember at any one time. - /// - /// - /// The event processor keeps track of recently seen user keys so that duplicate user details will not - /// be sent in analytics events. - /// - public int UserKeysCapacity { get; } - - /// - /// The interval at which the event processor will reset its set of known user keys. - /// - public TimeSpan UserKeysFlushInterval { get; } - - /// - /// Default value for . - /// - public static TimeSpan DefaultPollingInterval = TimeSpan.FromMinutes(5); - - /// - /// Minimum value for . - /// - public static TimeSpan MinimumPollingInterval = TimeSpan.FromMinutes(5); - internal static readonly Uri DefaultUri = new Uri("https://app.launchdarkly.com"); internal static readonly Uri DefaultStreamUri = new Uri("https://clientstream.launchdarkly.com"); internal static readonly Uri DefaultEventsUri = new Uri("https://mobile.launchdarkly.com"); @@ -238,10 +191,6 @@ public sealed class Configuration internal static readonly TimeSpan DefaultEventFlushInterval = TimeSpan.FromSeconds(5); internal static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); internal static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); - internal static readonly int DefaultUserKeysCapacity = 1000; - internal static readonly TimeSpan DefaultUserKeysFlushInterval = TimeSpan.FromMinutes(5); - internal static readonly TimeSpan DefaultBackgroundPollingInterval = TimeSpan.FromMinutes(60); - internal static readonly TimeSpan MinimumBackgroundPollingInterval = TimeSpan.FromMinutes(15); internal static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); /// @@ -296,9 +245,8 @@ internal Configuration(ConfigurationBuilder builder) { AllAttributesPrivate = builder._allAttributesPrivate; AutoAliasingOptOut = builder._autoAliasingOptOut; - BackgroundPollingInterval = builder._backgroundPollingInterval; - BaseUri = builder._baseUri; ConnectionTimeout = builder._connectionTimeout; + DataSourceFactory = builder._dataSourceFactory; EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; EventFlushInterval = builder._eventFlushInterval; @@ -308,20 +256,13 @@ internal Configuration(ConfigurationBuilder builder) PlatformSpecific.Http.CreateHttpMessageHandler(builder._connectionTimeout, builder._readTimeout) : builder._httpMessageHandler; InlineUsersInEvents = builder._inlineUsersInEvents; - IsStreamingEnabled = builder._isStreamingEnabled; LoggingConfigurationFactory = builder._loggingConfigurationFactory; MobileKey = builder._mobileKey; Offline = builder._offline; PersistFlagValues = builder._persistFlagValues; - PollingInterval = builder._pollingInterval; PrivateAttributeNames = builder._privateAttributeNames is null ? null : builder._privateAttributeNames.ToImmutableHashSet(); - ReadTimeout = builder._readTimeout; - ReconnectTime = builder._reconnectTime; - StreamUri = builder._streamUri; UseReport = builder._useReport; - UserKeysCapacity = builder._userKeysCapacity; - UserKeysFlushInterval = builder._userKeysFlushInterval; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; @@ -330,7 +271,6 @@ internal Configuration(ConfigurationBuilder builder) FlagCacheManager = builder._flagCacheManager; FlagChangedEventManager = builder._flagChangedEventManager; PersistentStorage = builder._persistentStorage; - UpdateProcessorFactory = builder._updateProcessorFactory; } internal HttpProperties HttpProperties => HttpProperties.Default diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 145a75ab..845a313a 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net.Http; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; @@ -34,9 +35,8 @@ public sealed class ConfigurationBuilder internal bool _autoAliasingOptOut = false; internal bool _allAttributesPrivate = false; - internal TimeSpan _backgroundPollingInterval = Configuration.DefaultBackgroundPollingInterval; - internal Uri _baseUri = Configuration.DefaultUri; internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; + internal IDataSourceFactory _dataSourceFactory = null; internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; internal int _eventCapacity = Configuration.DefaultEventCapacity; @@ -44,19 +44,13 @@ public sealed class ConfigurationBuilder internal Uri _eventsUri = Configuration.DefaultEventsUri; internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; internal bool _inlineUsersInEvents = false; - internal bool _isStreamingEnabled = true; internal ILoggingConfigurationFactory _loggingConfigurationFactory = null; internal string _mobileKey; internal bool _offline = false; internal bool _persistFlagValues = true; - internal TimeSpan _pollingInterval = Configuration.DefaultPollingInterval; internal HashSet _privateAttributeNames = null; internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; - internal TimeSpan _reconnectTime = Configuration.DefaultReconnectTime; - internal Uri _streamUri = Configuration.DefaultStreamUri; internal bool _useReport = false; - internal int _userKeysCapacity = Configuration.DefaultUserKeysCapacity; - internal TimeSpan _userKeysFlushInterval = Configuration.DefaultUserKeysFlushInterval; // Internal properties only settable for testing internal IBackgroundModeManager _backgroundModeManager; @@ -66,7 +60,6 @@ public sealed class ConfigurationBuilder internal IFlagCacheManager _flagCacheManager; internal IFlagChangedEventManager _flagChangedEventManager; internal IPersistentStorage _persistentStorage; - internal Func _updateProcessorFactory; internal ConfigurationBuilder(string mobileKey) { @@ -77,8 +70,6 @@ internal ConfigurationBuilder(Configuration copyFrom) { _allAttributesPrivate = copyFrom.AllAttributesPrivate; _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; - _backgroundPollingInterval = copyFrom.BackgroundPollingInterval; - _baseUri = copyFrom.BaseUri; _connectionTimeout = copyFrom.ConnectionTimeout; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; @@ -87,20 +78,14 @@ internal ConfigurationBuilder(Configuration copyFrom) _eventsUri = copyFrom.EventsUri; _httpMessageHandler = copyFrom.HttpMessageHandler; _inlineUsersInEvents = copyFrom.InlineUsersInEvents; - _isStreamingEnabled = copyFrom.IsStreamingEnabled; _loggingConfigurationFactory = copyFrom.LoggingConfigurationFactory; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; - _pollingInterval = copyFrom.PollingInterval; _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : new HashSet(copyFrom.PrivateAttributeNames); _readTimeout = copyFrom.ReadTimeout; - _reconnectTime = copyFrom.ReconnectTime; - _streamUri = copyFrom.StreamUri; _useReport = copyFrom.UseReport; - _userKeysCapacity = copyFrom.UserKeysCapacity; - _userKeysFlushInterval = copyFrom.UserKeysFlushInterval; } /// @@ -151,49 +136,39 @@ public ConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) } /// - /// Sets the interval between feature flag updates when the application is running in the background. + /// Sets the connection timeout for all HTTP requests. /// /// - /// This is only relevant on mobile platforms. The default is ; - /// the minimum is . + /// The default value is 10 seconds. /// - /// the background polling interval - /// the same builder - public ConfigurationBuilder BackgroundPollingInterval(TimeSpan backgroundPollingInterval) - { - if (backgroundPollingInterval.CompareTo(Configuration.MinimumBackgroundPollingInterval) < 0) - { - _backgroundPollingInterval = Configuration.MinimumBackgroundPollingInterval; - } - else - { - _backgroundPollingInterval = backgroundPollingInterval; - } - return this; - } - - /// - /// Sets the base URI of the LaunchDarkly server. - /// - /// the base URI + /// the connection timeout /// the same builder - public ConfigurationBuilder BaseUri(Uri baseUri) + public ConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) { - _baseUri = baseUri; + _connectionTimeout = connectionTimeout; return this; } /// - /// Sets the connection timeout for all HTTP requests. + /// Sets the implementation of the component that receives feature flag data from LaunchDarkly, + /// using a factory object. /// /// - /// The default value is 10 seconds. + /// + /// Depending on the implementation, the factory may be a builder that allows you to set other + /// configuration options as well. + /// + /// + /// The default is . You may instead use + /// . See those methods for details on how + /// to configure them. + /// /// - /// the connection timeout + /// the factory object /// the same builder - public ConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) + public ConfigurationBuilder DataSource(IDataSourceFactory dataSourceFactory) { - _connectionTimeout = connectionTimeout; + _dataSourceFactory = dataSourceFactory; return this; } @@ -203,12 +178,14 @@ public ConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) /// /// By default, on Android and iOS the SDK can still receive feature flag updates when an application /// is in the background, but it will use polling rather than maintaining a streaming connection (and - /// will use rather than ). - /// If you set this property to false, it will not check for feature flag updates until the - /// application returns to the foreground. + /// will use the background polling interval rather than the regular polling interval). If you set + /// this property to false, it will not check for feature flag updates until the application returns + /// to the foreground. /// /// if background updating should be allowed /// the same builder + /// + /// public ConfigurationBuilder EnableBackgroundUpdating(bool enableBackgroundUpdating) { _enableBackgroundUpdating = enableBackgroundUpdating; @@ -245,7 +222,7 @@ public ConfigurationBuilder EvaluationReasons(bool evaluationReasons) /// the same builder public ConfigurationBuilder EventCapacity(int eventCapacity) { - _eventCapacity = eventCapacity; + _eventCapacity = eventCapacity <= 0 ? Configuration.DefaultEventCapacity : eventCapacity; return this; } @@ -260,7 +237,8 @@ public ConfigurationBuilder EventCapacity(int eventCapacity) /// the same builder public ConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) { - _eventFlushInterval = eventflushInterval; + _eventFlushInterval = eventflushInterval <= TimeSpan.Zero ? + Configuration.DefaultEventFlushInterval : eventflushInterval; return this; } @@ -271,7 +249,7 @@ public ConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) /// the same builder public ConfigurationBuilder EventsUri(Uri eventsUri) { - _eventsUri = eventsUri; + _eventsUri = eventsUri ?? Configuration.DefaultEventsUri; return this; } @@ -310,20 +288,6 @@ public ConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) return this; } - /// - /// Sets whether or not the streaming API should be used to receive flag updates. - /// - /// - /// This is by default. Streaming should only be disabled on the advice of LaunchDarkly support. - /// - /// true if the streaming API should be used - /// the same builder - public ConfigurationBuilder IsStreamingEnabled(bool isStreamingEnabled) - { - _isStreamingEnabled = isStreamingEnabled; - return this; - } - /// /// Sets the SDK's logging destination. /// @@ -420,28 +384,6 @@ public ConfigurationBuilder PersistFlagValues(bool persistFlagValues) return this; } - /// - /// Sets the polling interval (when streaming is disabled). - /// - /// - /// The default is ; the minimum is - /// . - /// - /// the rule update polling interval - /// the same builder - public ConfigurationBuilder PollingInterval(TimeSpan pollingInterval) - { - if (pollingInterval.CompareTo(Configuration.MinimumPollingInterval) < 0) - { - _pollingInterval = Configuration.MinimumPollingInterval; - } - else - { - _pollingInterval = pollingInterval; - } - return this; - } - /// /// Marks an attribute name as private for all users. /// @@ -481,61 +423,6 @@ public ConfigurationBuilder ReadTimeout(TimeSpan readTimeout) return this; } - /// - /// Sets the reconnect base time for the streaming connection. - /// - /// - /// The streaming connection uses an exponential backoff algorithm (with jitter) for reconnects, but - /// will start the backoff with a value near the value specified here. The default value is 1 second. - /// - /// the reconnect time base value - /// the same builder - public ConfigurationBuilder ReconnectTime(TimeSpan reconnectTime) - { - _reconnectTime = reconnectTime; - return this; - } - - /// - /// Sets the base URI of the LaunchDarkly streaming server. - /// - /// the stream URI - /// the same builder - public ConfigurationBuilder StreamUri(Uri streamUri) - { - _streamUri = streamUri; - return this; - } - - /// - /// Sets the number of user keys that the event processor can remember at any one time. - /// - /// - /// The event processor keeps track of recently seen user keys so that duplicate user details will not - /// be sent in analytics events. - /// - /// the user key cache capacity - /// the same builder - public ConfigurationBuilder UserKeysCapacity(int userKeysCapacity) - { - _userKeysCapacity = userKeysCapacity; - return this; - } - - /// - /// Sets the interval at which the event processor will clear its cache of known user keys. - /// - /// - /// The default value is five minutes. - /// - /// the flush interval - /// the same builder - public ConfigurationBuilder UserKeysFlushInterval(TimeSpan userKeysFlushInterval) - { - _userKeysFlushInterval = userKeysFlushInterval; - return this; - } - // The following properties are internal and settable only for testing. internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backgroundModeManager) @@ -544,12 +431,6 @@ internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backg return this; } - internal ConfigurationBuilder BackgroundPollingIntervalWithoutMinimum(TimeSpan backgroundPollingInterval) - { - _backgroundPollingInterval = backgroundPollingInterval; - return this; - } - internal ConfigurationBuilder ConnectivityStateManager(IConnectivityStateManager connectivityStateManager) { _connectivityStateManager = connectivityStateManager; @@ -585,11 +466,5 @@ internal ConfigurationBuilder PersistentStorage(IPersistentStorage persistentSto _persistentStorage = persistentStorage; return this; } - - internal ConfigurationBuilder UpdateProcessorFactory(Func updateProcessorFactory) - { - _updateProcessorFactory = updateProcessorFactory; - return this; - } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/FeatureFlag.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs similarity index 57% rename from src/LaunchDarkly.ClientSdk/Internal/FeatureFlag.cs rename to src/LaunchDarkly.ClientSdk/DataModel.cs index 731f317c..75cb6eb3 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/FeatureFlag.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -2,45 +2,68 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; -namespace LaunchDarkly.Sdk.Client.Internal +namespace LaunchDarkly.Sdk.Client { - [JsonStreamConverter(typeof(FeatureFlag.JsonConverter))] - internal sealed class FeatureFlag : IEquatable, IJsonSerializable + /// + /// Contains information about the internal data model for feature flag state. + /// + /// + /// The details of the data model are not public to application code (although of course developers can easily + /// look at the code or the data) so that changes to LaunchDarkly SDK implementation details will not be breaking + /// changes to the application. Therefore, most of the members of this class are internal. The public members + /// provide a high-level description of model objects so that custom integration code or test code can store or + /// serialize them. + /// + public static class DataModel { - public readonly LdValue value; - public readonly int version; - public readonly int? flagVersion; - public readonly bool trackEvents; - public readonly bool trackReason; - public readonly int? variation; - public readonly UnixMillisecondTime? debugEventsUntilDate; - public readonly EvaluationReason? reason; - - public FeatureFlag(LdValue value, int version, int? flagVersion, bool trackEvents, bool trackReason, - int? variation, UnixMillisecondTime? debugEventsUntilDate, EvaluationReason? reason) + /// + /// Represents the state of a feature flag evaluation received from LaunchDarkly. + /// + [JsonStreamConverter(typeof(FeatureFlagJsonConverter))] + public sealed class FeatureFlag : IEquatable, IJsonSerializable { - this.value = value; - this.version = version; - this.flagVersion = flagVersion; - this.trackEvents = trackEvents; - this.trackReason = trackReason; - this.variation = variation; - this.debugEventsUntilDate = debugEventsUntilDate; - this.reason = reason; - } + internal LdValue value { get; } + internal int? variation { get; } + internal EvaluationReason? reason { get; } + internal int version { get; } + internal int? flagVersion { get; } + internal bool trackEvents { get; } + internal bool trackReason { get; } + internal UnixMillisecondTime? debugEventsUntilDate { get; } - public bool Equals(FeatureFlag otherFlag) - { - return value.Equals(otherFlag.value) - && version == otherFlag.version - && flagVersion == otherFlag.flagVersion - && trackEvents == otherFlag.trackEvents - && variation == otherFlag.variation - && debugEventsUntilDate == otherFlag.debugEventsUntilDate - && reason.Equals(otherFlag.reason); + internal FeatureFlag( + LdValue value, + int? variation, + EvaluationReason? reason, + int version, + int? flagVersion, + bool trackEvents, + bool trackReason, + UnixMillisecondTime? debugEventsUntilDate + ) + { + this.value = value; + this.variation = variation; + this.reason = reason; + this.version = version; + this.flagVersion = flagVersion; + this.trackEvents = trackEvents; + this.trackReason = trackReason; + this.debugEventsUntilDate = debugEventsUntilDate; + } + + /// + public bool Equals(FeatureFlag otherFlag) => + value.Equals(otherFlag.value) + && variation == otherFlag.variation + && reason.Equals(otherFlag.reason) + && version == otherFlag.version + && flagVersion == otherFlag.flagVersion + && trackEvents == otherFlag.trackEvents + && debugEventsUntilDate == otherFlag.debugEventsUntilDate; } - internal sealed class JsonConverter : IJsonStreamConverter + internal sealed class FeatureFlagJsonConverter : IJsonStreamConverter { public object ReadJson(ref JReader reader) { @@ -58,7 +81,7 @@ public static FeatureFlag ReadJsonValue(ref JReader reader) bool trackReason = false; UnixMillisecondTime? debugEventsUntilDate = null; - for (var or = reader.Object(); or.Next(ref reader); ) + for (var or = reader.Object(); or.Next(ref reader);) { // The use of multiple == tests instead of switch allows for a slight optimization on // some platforms where it wouldn't always need to allocate a string for or.Name. See: @@ -102,8 +125,16 @@ public static FeatureFlag ReadJsonValue(ref JReader reader) } } - return new FeatureFlag(value, version, flagVersion, trackEvents, trackReason, variation, - debugEventsUntilDate, reason); + return new FeatureFlag( + value, + variation, + reason, + version, + flagVersion, + trackEvents, + trackReason, + debugEventsUntilDate + ); } public void WriteJson(object o, IValueWriter writer) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs new file mode 100644 index 00000000..4b669d3d --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -0,0 +1,149 @@ +using System; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Internal.DataSources; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring the polling data source. + /// + /// + /// + /// Polling is not the default behavior; by default, the SDK uses a streaming connection to receive feature flag + /// data from LaunchDarkly. In polling mode, the SDK instead makes a new HTTP request to LaunchDarkly at regular + /// intervals. HTTP caching allows it to avoid redundantly downloading data if there have been no changes, but + /// polling is still less efficient than streaming and should only be used on the advice of LaunchDarkly support. + /// + /// + /// To use polling mode, create a builder with , change its properties + /// with the methods of this class, and pass it to . + /// + /// + /// Setting to will supersede this + /// setting and completely disable network requests. + /// + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .DataSource(Components.PollingDataSource() + /// .PollInterval(TimeSpan.FromSeconds(45))) + /// .Build(); + /// + /// + public sealed class PollingDataSourceBuilder : IDataSourceFactory + { + internal static readonly Uri DefaultBaseUri = new Uri("https://clientsdk.launchdarkly.com"); + + /// + /// The default value for : 5 minutes. + /// + public static readonly TimeSpan DefaultPollInterval = TimeSpan.FromMinutes(5); + + internal TimeSpan _backgroundPollInterval = Configuration.DefaultBackgroundPollInterval; + internal Uri _baseUri = null; + internal TimeSpan _pollInterval = DefaultPollInterval; + + /// + /// Sets the interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. The default is ; + /// the minimum is . + /// + /// the background polling interval + /// the same builder + /// + public PollingDataSourceBuilder BackgroundPollInterval(TimeSpan backgroundPollInterval) + { + _backgroundPollInterval = (backgroundPollInterval < Configuration.MinimumBackgroundPollInterval) ? + Configuration.MinimumBackgroundPollInterval : backgroundPollInterval; + return this; + } + + /// + /// Sets a custom base URI for the polling service. + /// + /// + /// You will only need to change this value in the following cases: + /// + /// + /// + /// You are using the Relay Proxy. + /// Set BaseUri to the base URI of the Relay Proxy instance. + /// + /// + /// + /// + /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. + /// + /// + /// + /// + /// the base URI of the polling service; null to use the default + /// the builder + public PollingDataSourceBuilder BaseUri(Uri baseUri) + { + _baseUri = baseUri ?? DefaultBaseUri; + return this; + } + + /// + /// Sets the interval at which the SDK will poll for feature flag updates. + /// + /// + /// The default and minimum value is . Values less than this will + /// be set to the default. + /// + /// the polling interval + /// the builder + public PollingDataSourceBuilder PollInterval(TimeSpan pollInterval) + { + _pollInterval = (pollInterval < DefaultPollInterval) ? + DefaultPollInterval : + pollInterval; + return this; + } + + // Exposed internally for testing + internal PollingDataSourceBuilder PollIntervalNoMinimum(TimeSpan pollInterval) + { + _pollInterval = pollInterval; + return this; + } + + /// + public IDataSource CreateDataSource( + LdClientContext context, + IDataSourceUpdateSink updateSink, + User currentUser, + bool inBackground + ) + { + if (!inBackground) + { + context.BaseLogger.Warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); + } + + var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); + var requestor = new FeatureFlagRequestor( + _baseUri ?? DefaultBaseUri, + currentUser, + context.UseReport, + context.EvaluationReasons, + context.HttpProperties, + logger + ); + + return new PollingDataSource( + updateSink, + currentUser, + requestor, + _pollInterval, + TimeSpan.Zero, + logger + ); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs new file mode 100644 index 00000000..e27b1c30 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -0,0 +1,208 @@ +using System; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Internal.DataSources; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring the streaming data source. + /// + /// + /// + /// By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. If you want + /// to customize the behavior of the connection, create a builder with , + /// change its properties with the methods of this class, and pass it to + /// . + /// + /// + /// Setting to will supersede this + /// setting and completely disable network requests. + /// + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .DataSource(Components.PollingDataSource() + /// .PollInterval(TimeSpan.FromSeconds(45))) + /// .Build(); + /// + /// + public sealed class StreamingDataSourceBuilder : IDataSourceFactory + { + internal static readonly Uri DefaultBaseUri = new Uri("https://clientstream.launchdarkly.com"); + + /// + /// The default value for : 1000 milliseconds. + /// + public static readonly TimeSpan DefaultInitialReconnectDelay = TimeSpan.FromSeconds(1); + + internal TimeSpan _backgroundPollInterval = Configuration.DefaultBackgroundPollInterval; + internal Uri _baseUri = null; + internal TimeSpan _initialReconnectDelay = DefaultInitialReconnectDelay; + internal Uri _pollingBaseUri = null; + + internal StreamingDataSource.EventSourceCreator _eventSourceCreator = null; // used only in testing + + /// + /// Sets the interval between feature flag updates when the application is running in the background. + /// + /// + /// This is only relevant on mobile platforms. The default is ; + /// the minimum is . + /// + /// the background polling interval + /// the same builder + /// + public StreamingDataSourceBuilder BackgroundPollInterval(TimeSpan backgroundPollInterval) + { + _backgroundPollInterval = (backgroundPollInterval < Configuration.MinimumBackgroundPollInterval) ? + Configuration.MinimumBackgroundPollInterval : backgroundPollInterval; + return this; + } + + internal StreamingDataSourceBuilder BackgroundPollingIntervalWithoutMinimum(TimeSpan backgroundPollInterval) + { + _backgroundPollInterval = backgroundPollInterval; + return this; + } + + /// + /// Sets a custom base URI for the streaming service. + /// + /// + /// You will only need to change this value in the following cases: + /// + /// + /// + /// You are using the Relay Proxy. + /// Set BaseUri to the base URI of the Relay Proxy instance. + /// + /// + /// + /// + /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. + /// + /// + /// + /// + /// the base URI of the streaming service; null to use the default + /// the builder + public StreamingDataSourceBuilder BaseUri(Uri baseUri) + { + _baseUri = baseUri; + return this; + } + + /// + /// Sets the initial reconnect delay for the streaming connection. + /// + /// + /// + /// The streaming service uses a backoff algorithm (with jitter) every time the connection needs + /// to be reestablished.The delay for the first reconnection will start near this value, and then + /// increase exponentially for any subsequent connection failures. + /// + /// + /// The default value is . + /// + /// + /// the reconnect time base value + /// the builder + public StreamingDataSourceBuilder InitialReconnectDelay(TimeSpan initialReconnectDelay) + { + _initialReconnectDelay = initialReconnectDelay; + return this; + } + + /// + /// Sets a custom base URI for the polling service. + /// + /// + /// + /// The SDK may need the polling service even if you are using streaming mode, under certain + /// circumstances: if the application has been put in the background on a mobile device, or if + /// the streaming endpoint cannot deliver an update itself and instead tells the SDK to re-poll + /// to get the update. + /// + /// + /// You will only need to change this value if you are connecting to a test server or a + /// nonstandard endpoint for the LaunchDarkly service. + /// + /// + /// If you are using the Relay Proxy. + /// you only need to set ; it will automatically default to using the + /// same value for PollingBaseUri. + /// + /// + /// the base URI of the polling service; null to use the default + /// the builder + public StreamingDataSourceBuilder PollingBaseUri(Uri pollingBaseUri) + { + _pollingBaseUri = pollingBaseUri; + return this; + } + + internal StreamingDataSourceBuilder EventSourceCreator(StreamingDataSource.EventSourceCreator fn) + { + _eventSourceCreator = fn; + return this; + } + + /// + public IDataSource CreateDataSource( + LdClientContext context, + IDataSourceUpdateSink updateSink, + User currentUser, + bool inBackground + ) + { + var baseUri = _baseUri ?? DefaultBaseUri; + Uri pollingBaseUri; + if (_pollingBaseUri is null) + { + // If they specified a nonstandard BaseUri but did *not* specify PollingBaseUri, + // we assume it's a Relay Proxy instance and we set both to the same. + pollingBaseUri = (_baseUri is null || _baseUri == DefaultBaseUri) ? + PollingDataSourceBuilder.DefaultBaseUri : + _baseUri; + } + else + { + pollingBaseUri = _pollingBaseUri; + } + + if (inBackground) + { + // When in the background, always use polling instead of streaming + return new PollingDataSourceBuilder() + .BaseUri(pollingBaseUri) + .BackgroundPollInterval(_backgroundPollInterval) + .CreateDataSource(context, updateSink, currentUser, true); + } + + var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); + var requestor = new FeatureFlagRequestor( + pollingBaseUri, + currentUser, + context.UseReport, + context.EvaluationReasons, + context.HttpProperties, + logger + ); + + return new StreamingDataSource( + updateSink, + currentUser, + baseUri, + context.UseReport, + context.EvaluationReasons, + _initialReconnectDelay, + requestor, + context.HttpProperties, + logger, + _eventSourceCreator + ); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs new file mode 100644 index 00000000..de836058 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs @@ -0,0 +1,32 @@ +using System.Collections.Immutable; + +using static LaunchDarkly.Sdk.Client.DataModel; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Types that are used by the data store and related interfaces. + /// + public static class DataStoreTypes + { + /// + /// Represents a full set of feature flag data received from LaunchDarkly. + /// + public struct FullDataSet + { + /// + /// The feature flags, indexed by key. + /// + public IImmutableDictionary Flags { get; } + + /// + /// Creates a new instance. + /// + /// the feature flags, indexed by key + public FullDataSet(IImmutableDictionary flags) + { + Flags = flags ?? ImmutableDictionary.Create(); + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs new file mode 100644 index 00000000..14007233 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for an object that receives updates to feature flags from LaunchDarkly. + /// + /// + /// This component uses a push model. When it is created (via ) it is + /// given a reference to an component, which is a write-only abstraction of + /// the data store. The SDK never requests feature flag data from the , it + /// only looks at the last known data that was previously put into the store. + /// + /// + public interface IDataSource : IDisposable + { + /// + /// Initializes the data source. This is called once from the constructor. + /// + /// a Task which is completed once the data source has finished starting up + Task Start(); + + /// + /// Checks whether the data source has finished initializing. + /// + /// + /// This is true if it has received at least one full set of feature flag data from LaunchDarkly, + /// or if it is never going to do so because we are deliberately offline. + /// + /// true if fully initialized + bool Initialized { get; } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs new file mode 100644 index 00000000..55c2abcd --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs @@ -0,0 +1,27 @@ + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for a factory that creates some implementation of . + /// + /// + /// + public interface IDataSourceFactory + { + /// + /// Called internally by the SDK to create an implementation instance. Applications do not need + /// to call this method. + /// + /// configuration of the current client instance + /// the destination for pushing data and status updates + /// the current user attributes + /// true if the application is known to be in the background + /// an instance + IDataSource CreateDataSource( + LdClientContext context, + IDataSourceUpdateSink updateSink, + User currentUser, + bool inBackground + ); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs new file mode 100644 index 00000000..1dd48a2a --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Immutable; + +using static LaunchDarkly.Sdk.Client.DataModel; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface that an implementation of will use to push data into the SDK. + /// + /// + /// The data source interacts with this object, rather than manipulating the data store directly, so + /// that the SDK can perform any other necessary operations that must happen when data is updated. + /// + public interface IDataSourceUpdateSink + { + /// + /// Completely overwrites the current contents of the data store with a set of items for each collection. + /// + /// the data set + /// the current user + /// true if the update succeeded, false if it failed + void Init(DataStoreTypes.FullDataSet data, User user); + + /// + /// Updates or inserts an item. For updates, the object will only be updated if the existing + /// version is less than the new version. + /// + /// the feature flag key + /// the data version + /// the flag data + /// the current user + void Upsert(string key, int version, FeatureFlag data, User user); + + /// + /// Deletes an item, if the version is greater than any existing version. + /// + /// the feature flag key + /// the deletion version + /// the current user + void Delete(string key, int version, User user); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs new file mode 100644 index 00000000..ec2c1c76 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -0,0 +1,55 @@ +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Internal.Http; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Encapsulates SDK client context when creating components. + /// + /// + /// Factory interfaces like receive this class as a parameter. + /// Its public properties provide information about the SDK configuration and environment. The SDK + /// may also include non-public properties that are relevant only when creating one of the built-in + /// component types and are not accessible to custom components. + /// + public sealed class LdClientContext + { + internal Configuration Configuration { get; } + + internal HttpProperties HttpProperties { get; } + + /// + /// The configured logger for the SDK. + /// + public Logger BaseLogger { get; } + + /// + /// True if evaluation reasons are enabled. + /// + public bool EvaluationReasons { get; } + + /// + /// True if the HTTP REPORT method is enabled. + /// + public bool UseReport { get; } + + /// + /// Creates an instance. + /// + /// the SDK configuration + public LdClientContext( + Configuration configuration + ) + { + var logConfig = (configuration.LoggingConfigurationFactory ?? Components.Logging()) + .CreateLoggingConfiguration(); + var logAdapter = logConfig.LogAdapter ?? Logs.None; + this.BaseLogger = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); + + this.EvaluationReasons = configuration.EvaluationReasons; + this.HttpProperties = configuration.HttpProperties; + this.UseReport = configuration.UseReport; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs new file mode 100644 index 00000000..896cbc36 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal static class ComponentsImpl + { + internal sealed class NullDataSourceFactory : IDataSourceFactory + { + public IDataSource CreateDataSource( + LdClientContext context, + IDataSourceUpdateSink updateSink, + User currentUser, + bool inBackground + ) => + new NullDataSource(); + } + + internal sealed class NullDataSource : IDataSource + { + public bool Initialized => true; + + public void Dispose() { } + + public Task Start() => Task.FromResult(true); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index 3c64c340..2d65283c 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; namespace LaunchDarkly.Sdk.Client.Internal.DataSources @@ -13,7 +13,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources /// /// /// Whenever the state of this object is modified by , - /// , , + /// , , /// or , it will decide whether to make a new connection, drop an existing /// connection, both, or neither. If the caller wants to know when a new connection (if any) is /// ready, it should await the returned task. @@ -30,8 +30,8 @@ internal sealed class ConnectionManager : IDisposable private bool _initialized = false; private bool _forceOffline = false; private bool _networkEnabled = false; - private IMobileUpdateProcessor _updateProcessor = null; - private Func _updateProcessorFactory = null; + private IDataSource _dataSource = null; + private Func _dataSourceConstructor = null; // Note that these properties do not have simple setter methods, because the setters all // need to return Tasks. @@ -173,21 +173,21 @@ public Task SetNetworkEnabled(bool networkEnabled) /// because we are in offline mode. In other words, the result is true if /// is true. /// - /// a factory function or null + /// a factory function or null /// true if we should reset the initialized state (e.g. if we /// are switching users /// a task as described above - public Task SetUpdateProcessorFactory(Func updateProcessorFactory, bool resetInitialized) + public Task SetDataSourceConstructor(Func dataSourceConstructor, bool resetInitialized) { return LockUtils.WithWriteLock(_lock, () => { - if (_disposed || _updateProcessorFactory == updateProcessorFactory) + if (_disposed || _dataSourceConstructor == dataSourceConstructor) { return Task.FromResult(false); } - _updateProcessorFactory = updateProcessorFactory; - _updateProcessor?.Dispose(); - _updateProcessor = null; + _dataSourceConstructor = dataSourceConstructor; + _dataSource?.Dispose(); + _dataSource = null; if (resetInitialized) { _initialized = false; @@ -216,19 +216,19 @@ public Task Start() public void Dispose() { - IMobileUpdateProcessor processor = null; + IDataSource dataSource = null; LockUtils.WithWriteLock(_lock, () => { if (_disposed) { return; } - processor = _updateProcessor; - _updateProcessor = null; - _updateProcessorFactory = null; + dataSource = _dataSource; + _dataSource = null; + _dataSourceConstructor = null; _disposed = true; }); - processor?.Dispose(); + dataSource?.Dispose(); } // This method is called while _lock is being held. If we're starting up a new connection, we do @@ -243,17 +243,17 @@ private Task OpenOrCloseConnectionIfNecessary() } if (_networkEnabled && !_forceOffline) { - if (_updateProcessor == null && _updateProcessorFactory != null) + if (_dataSource == null && _dataSourceConstructor != null) { - _updateProcessor = _updateProcessorFactory(); - return _updateProcessor.Start() + _dataSource = _dataSourceConstructor(); + return _dataSource.Start() .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); } } else { - _updateProcessor?.Dispose(); - _updateProcessor = null; + _dataSource?.Dispose(); + _dataSource = null; _initialized = true; return Task.FromResult(true); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs new file mode 100644 index 00000000..73aafa39 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -0,0 +1,58 @@ +using System.Collections.Immutable; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.Interfaces; + +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataSources +{ + internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink + { + private readonly IFlagCacheManager _dataStore; + + public DataSourceUpdateSinkImpl(IFlagCacheManager flagCacheManager) + { + _dataStore = flagCacheManager; + } + + public void Init(FullDataSet data, User user) + { + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var entry in data.Flags) + { + if (entry.Value != null) + { + builder.Add(entry.Key, entry.Value); + } + } + _dataStore.CacheFlagsFromService(builder.ToImmutableDictionary(), user); + } + + public void Upsert(string key, int version, FeatureFlag data, User user) + { + var oldItem = _dataStore.FlagForUser(key, user); + if (oldItem != null && oldItem.version >= version) + { + return; + } + _dataStore.UpdateFlagForUser(key, data, user); + // Eventually we should make this class responsible for sending flag change events, + // to decouple that behavior from the storage mechanism, but currently that is + // implemented within FlagCacheManager. + } + + public void Delete(string key, int version, User user) + { + var oldItem = _dataStore.FlagForUser(key, user); + if (oldItem == null || oldItem.version >= version) + { + return; + } + // Currently we are not storing a "tombstone" for deletions, so it is possible for + // an out-of-order update after a delete to recreate the flag. We need to extend the + // data store implementation to allow tombstones. + _dataStore.RemoveFlagForUser(key, user); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index 4bfa96ee..c40f6d3a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; @@ -35,25 +34,36 @@ internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor { private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); - private readonly Configuration _configuration; + private readonly Uri _baseUri; private readonly User _currentUser; + private readonly bool _useReport; + private readonly bool _withReasons; private readonly HttpClient _httpClient; private readonly HttpProperties _httpProperties; private readonly Logger _log; private volatile EntityTagHeaderValue _etag; - internal FeatureFlagRequestor(Configuration configuration, User user, Logger log) + internal FeatureFlagRequestor( + Uri baseUri, + User user, + bool useReport, + bool withReasons, + HttpProperties httpProperties, + Logger log + ) { - this._configuration = configuration; - this._httpProperties = configuration.HttpProperties; - this._httpClient = configuration.HttpProperties.NewHttpClient(); + this._baseUri = baseUri; + this._httpProperties = httpProperties; + this._httpClient = httpProperties.NewHttpClient(); this._currentUser = user; + this._useReport = useReport; + this._withReasons = withReasons; this._log = log; } public async Task FeatureFlagsAsync() { - var requestMessage = _configuration.UseReport ? ReportRequestMessage() : GetRequestMessage(); + var requestMessage = _useReport ? ReportRequestMessage() : GetRequestMessage(); return await MakeRequest(requestMessage); } @@ -72,14 +82,14 @@ private HttpRequestMessage ReportRequestMessage() private Uri MakeRequestUriWithPath(string path) { - var uri = _configuration.BaseUri.AddPath(path); - return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; + var uri = _baseUri.AddPath(path); + return _withReasons ? uri.AddQuery("withReasons=true") : uri; } private async Task MakeRequest(HttpRequestMessage request) { _httpProperties.AddHeaders(request); - using (var cts = new CancellationTokenSource(_configuration.ConnectionTimeout)) + using (var cts = new CancellationTokenSource(_httpProperties.ConnectTimeout)) { if (_etag != null) { @@ -116,7 +126,7 @@ private async Task MakeRequest(HttpRequestMessage request) } //Otherwise this was a request timeout. throw new TimeoutException("Get item with URL: " + request.RequestUri + - " timed out after : " + _configuration.ConnectionTimeout); + " timed out after : " + _httpProperties.ConnectTimeout); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobilePollingProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs similarity index 73% rename from src/LaunchDarkly.ClientSdk/Internal/DataSources/MobilePollingProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index 49b6bc3f..edd1da99 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobilePollingProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -1,37 +1,37 @@ using System; -using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client.Internal.DataSources { - internal sealed class MobilePollingProcessor : IMobileUpdateProcessor + internal sealed class PollingDataSource : IDataSource { private readonly IFeatureFlagRequestor _featureFlagRequestor; - private readonly IFlagCacheManager _flagCacheManager; + private readonly IDataSourceUpdateSink _updateSink; private readonly User _user; private readonly TimeSpan _pollingInterval; private readonly TimeSpan _initialDelay; private readonly Logger _log; private readonly TaskCompletionSource _startTask; private readonly TaskCompletionSource _stopTask; - private const int UNINITIALIZED = 0; - private const int INITIALIZED = 1; - private int _initialized = UNINITIALIZED; + private readonly AtomicBoolean _initialized = new AtomicBoolean(false); private volatile bool _disposed; - internal MobilePollingProcessor(IFeatureFlagRequestor featureFlagRequestor, - IFlagCacheManager cacheManager, - User user, - TimeSpan pollingInterval, - TimeSpan initialDelay, - Logger log) + internal PollingDataSource( + IDataSourceUpdateSink updateSink, + User user, + IFeatureFlagRequestor featureFlagRequestor, + TimeSpan pollingInterval, + TimeSpan initialDelay, + Logger log) { this._featureFlagRequestor = featureFlagRequestor; - this._flagCacheManager = cacheManager; + this._updateSink = updateSink; this._user = user; this._pollingInterval = pollingInterval; this._initialDelay = initialDelay; @@ -40,7 +40,7 @@ internal MobilePollingProcessor(IFeatureFlagRequestor featureFlagRequestor, _stopTask = new TaskCompletionSource(); } - Task IMobileUpdateProcessor.Start() + public Task Start() { if (_pollingInterval.Equals(TimeSpan.Zero)) throw new Exception("Timespan for polling can't be zero"); @@ -58,10 +58,7 @@ Task IMobileUpdateProcessor.Start() return _startTask.Task; } - bool IMobileUpdateProcessor.Initialized() - { - return _initialized == INITIALIZED; - } + public bool Initialized => _initialized.Get(); private async Task UpdateTaskLoopAsync() { @@ -85,10 +82,9 @@ private async Task UpdateTaskAsync() { var flagsAsJsonString = response.jsonResponse; var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); - _flagCacheManager.CacheFlagsFromService(flagsDictionary, _user); + _updateSink.Init(new FullDataSet(flagsDictionary), _user); - // We can't use bool in CompareExchange because it is not a reference type. - if (Interlocked.CompareExchange(ref _initialized, INITIALIZED, UNINITIALIZED) == UNINITIALIZED) + if (_initialized.GetAndSet(true) == false) { _startTask.SetResult(true); _log.Info("Initialized LaunchDarkly Polling Processor."); @@ -99,7 +95,7 @@ private async Task UpdateTaskAsync() { _log.Error("Error Updating features: '{0}'", LogValues.ExceptionSummary(ex)); _log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); - if (_initialized == UNINITIALIZED) + if (!_initialized.Get()) { _startTask.SetException(ex); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobileStreamingProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs similarity index 73% rename from src/LaunchDarkly.ClientSdk/Internal/DataSources/MobileStreamingProcessor.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index ff8d7369..d9651354 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/MobileStreamingProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -1,19 +1,20 @@ using System; -using System.Collections.Immutable; using System.Linq; -using System.Threading.Tasks; using System.Net.Http; +using System.Threading.Tasks; using LaunchDarkly.EventSource; using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client.Internal.DataSources { - internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor + internal sealed class StreamingDataSource : IDataSource { // The read timeout for the stream is not the same read timeout that can be set in the SDK configuration. // It is a fixed value that is set to be slightly longer than the expected interval between heartbeats @@ -23,11 +24,15 @@ internal sealed class MobileStreamingProcessor : IMobileUpdateProcessor private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); - private readonly Configuration _configuration; - private readonly IFlagCacheManager _cacheManager; + private readonly IDataSourceUpdateSink _updateSink; + private readonly Uri _baseUri; private readonly User _user; - private readonly EventSourceCreator _eventSourceCreator; + private readonly bool _useReport; + private readonly bool _withReasons; + private readonly TimeSpan _initialReconnectDelay; private readonly IFeatureFlagRequestor _requestor; + private readonly HttpProperties _httpProperties; + private readonly EventSourceCreator _eventSourceCreator; private readonly TaskCompletionSource _initTask; private readonly AtomicBoolean _initialized = new AtomicBoolean(false); private readonly Logger _log; @@ -41,32 +46,40 @@ internal delegate IEventSource EventSourceCreator( string jsonBody ); - internal MobileStreamingProcessor(Configuration configuration, - IFlagCacheManager cacheManager, - IFeatureFlagRequestor requestor, - User user, - EventSourceCreator eventSourceCreator, - Logger log) + internal StreamingDataSource( + IDataSourceUpdateSink updateSink, + User user, + Uri baseUri, + bool useReport, + bool withReasons, + TimeSpan initialReconnectDelay, + IFeatureFlagRequestor requestor, + HttpProperties httpProperties, + Logger log, + EventSourceCreator eventSourceCreator // used only in tests + ) { - this._configuration = configuration; - this._cacheManager = cacheManager; - this._requestor = requestor; + this._updateSink = updateSink; this._user = user; - this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; + this._baseUri = baseUri; + this._useReport = useReport; + this._withReasons = withReasons; + this._initialReconnectDelay = initialReconnectDelay; + this._requestor = requestor; + this._httpProperties = httpProperties; this._initTask = new TaskCompletionSource(); this._log = log; + this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; } - #region IMobileUpdateProcessor - - bool IMobileUpdateProcessor.Initialized() => _initialized.Get(); + public bool Initialized => _initialized.Get(); - Task IMobileUpdateProcessor.Start() + public Task Start() { - if (_configuration.UseReport) + if (_useReport) { _eventSource = _eventSourceCreator( - _configuration.HttpProperties, + _httpProperties, ReportMethod, MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), JsonUtil.EncodeJson(_user) @@ -75,7 +88,7 @@ Task IMobileUpdateProcessor.Start() else { _eventSource = _eventSourceCreator( - _configuration.HttpProperties, + _httpProperties, HttpMethod.Get, MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + Base64.UrlSafeEncode(JsonUtil.EncodeJson(_user))), @@ -91,8 +104,6 @@ Task IMobileUpdateProcessor.Start() return _initTask.Task; } - #endregion - private IEventSource CreateEventSource( HttpProperties httpProperties, HttpMethod method, @@ -103,8 +114,8 @@ string jsonBody var configBuilder = EventSource.Configuration.Builder(uri) .Method(method) .HttpMessageHandler(httpProperties.HttpMessageHandlerFactory(httpProperties)) - .ConnectionTimeout(httpProperties.ConnectTimeout) - .InitialRetryDelay(_configuration.ReconnectTime) + .ResponseStartTimeout(httpProperties.ConnectTimeout) + .InitialRetryDelay(_initialReconnectDelay) .ReadTimeout(LaunchDarklyStreamReadTimeout) .RequestHeaders(httpProperties.BaseHeaders.ToDictionary(kv => kv.Key, kv => kv.Value)) .Logger(_log); @@ -113,8 +124,8 @@ string jsonBody private Uri MakeRequestUriWithPath(string path) { - var uri = _configuration.StreamUri.AddPath(path); - return _configuration.EvaluationReasons ? uri.AddQuery("withReasons=true") : uri; + var uri = _baseUri.AddPath(path); + return _withReasons ? uri.AddQuery("withReasons=true") : uri; } private void OnOpen(object sender, EventSource.StateChangedEventArgs e) @@ -150,20 +161,18 @@ private void OnError(object sender, EventSource.ExceptionEventArgs e) if (!HttpErrors.IsRecoverable(status)) { _initTask.TrySetException(ex); // sends this exception to the client if we haven't already started up - ((IDisposable)this).Dispose(); + Dispose(true); } } } - #region IStreamProcessor - void HandleMessage(string messageType, string messageData) { switch (messageType) { case Constants.PUT: { - _cacheManager.CacheFlagsFromService(JsonUtil.DeserializeFlags(messageData), _user); + _updateSink.Init(new FullDataSet(JsonUtil.DeserializeFlags(messageData)), _user); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -177,7 +186,7 @@ void HandleMessage(string messageType, string messageData) var parsed = LdValue.Parse(messageData); var flagkey = parsed.Get(Constants.KEY).AsString; var featureFlag = JsonUtil.DecodeJson(messageData); - PatchFeatureFlag(flagkey, featureFlag); + _updateSink.Upsert(flagkey, featureFlag.version, featureFlag, _user); } catch (Exception ex) { @@ -192,7 +201,7 @@ void HandleMessage(string messageType, string messageData) var parsed = LdValue.Parse(messageData); int version = parsed.Get(Constants.VERSION).AsInt; string flagKey = parsed.Get(Constants.KEY).AsString; - DeleteFeatureFlag(flagKey, version); + _updateSink.Delete(flagKey, version, _user); } catch (Exception ex) { @@ -209,7 +218,7 @@ void HandleMessage(string messageType, string messageData) var response = await _requestor.FeatureFlagsAsync(); var flagsAsJsonString = response.jsonResponse; var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); - _cacheManager.CacheFlagsFromService(flagsDictionary, _user); + _updateSink.Init(new FullDataSet(flagsDictionary), _user); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -227,36 +236,7 @@ void HandleMessage(string messageType, string messageData) } } - void PatchFeatureFlag(string flagKey, FeatureFlag featureFlag) - { - if (FeatureFlagShouldBeDeletedOrPatched(flagKey, featureFlag.version)) - { - _cacheManager.UpdateFlagForUser(flagKey, featureFlag, _user); - } - } - - void DeleteFeatureFlag(string flagKey, int version) - { - if (FeatureFlagShouldBeDeletedOrPatched(flagKey, version)) - { - _cacheManager.RemoveFlagForUser(flagKey, _user); - } - } - - bool FeatureFlagShouldBeDeletedOrPatched(string flagKey, int version) - { - var oldFlag = _cacheManager.FlagForUser(flagKey, _user); - if (oldFlag != null) - { - return oldFlag.version < version; - } - - return true; - } - - #endregion - - void IDisposable.Dispose() + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs index e252e34d..d33fca13 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs @@ -4,6 +4,8 @@ using System.Threading; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal.DataStores { internal sealed class FlagCacheManager : IFlagCacheManager diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs index 10ec0c7f..52e3bad5 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs @@ -3,6 +3,8 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal.DataStores { internal sealed class UserFlagDeviceCache : IUserFlagCache diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs index 6f5a6814..8a1b00c5 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs @@ -2,6 +2,8 @@ using System.Collections.Immutable; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal.DataStores { internal sealed class UserFlagInMemoryCache : IUserFlagCache diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs index 17b9b9b7..03a8e04e 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs @@ -1,4 +1,5 @@  +using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Internal.Events.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client.Internal.Events diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index 5d6549b5..4d3ed983 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -34,34 +34,6 @@ internal static IConnectivityStateManager CreateConnectivityStateManager(Configu return configuration.ConnectivityStateManager ?? new DefaultConnectivityStateManager(); } - internal static Func CreateUpdateProcessorFactory(Configuration configuration, User user, - IFlagCacheManager flagCacheManager, Logger baseLog, bool inBackground) - { - Logger log = baseLog.SubLogger(LogNames.DataSourceSubLog); - return () => - { - if (configuration.UpdateProcessorFactory != null) - { - return configuration.UpdateProcessorFactory(configuration, flagCacheManager, user); - } - - var featureFlagRequestor = new FeatureFlagRequestor(configuration, user, log); - if (configuration.IsStreamingEnabled && !inBackground) - { - return new MobileStreamingProcessor(configuration, flagCacheManager, featureFlagRequestor, user, null, log); - } - else - { - return new MobilePollingProcessor(featureFlagRequestor, - flagCacheManager, - user, - inBackground ? configuration.BackgroundPollingInterval : configuration.PollingInterval, - inBackground ? configuration.BackgroundPollingInterval : TimeSpan.Zero, - log); - } - }; - } - internal static IEventProcessor CreateEventProcessor(Configuration configuration, Logger baseLog) { if (configuration.EventProcessor != null) @@ -80,9 +52,7 @@ internal static IEventProcessor CreateEventProcessor(Configuration configuration EventsUri = configuration.EventsUri.AddPath(Constants.EVENTS_PATH), InlineUsersInEvents = configuration.InlineUsersInEvents, PrivateAttributeNames = configuration.PrivateAttributeNames, - RetryInterval = TimeSpan.FromSeconds(1), - UserKeysCapacity = configuration.UserKeysCapacity, - UserKeysFlushInterval = configuration.UserKeysFlushInterval + RetryInterval = TimeSpan.FromSeconds(1) }; var httpProperties = configuration.HttpProperties; var eventProcessor = new EventProcessor( diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs index 77b54963..ee1319a5 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal.Interfaces { internal interface IFlagCacheManager @@ -10,4 +12,4 @@ internal interface IFlagCacheManager void RemoveFlagForUser(string flagKey, User user); IImmutableDictionary FlagsForUser(User user); } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs deleted file mode 100644 index f0647de7..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IMobileUpdateProcessor.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - /// - /// Interface for an object that receives updates to feature flags, user segments, and anything - /// else that might come from LaunchDarkly. - /// - internal interface IMobileUpdateProcessor : IDisposable - { - /// - /// Initializes the processor. This is called once from the constructor. - /// - /// a Task which is completed once the processor has finished starting up - Task Start(); - - /// - /// Checks whether the processor has finished initializing. - /// - /// true if fully initialized - bool Initialized(); - } - - /// - /// Used when the client is offline or in LDD mode. - /// - internal sealed class NullUpdateProcessor : IMobileUpdateProcessor - { - Task IMobileUpdateProcessor.Start() - { - return Task.FromResult(true); - } - - bool IMobileUpdateProcessor.Initialized() - { - return true; - } - - void IDisposable.Dispose() { } - } -} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs index 10744121..d068605a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal.Interfaces { internal interface IUserFlagCache diff --git a/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs b/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs index 7c544583..fc5536d5 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs @@ -4,6 +4,8 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client.Internal { internal class JsonUtil @@ -22,7 +24,7 @@ public static ImmutableDictionary DeserializeFlags(string j var builder = ImmutableDictionary.CreateBuilder(); for (var or = r.Object(); or.Next(ref r);) { - builder.Add(or.Name.ToString(), FeatureFlag.JsonConverter.ReadJsonValue(ref r)); + builder.Add(or.Name.ToString(), FeatureFlagJsonConverter.ReadJsonValue(ref r)); } return builder.ToImmutable(); } @@ -39,7 +41,7 @@ public static string SerializeFlags(IReadOnlyDictionary fla { foreach (var kv in flags) { - FeatureFlag.JsonConverter.WriteJsonValue(kv.Value, ow.Name(kv.Key)); + FeatureFlagJsonConverter.WriteJsonValue(kv.Value, ow.Name(kv.Key)); } } return w.GetString(); diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 9f2940d6..8ba9099b 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -69,16 +69,4 @@ - - - - - - - - - - - - diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 6959b68f..9bfabb3f 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -28,6 +28,9 @@ public sealed class LdClient : ILdClient // Immutable client state readonly Configuration _config; + readonly LdClientContext _context; + readonly IDataSourceFactory _dataSourceFactory; + readonly IDataSourceUpdateSink _dataSourceUpdateSink; readonly ConnectionManager _connectionManager; readonly IBackgroundModeManager _backgroundModeManager; readonly IDeviceInfo deviceInfo; @@ -129,10 +132,8 @@ public event EventHandler FlagChanged _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - var logConfig = (configuration.LoggingConfigurationFactory ?? Components.Logging()) - .CreateLoggingConfiguration(); - var logAdapter = logConfig.LogAdapter ?? Logs.None; - _log = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); + _context = new LdClientContext(configuration); + _log = _context.BaseLogger; _log.Info("Starting LaunchDarkly Client {0}", Version); @@ -143,6 +144,9 @@ public event EventHandler FlagChanged _user = DecorateUser(user); flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User, _log); + _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(flagCacheManager); + + _dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); _connectionManager = new ConnectionManager(_log); _connectionManager.SetForceOffline(configuration.Offline); @@ -150,8 +154,8 @@ public event EventHandler FlagChanged { _log.Info("Starting LaunchDarkly client in offline mode"); } - _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(configuration, User, flagCacheManager, _log, _inBackground), + _connectionManager.SetDataSourceConstructor( + MakeDataSourceConstructor(_user, _inBackground), true ); @@ -567,8 +571,8 @@ public async Task IdentifyAsync(User user) } } - return await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, newUser, flagCacheManager, _log, _inBackground), + return await _connectionManager.SetDataSourceConstructor( + MakeDataSourceConstructor(newUser, _inBackground), true ); } @@ -688,15 +692,25 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh if (!Config.EnableBackgroundUpdating) { _log.Debug("Background updating is disabled"); - await _connectionManager.SetUpdateProcessorFactory(null, false); + await _connectionManager.SetDataSourceConstructor(null, false); return; } _log.Debug("Background updating is enabled, starting polling processor"); } - await _connectionManager.SetUpdateProcessorFactory( - Factory.CreateUpdateProcessorFactory(_config, User, flagCacheManager, _log, goingIntoBackground), + await _connectionManager.SetDataSourceConstructor( + MakeDataSourceConstructor(User, goingIntoBackground), false // don't reset initialized state because the user is still the same ); } + + internal Func MakeDataSourceConstructor(User user, bool background) + { + return () => _dataSourceFactory.CreateDataSource( + _context, + _dataSourceUpdateSink, + user, + background + ); + } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs new file mode 100644 index 00000000..27d2ce10 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs @@ -0,0 +1,148 @@ +using System; +using Xunit; + +namespace LaunchDarkly.Sdk.Client +{ + public static class BuilderTestUtil + { + // Use this when we want to test the effect of builder changes on the object that + // is eventually built. + public static BuilderTestUtil For( + Func constructor, Func buildMethod) => + new BuilderTestUtil(constructor, buildMethod, null); + + // Use this when we want to test the builder's internal state directly, without + // calling Build - i.e. if the object is difficult to inspect after it's built. + public static BuilderInternalTestUtil For(Func constructor) => + new BuilderInternalTestUtil(constructor); + } + + public class BuilderTestUtil + { + private readonly Func _constructor; + internal readonly Func _buildMethod; + internal readonly Func _copyConstructor; + + public BuilderTestUtil(Func constructor, + Func buildMethod, + Func copyConstructor + ) + { + _constructor = constructor; + _buildMethod = buildMethod; + _copyConstructor = copyConstructor; + } + + public BuilderPropertyTestUtil Property( + Func getter, + Action builderSetter + ) => + new BuilderPropertyTestUtil( + this, getter, builderSetter); + + public TBuilder New() => _constructor(); + + public BuilderTestUtil WithCopyConstructor( + Func copyConstructor + ) => + new BuilderTestUtil(_constructor, _buildMethod, copyConstructor); + } + + public class BuilderPropertyTestUtil + { + private readonly BuilderTestUtil _owner; + private readonly Func _getter; + private readonly Action _builderSetter; + + public BuilderPropertyTestUtil(BuilderTestUtil owner, + Func getter, + Action builderSetter) + { + _owner = owner; + _getter = getter; + _builderSetter = builderSetter; + } + + public void AssertDefault(TValue defaultValue) + { + var b = _owner.New(); + AssertValue(b, defaultValue); + } + + public void AssertCanSet(TValue newValue) + { + AssertSetIsChangedTo(newValue, newValue); + } + + public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) + { + var b = _owner.New(); + _builderSetter(b, attemptedValue); + AssertValue(b, resultingValue); + } + + private void AssertValue(TBuilder b, TValue v) + { + var o = _owner._buildMethod(b); + Assert.Equal(v, _getter(o)); + if (_owner._copyConstructor != null) + { + var b1 = _owner._copyConstructor(o); + var o1 = _owner._buildMethod(b); + Assert.Equal(v, _getter(o)); + } + } + } + + public class BuilderInternalTestUtil + { + private readonly Func _constructor; + + public BuilderInternalTestUtil(Func constructor) + { + _constructor = constructor; + } + + public BuilderInternalPropertyTestUtil Property( + Func builderGetter, + Action builderSetter + ) => + new BuilderInternalPropertyTestUtil(this, + builderGetter, builderSetter); + + public TBuilder New() => _constructor(); + } + + public class BuilderInternalPropertyTestUtil + { + private readonly BuilderInternalTestUtil _owner; + private readonly Func _builderGetter; + private readonly Action _builderSetter; + + public BuilderInternalPropertyTestUtil(BuilderInternalTestUtil owner, + Func builderGetter, + Action builderSetter) + { + _owner = owner; + _builderGetter = builderGetter; + _builderSetter = builderSetter; + } + + public void AssertDefault(TValue defaultValue) + { + Assert.Equal(defaultValue, _builderGetter(_owner.New())); + } + + public void AssertCanSet(TValue newValue) + { + AssertSetIsChangedTo(newValue, newValue); + } + + public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) + { + var b = _owner.New(); + _builderSetter(b, attemptedValue); + Assert.Equal(resultingValue, _builderGetter(b)); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index bfa4d9b9..01b78307 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Immutable; using System.Net.Http; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Internal; using Xunit; using Xunit.Abstractions; @@ -7,116 +10,151 @@ namespace LaunchDarkly.Sdk.Client { public class ConfigurationTest : BaseTest { + private readonly BuilderTestUtil _tester = + BuilderTestUtil.For(() => Configuration.Builder(mobileKey), b => b.Build()) + .WithCopyConstructor(c => Configuration.Builder(c)); + + const string mobileKey = "any-key"; + public ConfigurationTest(ITestOutputHelper testOutput) : base(testOutput) { } [Fact] - public void TestDefaultsFromDefaultFactoryMethod() + public void DefaultSetsKey() { - VerifyDefaults(Configuration.Default("my-key")); + var config = Configuration.Default(mobileKey); + Assert.Equal(mobileKey, config.MobileKey); } [Fact] - public void TestDefaultsFromBuilder() + public void BuilderSetsKey() { - VerifyDefaults(Configuration.Builder("my-key").Build()); + var config = Configuration.Builder(mobileKey).Build(); + Assert.Equal(mobileKey, config.MobileKey); } - private void VerifyDefaults(Configuration c) + [Fact] + public void AutoAliasingOptOut() { - Assert.False(c.AllAttributesPrivate); - Assert.False(c.AutoAliasingOptOut); - Assert.Equal(Configuration.DefaultBackgroundPollingInterval, c.BackgroundPollingInterval); - Assert.Equal(Configuration.DefaultUri, c.BaseUri); - Assert.Equal(Configuration.DefaultConnectionTimeout, c.ConnectionTimeout); - Assert.True(c.EnableBackgroundUpdating); - Assert.False(c.EvaluationReasons); - Assert.Equal(Configuration.DefaultEventCapacity, c.EventCapacity); - Assert.Equal(Configuration.DefaultEventFlushInterval, c.EventFlushInterval); - Assert.Equal(Configuration.DefaultEventsUri, c.EventsUri); - Assert.False(c.InlineUsersInEvents); - Assert.True(c.IsStreamingEnabled); - Assert.False(c.Offline); - Assert.True(c.PersistFlagValues); - Assert.Equal(Configuration.DefaultPollingInterval, c.PollingInterval); - Assert.Null(c.PrivateAttributeNames); - Assert.Equal(Configuration.DefaultReadTimeout, c.ReadTimeout); - Assert.Equal(Configuration.DefaultReconnectTime, c.ReconnectTime); - Assert.Equal(Configuration.DefaultStreamUri, c.StreamUri); - Assert.False(c.UseReport); - Assert.Equal(Configuration.DefaultUserKeysCapacity, c.UserKeysCapacity); - Assert.Equal(Configuration.DefaultUserKeysFlushInterval, c.UserKeysFlushInterval); + var prop = _tester.Property(c => c.AutoAliasingOptOut, (b, v) => b.AutoAliasingOptOut(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); } [Fact] - public void CanOverrideConfiguration() + public void DataSource() { - var config = Configuration.Builder("AnyOtherSdkKey") - .AutoAliasingOptOut(true) - .BaseUri(new Uri("https://app.AnyOtherEndpoint.com")) - .EventCapacity(99) - .PollingInterval(TimeSpan.FromMinutes(45)) - .Build(); + var prop = _tester.Property(c => c.DataSourceFactory, (b, v) => b.DataSource(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new ComponentsImpl.NullDataSourceFactory()); + } - Assert.True(config.AutoAliasingOptOut); - Assert.Equal(new Uri("https://app.AnyOtherEndpoint.com"), config.BaseUri); - Assert.Equal("AnyOtherSdkKey", config.MobileKey); - Assert.Equal(99, config.EventCapacity); - Assert.Equal(TimeSpan.FromMinutes(45), config.PollingInterval); + [Fact] + public void AllAttributesPrivate() + { + var prop = _tester.Property(b => b.AllAttributesPrivate, (b, v) => b.AllAttributesPrivate(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); } [Fact] - public void CanOverrideStreamConfiguration() + public void EventCapacity() { - var config = Configuration.Builder("AnyOtherSdkKey") - .StreamUri(new Uri("https://stream.AnyOtherEndpoint.com")) - .IsStreamingEnabled(false) - .ReadTimeout(TimeSpan.FromDays(1)) - .ReconnectTime(TimeSpan.FromDays(1)) - .Build(); + var prop = _tester.Property(b => b.EventCapacity, (b, v) => b.EventCapacity(v)); + prop.AssertDefault(Configuration.DefaultEventCapacity); + prop.AssertCanSet(1); + prop.AssertSetIsChangedTo(0, Configuration.DefaultEventCapacity); + prop.AssertSetIsChangedTo(-1, Configuration.DefaultEventCapacity); + } - Assert.Equal(new Uri("https://stream.AnyOtherEndpoint.com"), config.StreamUri); - Assert.False(config.IsStreamingEnabled); - Assert.Equal(TimeSpan.FromDays(1), config.ReadTimeout); - Assert.Equal(TimeSpan.FromDays(1), config.ReconnectTime); + [Fact] + public void EventsUri() + { + var prop = _tester.Property(b => b.EventsUri, (b, v) => b.EventsUri(v)); + prop.AssertDefault(Configuration.DefaultEventsUri); + prop.AssertCanSet(new Uri("http://x")); + prop.AssertSetIsChangedTo(null, Configuration.DefaultEventsUri); } - + [Fact] - public void MobileKeyCannotBeNull() + public void FlushInterval() { - Assert.Throws(() => Configuration.Default(null)); + var prop = _tester.Property(b => b.EventFlushInterval, (b, v) => b.EventFlushInterval(v)); + prop.AssertDefault(Configuration.DefaultEventFlushInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(7)); + prop.AssertSetIsChangedTo(TimeSpan.Zero, Configuration.DefaultEventFlushInterval); + prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(-1), Configuration.DefaultEventFlushInterval); } [Fact] - public void MobileKeyCannotBeEmpty() + public void HttpMessageHandler() { - Assert.Throws(() => Configuration.Default("")); + var prop = _tester.Property(c => c.HttpMessageHandler, (b, v) => b.HttpMessageHandler(v)); + // Can't test the default here because the default is platform-dependent. + prop.AssertCanSet(new HttpClientHandler()); } [Fact] - public void CannotSetTooSmallPollingInterval() + public void InlineUsersInEvents() { - var config = Configuration.Builder("AnyOtherSdkKey").PollingInterval(TimeSpan.FromSeconds(299)).Build(); + var prop = _tester.Property(b => b.InlineUsersInEvents, (b, v) => b.InlineUsersInEvents(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } - Assert.Equal(TimeSpan.FromSeconds(300), config.PollingInterval); + [Fact] + public void Logging() + { + var prop = _tester.Property(c => c.LoggingConfigurationFactory, (b, v) => b.Logging(v)); + prop.AssertDefault(null); + prop.AssertCanSet(Components.Logging(Logs.ToWriter(Console.Out))); } [Fact] - public void CannotSetTooSmallBackgroundPollingInterval() + public void LoggingAdapterShortcut() { - var config = Configuration.Builder("SdkKey").BackgroundPollingInterval(TimeSpan.FromSeconds(899)).Build(); + var adapter = Logs.ToWriter(Console.Out); + var config = Configuration.Builder("key").Logging(adapter).Build(); + var logConfig = config.LoggingConfigurationFactory.CreateLoggingConfiguration(); + Assert.Same(adapter, logConfig.LogAdapter); + } - Assert.Equal(TimeSpan.FromSeconds(900), config.BackgroundPollingInterval); + [Fact] + public void MobileKey() + { + var prop = _tester.Property(c => c.MobileKey, (b, v) => b.MobileKey(v)); + prop.AssertCanSet("other-key"); } [Fact] - public void CanSetHttpMessageHandler() + public void Offline() { - var handler = new HttpClientHandler(); - var config = Configuration.Builder("AnyOtherSdkKey") - .HttpMessageHandler(handler) - .Build(); + var prop = _tester.Property(c => c.Offline, (b, v) => b.Offline(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } - Assert.Same(handler, config.HttpMessageHandler); + [Fact] + public void PrivateAttributes() + { + var b = _tester.New(); + Assert.Null(b.Build().PrivateAttributeNames); + b.PrivateAttribute(UserAttribute.Name); + b.PrivateAttribute(UserAttribute.Email); + b.PrivateAttribute(UserAttribute.ForName("other")); + Assert.Equal(ImmutableHashSet.Create( + UserAttribute.Name, UserAttribute.Email, UserAttribute.ForName("other")), b.Build().PrivateAttributeNames); + } + + [Fact] + public void MobileKeyCannotBeNull() + { + Assert.Throws(() => Configuration.Default(null)); + } + + [Fact] + public void MobileKeyCannotBeEmpty() + { + Assert.Throws(() => Configuration.Default("")); } } } \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs b/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs index c9ffa87e..86644585 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs @@ -1,5 +1,7 @@ using LaunchDarkly.Sdk.Client.Internal; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client { internal class FeatureFlagBuilder @@ -24,7 +26,7 @@ public FeatureFlagBuilder() public FeatureFlag Build() { - return new FeatureFlag(_value, _version, _flagVersion, _trackEvents, _trackReason, _variation, _debugEventsUntilDate, _reason); + return new FeatureFlag(_value, _variation, _reason, _version, _flagVersion, _trackEvents, _trackReason, _debugEventsUntilDate); } public FeatureFlagBuilder Value(LdValue value) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index b98c1f8d..5f676f66 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -118,7 +118,9 @@ public EvaluationDetail StringVariationDetail(string key, string default public bool Initialized => true; public bool Offline => false; +#pragma warning disable CS0067 // FlagChanged isn't used in tests public event System.EventHandler FlagChanged; +#pragma warning restore CS0067 public IDictionary AllFlags() => throw new System.NotImplementedException(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs new file mode 100644 index 00000000..76d0a579 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs @@ -0,0 +1,39 @@ +using System; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class PollingDataSourceBuilderTest + { + private readonly BuilderInternalTestUtil _tester = + BuilderTestUtil.For(Components.PollingDataSource); + + [Fact] + public void BackgroundPollInterval() + { + var prop = _tester.Property(b => b._backgroundPollInterval, (b, v) => b.BackgroundPollInterval(v)); + prop.AssertDefault(Configuration.DefaultBackgroundPollInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(90)); + prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(222), Configuration.MinimumBackgroundPollInterval); + } + + [Fact] + public void BaseUri() + { + var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new Uri("http://x")); + } + + [Fact] + public void PollInterval() + { + var prop = _tester.Property(b => b._pollInterval, (b, v) => b.PollInterval(v)); + prop.AssertDefault(PollingDataSourceBuilder.DefaultPollInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(7)); + prop.AssertSetIsChangedTo( + PollingDataSourceBuilder.DefaultPollInterval.Subtract(TimeSpan.FromMilliseconds(1)), + PollingDataSourceBuilder.DefaultPollInterval); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs new file mode 100644 index 00000000..656ae502 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs @@ -0,0 +1,44 @@ +using System; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class StreamingDataSourceBuilderTest + { + private readonly BuilderInternalTestUtil _tester = + BuilderTestUtil.For(Components.StreamingDataSource); + + [Fact] + public void BackgroundPollInterval() + { + var prop = _tester.Property(b => b._backgroundPollInterval, (b, v) => b.BackgroundPollInterval(v)); + prop.AssertDefault(Configuration.DefaultBackgroundPollInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(90)); + prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(222), Configuration.MinimumBackgroundPollInterval); + } + + [Fact] + public void BaseUri() + { + var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new Uri("http://x")); + } + + [Fact] + public void InitialReconnectDelay() + { + var prop = _tester.Property(b => b._initialReconnectDelay, (b, v) => b.InitialReconnectDelay(v)); + prop.AssertDefault(StreamingDataSourceBuilder.DefaultInitialReconnectDelay); + prop.AssertCanSet(TimeSpan.FromMilliseconds(222)); + } + + [Fact] + public void PollingBaseUri() + { + var prop = _tester.Property(b => b._pollingBaseUri, (b, v) => b.PollingBaseUri(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new Uri("http://x")); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index f9ae4f97..0bef3b2d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; @@ -39,12 +38,15 @@ string expectedQuery { using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) { - var config = Configuration.Builder(_mobileKey) - .BaseUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) - .EvaluationReasons(withReasons) - .Build(); + var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); - using (var requestor = new FeatureFlagRequestor(config, _user, testLogger)) + using (var requestor = new FeatureFlagRequestor( + baseUri, + _user, + false, + withReasons, + Configuration.Builder(_mobileKey).Build().HttpProperties, + testLogger)) { var resp = await requestor.FeatureFlagsAsync(); Assert.Equal(200, resp.statusCode); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs similarity index 61% rename from tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs rename to tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index afd2fef5..18377fa3 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobilePollingProcessorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -1,4 +1,5 @@ using System; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using Xunit; @@ -6,7 +7,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { - public class MobilePollingProcessorTests : BaseTest + public class PollingDataSourceTest : BaseTest { private const string flagsJson = "{" + "\"int-flag\":{\"value\":15}," + @@ -17,28 +18,35 @@ public class MobilePollingProcessorTests : BaseTest IFlagCacheManager mockFlagCacheManager; User user; - public MobilePollingProcessorTests(ITestOutputHelper testOutput) : base(testOutput) { } + public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } - IMobileUpdateProcessor Processor() + IDataSource MakeDataSource() { var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); var stubbedFlagCache = new UserFlagInMemoryCache(); mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); user = User.WithKey("user1Key"); - return new MobilePollingProcessor(mockFeatureFlagRequestor, mockFlagCacheManager, user, TimeSpan.FromSeconds(30), TimeSpan.Zero, testLogger); + return new PollingDataSource( + new DataSourceUpdateSinkImpl(mockFlagCacheManager), + user, + mockFeatureFlagRequestor, + TimeSpan.FromSeconds(30), + TimeSpan.Zero, + testLogger + ); } [Fact] - public void CanCreateMobilePollingProcessor() + public void CanCreatePollingDataSource() { - Assert.NotNull(Processor()); + Assert.NotNull(MakeDataSource()); } [Fact] public void StartWaitsUntilFlagCacheFilled() { - var processor = Processor(); - var initTask = processor.Start(); + var dataSource = MakeDataSource(); + var initTask = dataSource.Start(); var unused = initTask.Wait(TimeSpan.FromSeconds(1)); var flags = mockFlagCacheManager.FlagsForUser(user); Assert.Equal(3, flags.Count); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs similarity index 85% rename from tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs rename to tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index f28f195a..192d536c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/MobileStreamingProcessorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -2,6 +2,7 @@ using System.Net.Http; using System.Threading.Tasks; using LaunchDarkly.EventSource; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Internal.Http; @@ -10,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { - public class MobileStreamingProcessorTests : BaseTest + public class StreamingDataSourceTest : BaseTest { private const string initialFlagsJson = "{" + "\"int-flag\":{\"value\":15,\"version\":100}," + @@ -25,27 +26,35 @@ public class MobileStreamingProcessorTests : BaseTest private TestEventSourceFactory eventSourceFactory; private IFlagCacheManager mockFlagCacheMgr; private IFeatureFlagRequestor mockRequestor; - private ConfigurationBuilder configBuilder; + private Uri baseUri; + private TimeSpan initialReconnectDelay = StreamingDataSourceBuilder.DefaultInitialReconnectDelay; + private bool withReasons = false; - public MobileStreamingProcessorTests(ITestOutputHelper testOutput) : base(testOutput) + public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); mockRequestor = new MockFeatureFlagRequestor(initialFlagsJson); - configBuilder = Configuration.Builder("someKey") - .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .FlagCacheManager(mockFlagCacheMgr) - .IsStreamingEnabled(true) - .Logging(testLogging); + baseUri = new Uri("http://example"); } - private IMobileUpdateProcessor MobileStreamingProcessorStarted() + private StreamingDataSource MakeStartedStreamingDataSource() { - IMobileUpdateProcessor processor = new MobileStreamingProcessor(configBuilder.Build(), - mockFlagCacheMgr, mockRequestor, user, eventSourceFactory.Create(), testLogger); - processor.Start(); - return processor; + var dataSource = new StreamingDataSource( + new DataSourceUpdateSinkImpl(mockFlagCacheMgr), + user, + baseUri, + false, + withReasons, + initialReconnectDelay, + mockRequestor, + Configuration.Builder("key").Build().HttpProperties, + testLogger, + eventSourceFactory.Create() + ); + dataSource.Start(); + return dataSource; } [Theory] @@ -64,12 +73,12 @@ string expectedQuery { var fakeRootUri = "http://fake-stream-host"; var fakeBaseUri = fakeRootUri + baseUriExtraPath; - configBuilder.StreamUri(new Uri(fakeBaseUri)); - configBuilder.EvaluationReasons(withReasons); - MobileStreamingProcessorStarted(); + this.baseUri = new Uri(fakeBaseUri); + this.withReasons = withReasons; + MakeStartedStreamingDataSource(); Assert.Equal(HttpMethod.Get, eventSourceFactory.ReceivedMethod); Assert.Equal(new Uri(fakeRootUri + expectedPathWithoutUser + encodedUser + expectedQuery), - eventSourceFactory.ReceivedUri); + eventSourceFactory.ReceivedUri); } // Report mode is currently disabled - ch47341 @@ -106,7 +115,7 @@ string expectedQuery [Fact] public void PutStoresFeatureFlags() { - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); // should be empty before PUT message arrives var flagsInCache = mockFlagCacheMgr.FlagsForUser(user); Assert.Empty(flagsInCache); @@ -122,7 +131,7 @@ public void PutStoresFeatureFlags() public void PatchUpdatesFeatureFlag() { // before PATCH, fill in flags - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); @@ -140,7 +149,7 @@ public void PatchUpdatesFeatureFlag() public void PatchDoesnotUpdateFlagIfVersionIsLower() { // before PATCH, fill in flags - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); @@ -158,7 +167,7 @@ public void PatchDoesnotUpdateFlagIfVersionIsLower() public void DeleteRemovesFeatureFlag() { // before DELETE, fill in flags, test it's there - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); @@ -175,7 +184,7 @@ public void DeleteRemovesFeatureFlag() public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() { // before DELETE, fill in flags, test it's there - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; Assert.Equal(15, intFlagFromPUT); @@ -191,7 +200,7 @@ public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() [Fact] public async void PingCausesPoll() { - MobileStreamingProcessorStarted(); + MakeStartedStreamingDataSource(); mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("ping", null, null))); var deadline = DateTime.Now.Add(TimeSpan.FromSeconds(5)); while (DateTime.Now < deadline) @@ -250,7 +259,7 @@ public TestEventSourceFactory(IEventSource eventSource) _eventSource = eventSource; } - public MobileStreamingProcessor.EventSourceCreator Create() + public StreamingDataSource.EventSourceCreator Create() { return (httpProperties, method, uri, jsonBody) => { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 4096d567..e64587e1 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -80,8 +80,8 @@ public void StreamingInitMakesPollRequestIfStreamSendsPing() { using (var pollServer = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - var config = BaseConfig(streamServer.Uri, UpdateMode.Streaming, - b => b.BaseUri(pollServer.Uri)); + var config = BaseConfig(b => + b.DataSource(Components.StreamingDataSource().BaseUri(streamServer.Uri).PollingBaseUri(pollServer.Uri))); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(5))) { VerifyRequest(streamServer.Recorder, UpdateMode.Streaming); @@ -98,7 +98,7 @@ public void InitCanTimeOutSync() var handler = Handlers.Delay(TimeSpan.FromSeconds(2)).Then(SetupResponse(_flagData1, UpdateMode.Polling)); using (var server = HttpServer.Start(handler)) { - var config = BaseConfig(server.Uri, builder => builder.IsStreamingEnabled(false)); + var config = BaseConfig(builder => builder.DataSource(Components.PollingDataSource().BaseUri(server.Uri))); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized); @@ -265,7 +265,7 @@ string expectedPath using (var server = HttpServer.Start(Handlers.Status(202))) { var config = Configuration.Builder(_mobileKey) - .UpdateProcessorFactory(MockPollingProcessor.Factory("{}")) + .DataSource(MockPollingProcessor.Factory("{}")) .EventsUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .PersistFlagValues(false) .Build(); @@ -353,9 +353,9 @@ public async Task BackgroundModeForcesPollingAsync() { switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); - var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder + var config = BaseConfig(builder => builder .BackgroundModeManager(mockBackgroundModeManager) - .BackgroundPollingIntervalWithoutMinimum(backgroundInterval) + .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollingIntervalWithoutMinimum(backgroundInterval)) .PersistFlagValues(false)); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -400,10 +400,10 @@ public async Task BackgroundModePollingCanBeDisabledAsync() { switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); - var config = BaseConfig(server.Uri, UpdateMode.Streaming, builder => builder + var config = BaseConfig(builder => builder .BackgroundModeManager(mockBackgroundModeManager) .EnableBackgroundUpdating(false) - .BackgroundPollingInterval(backgroundInterval) + .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollInterval(backgroundInterval)) .PersistFlagValues(false)); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -477,14 +477,12 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) } } - private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) + private Configuration BaseConfig(Func extraConfig = null) { var builderInternal = Configuration.Builder(_mobileKey) .EventProcessor(new MockEventProcessor()); builderInternal .Logging(testLogging) - .BaseUri(serverUri) - .StreamUri(serverUri) .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination var builder = extraConfig == null ? builderInternal : extraConfig(builderInternal); return builder.Build(); @@ -492,9 +490,16 @@ private Configuration BaseConfig(Uri serverUri, Func extraConfig = null) { - return BaseConfig(serverUri, builder => + return BaseConfig(builder => { - builder.IsStreamingEnabled(mode.IsStreaming); + if (mode.IsStreaming) + { + builder.DataSource(Components.StreamingDataSource().BaseUri(serverUri)); + } + else + { + builder.DataSource(Components.PollingDataSource().BaseUri(serverUri)); + } return extraConfig == null ? builder : extraConfig(builder); }); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 2153ff6a..5644cbd0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -269,7 +269,7 @@ public void VariationSendsFeatureEventForUnknownFlag() public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") - .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) + .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor) .Logging(testLogging); @@ -377,7 +377,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") - .UpdateProcessorFactory(MockUpdateProcessorThatNeverInitializes.Factory()) + .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) .EventProcessor(eventProcessor) .Logging(testLogging); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index c44ec1e5..35093d53 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -2,10 +2,11 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest @@ -59,7 +60,7 @@ public async void InitPassesUserToUpdateProcessorFactory() User testUser = User.WithKey("new-user"); var config = TestUtil.ConfigWithFlagsJson(testUser, appKey, "{}") - .UpdateProcessorFactory(stub.AsFactory()) + .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -78,7 +79,7 @@ public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProces User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); var config = TestUtil.ConfigWithFlagsJson(anonUserIn, appKey, "{}") - .UpdateProcessorFactory(stub.AsFactory()) + .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -123,25 +124,25 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func updateProcessorFactory = (c, flags, user) => - new MockUpdateProcessorFromLambda(user, async () => + var dataSourceFactory = new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => + new MockDataSourceFromLambda(user, async () => { switch (user.Key) { case "a": - flags.CacheFlagsFromService(userAFlags, user); + updates.Init(new FullDataSet(userAFlags), user); break; case "b": startedIdentifyUserB.Release(); await canFinishIdentifyUserB.WaitAsync(); - flags.CacheFlagsFromService(userBFlags, user); + updates.Init(new FullDataSet(userBFlags), user); break; } - }); + })); var config = TestUtil.ConfigWithFlagsJson(userA, appKey, "{}") - .UpdateProcessorFactory(updateProcessorFactory) + .DataSource(dataSourceFactory) .Logging(testLogging) .Build(); @@ -192,13 +193,13 @@ public void IdentifyAsyncWithNullUserThrowsException() } [Fact] - public async void IdentifyPassesUserToUpdateProcessorFactory() + public async void IdentifyPassesUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor("{}"); User newUser = User.WithKey("new-user"); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .UpdateProcessorFactory(stub.AsFactory()) + .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -223,7 +224,7 @@ public async void IdentifyWithAutoGeneratedAnonUserPassesGeneratedUserToUpdatePr User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .UpdateProcessorFactory(stub.AsFactory()) + .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -275,7 +276,7 @@ public void ConnectionChangeShouldStopUpdateProcessor() var mockUpdateProc = new MockPollingProcessor(null); var mockConnectivityStateManager = new MockConnectivityStateManager(true); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .UpdateProcessorFactory(mockUpdateProc.AsFactory()) + .DataSource(mockUpdateProc.AsFactory()) .ConnectivityStateManager(mockConnectivityStateManager) .Logging(testLogging) .Build(); @@ -449,7 +450,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() var flagsJson = "{\"flag\": {\"value\": 100}}"; var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, flagsJson) .FlagCacheManager(null) - .UpdateProcessorFactory(MockPollingProcessor.Factory(flagsJson)) + .DataSource(MockPollingProcessor.Factory(flagsJson)) .PersistentStorage(storage) .Logging(testLogging) .Build(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index eea844d6..e8c4e1bd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -2,12 +2,16 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client { internal class MockBackgroundModeManager : IBackgroundModeManager @@ -183,9 +187,27 @@ public void Save(string key, string value) } } - internal class MockPollingProcessor : IMobileUpdateProcessor + internal class MockDataSourceFactoryFromLambda : IDataSourceFactory { - private IFlagCacheManager _cacheManager; + private readonly Func _fn; + + internal MockDataSourceFactoryFromLambda(Func fn) + { + _fn = fn; + } + + public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, User currentUser, bool inBackground) => + _fn(context, updateSink, currentUser, inBackground); + } + + internal class SingleDataSourceFactory : MockDataSourceFactoryFromLambda + { + internal SingleDataSourceFactory(IDataSource instance) : base((c, up, u, bg) => instance) { } + } + + internal class MockPollingProcessor : IDataSource + { + private IDataSourceUpdateSink _updateSink; private User _user; private string _flagsJson; @@ -193,27 +215,24 @@ internal class MockPollingProcessor : IMobileUpdateProcessor public MockPollingProcessor(string flagsJson) : this(null, null, flagsJson) { } - private MockPollingProcessor(IFlagCacheManager cacheManager, User user, string flagsJson) + private MockPollingProcessor(IDataSourceUpdateSink updateSink, User user, string flagsJson) { - _cacheManager = cacheManager; + _updateSink = updateSink; _user = user; _flagsJson = flagsJson; } - public static Func Factory(string flagsJson) - { - return (config, manager, user) => new MockPollingProcessor(manager, user, flagsJson); - } + public static IDataSourceFactory Factory(string flagsJson) => + new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => + new MockPollingProcessor(updates, user, flagsJson)); - public Func AsFactory() - { - return (config, manager, user) => + public IDataSourceFactory AsFactory() => + new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => { - _cacheManager = manager; - _user = user; + this._updateSink = updates; + this._user = user; return this; - }; - } + }); public bool IsRunning { @@ -226,29 +245,26 @@ public void Dispose() IsRunning = false; } - public bool Initialized() - { - return IsRunning; - } + public bool Initialized => IsRunning; public Task Start() { IsRunning = true; - if (_cacheManager != null && _flagsJson != null) + if (_updateSink != null && _flagsJson != null) { - _cacheManager.CacheFlagsFromService(JsonUtil.DeserializeFlags(_flagsJson), _user); + _updateSink.Init(new FullDataSet(JsonUtil.DeserializeFlags(_flagsJson)), _user); } return Task.FromResult(true); } } - internal class MockUpdateProcessorFromLambda : IMobileUpdateProcessor + internal class MockDataSourceFromLambda : IDataSource { private readonly User _user; private readonly Func _startFn; private bool _initialized; - public MockUpdateProcessorFromLambda(User user, Func startFn) + public MockDataSourceFromLambda(User user, Func startFn) { _user = user; _startFn = startFn; @@ -263,28 +279,21 @@ public Task Start() }); } - public bool Initialized() => _initialized; + public bool Initialized => _initialized; public void Dispose() { } } - internal class MockUpdateProcessorThatNeverInitializes : IMobileUpdateProcessor + internal class MockUpdateProcessorThatNeverInitializes : IDataSource { - public static Func Factory() - { - return (config, manager, user) => new MockUpdateProcessorThatNeverInitializes(); - } + public static IDataSourceFactory Factory() => + new SingleDataSourceFactory(new MockUpdateProcessorThatNeverInitializes()); public bool IsRunning => false; - public void Dispose() - { - } + public void Dispose() { } - public bool Initialized() - { - return false; - } + public bool Initialized => false; public Task Start() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 4bd7f85b..d6b1ca4a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -7,6 +7,8 @@ using LaunchDarkly.Sdk.Client.Internal.Interfaces; using Xunit; +using static LaunchDarkly.Sdk.Client.DataModel; + namespace LaunchDarkly.Sdk.Client { public static class TestUtil @@ -143,7 +145,7 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) .ConnectivityStateManager(new MockConnectivityStateManager(true)) .EventProcessor(new MockEventProcessor()) - .UpdateProcessorFactory(MockPollingProcessor.Factory(null)) + .DataSource(MockPollingProcessor.Factory(null)) .PersistentStorage(new MockPersistentStorage()) .DeviceInfo(new MockDeviceInfo()); } From c919883a52fd8064a6b5c8a7a891a713657878c7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 7 Sep 2021 15:37:17 -0700 Subject: [PATCH 356/499] (#5) scoped configuration & public interface for events (#125) --- src/LaunchDarkly.ClientSdk/Components.cs | 51 +++- src/LaunchDarkly.ClientSdk/Configuration.cs | 67 +---- .../ConfigurationBuilder.cs | 121 +------- .../Integrations/EventProcessorBuilder.cs | 234 +++++++++++++++ .../EventProcessorTypes.cs | 4 +- .../Interfaces/IEventProcessor.cs | 65 ++++ .../Interfaces/IEventProcessorFactory.cs | 17 ++ .../Interfaces/ILdClient.cs | 3 +- .../Internal/ComponentsImpl.cs | 30 +- .../Events/DefaultEventProcessorWrapper.cs | 3 +- .../Internal/Events/EventFactory.cs | 2 +- .../Internal/Events/IEventProcessor.cs | 19 -- .../Internal/Factory.cs | 39 +-- .../LaunchDarkly.ClientSdk.csproj | 7 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 93 +++--- .../BuilderBehavior.cs | 283 ++++++++++++++++++ .../ConfigurationTest.cs | 63 ++-- .../Integrations/EventProcessorBuilderTest.cs | 76 +++++ .../PollingDataSourceBuilderTest.cs | 5 +- .../StreamingDataSourceBuilderTest.cs | 5 +- .../LDClientEndToEndTests.cs | 4 +- .../LdClientEventTests.cs | 16 +- .../LdClientTests.cs | 6 +- .../MockComponents.cs | 12 + .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 5 +- 25 files changed, 878 insertions(+), 352 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs rename src/LaunchDarkly.ClientSdk/{Internal/Events => Interfaces}/EventProcessorTypes.cs (98%) create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index aeb07a74..cf7b7c29 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -1,6 +1,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; namespace LaunchDarkly.Sdk.Client { @@ -88,6 +89,24 @@ public static LoggingConfigurationBuilder Logging() => public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => new LoggingConfigurationBuilder().Adapter(adapter); + /// + /// Returns a configuration object that disables analytics events. + /// + /// + /// Passing this to causes + /// the SDK to discard all analytics events and not send them to LaunchDarkly, regardless of + /// any other configuration. + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .Events(Components.NoEvents) + /// .Build(); + /// + /// + public static IEventProcessorFactory NoEvents => + ComponentsImpl.NullEventProcessorFactory.Instance; + /// /// A configuration object that disables logging. /// @@ -96,7 +115,7 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// /// /// - /// var config = Configuration.Builder(sdkKey) + /// var config = Configuration.Builder(mobileKey) /// .Logging(Components.NoLogging) /// .Build(); /// @@ -131,7 +150,7 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// /// /// - /// var config = Configuration.Builder(sdkKey) + /// var config = Configuration.Builder(mobileKey) /// .DataSource(Components.PollingDataSource() /// .PollInterval(TimeSpan.FromSeconds(45))) /// .Build(); @@ -143,6 +162,32 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => public static PollingDataSourceBuilder PollingDataSource() => new PollingDataSourceBuilder(); + /// + /// Returns a configuration builder for analytics event delivery. + /// + /// + /// + /// The default configuration has events enabled with default settings. If you want to + /// customize this behavior, call this method to obtain a builder, change its properties + /// with the methods, and pass it to + /// . + /// + /// + /// To completely disable sending analytics events, use instead. + /// + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .Events(Components.SendEvents() + /// .Capacity(5000) + /// .FlushInterval(TimeSpan.FromSeconds(2))) + /// .Build(); + /// + /// + /// a builder for setting event properties + public static EventProcessorBuilder SendEvents() => new EventProcessorBuilder(); + /// /// Returns a configurable factory for using streaming mode to get feature flag data. /// @@ -165,7 +210,7 @@ public static PollingDataSourceBuilder PollingDataSource() => /// /// /// - /// var config = Configuration.Builder(sdkKey) + /// var config = Configuration.Builder(mobileKey) /// .DataSource(Components.StreamingDataSource() /// .InitialReconnectDelay(TimeSpan.FromMilliseconds(500))) /// .Build(); diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index d53737d7..93942865 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -36,22 +36,10 @@ public sealed class Configuration internal IBackgroundModeManager BackgroundModeManager { get; } internal IConnectivityStateManager ConnectivityStateManager { get; } internal IDeviceInfo DeviceInfo { get; } - internal IEventProcessor EventProcessor { get; } internal IFlagCacheManager FlagCacheManager { get; } internal IFlagChangedEventManager FlagChangedEventManager { get; } internal IPersistentStorage PersistentStorage { get; } - /// - /// Whether or not user attributes (other than the key) should be private (not sent to - /// the LaunchDarkly server). - /// - /// - /// By default, this is . If , all of the user attributes - /// will be private, not just the attributes specified with - /// or with the method. - /// - public bool AllAttributesPrivate { get; } - /// /// Whether to disable the automatic sending of an alias event when the current user is changed /// to a non-anonymous user and the previous user was anonymous. @@ -97,43 +85,18 @@ public sealed class Configuration /// public bool EvaluationReasons { get; } - /// - /// The capacity of the event buffer. - /// - /// - /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded - /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events - /// are less likely to be discarded, at the cost of consuming more memory. - /// - public int EventCapacity { get; } - - /// - /// The time between flushes of the event buffer. - /// - /// - /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. - /// - public TimeSpan EventFlushInterval { get; } /// - /// The base URL of the LaunchDarkly analytics event server. + /// A factory object that creates an implementation of , responsible + /// for sending analytics events. /// - public Uri EventsUri { get; } + public IEventProcessorFactory EventProcessorFactory { get; } /// /// The object to be used for sending HTTP requests, if a specific implementation is desired. /// public HttpMessageHandler HttpMessageHandler { get; } - /// - /// Sets whether to include full user details in every analytics event. - /// - /// - /// The default is : events will only include the user key, except for one - /// "index" event that provides the full details for the user. - /// - public bool InlineUsersInEvents { get; } - internal ILoggingConfigurationFactory LoggingConfigurationFactory { get; } /// @@ -158,16 +121,6 @@ public sealed class Configuration /// public bool PersistFlagValues { get; } - /// - /// Attribute names that have been marked as private for all users. - /// - /// - /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name - /// removed, even if you did not use the - /// method when building the user. - /// - public IImmutableSet PrivateAttributeNames { get; } - /// /// The timeout when reading data from the streaming connection. /// @@ -184,11 +137,6 @@ public sealed class Configuration internal bool UseReport { get; } // UseReport is currently disabled due to Android HTTP issues (ch47341), but it's still implemented internally - internal static readonly Uri DefaultUri = new Uri("https://app.launchdarkly.com"); - internal static readonly Uri DefaultStreamUri = new Uri("https://clientstream.launchdarkly.com"); - internal static readonly Uri DefaultEventsUri = new Uri("https://mobile.launchdarkly.com"); - internal static readonly int DefaultEventCapacity = 100; - internal static readonly TimeSpan DefaultEventFlushInterval = TimeSpan.FromSeconds(5); internal static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); internal static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); internal static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); @@ -243,31 +191,24 @@ public static ConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { - AllAttributesPrivate = builder._allAttributesPrivate; AutoAliasingOptOut = builder._autoAliasingOptOut; ConnectionTimeout = builder._connectionTimeout; DataSourceFactory = builder._dataSourceFactory; EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; - EventFlushInterval = builder._eventFlushInterval; - EventCapacity = builder._eventCapacity; - EventsUri = builder._eventsUri; + EventProcessorFactory = builder._eventProcessorFactory; HttpMessageHandler = object.ReferenceEquals(builder._httpMessageHandler, ConfigurationBuilder.DefaultHttpMessageHandlerInstance) ? PlatformSpecific.Http.CreateHttpMessageHandler(builder._connectionTimeout, builder._readTimeout) : builder._httpMessageHandler; - InlineUsersInEvents = builder._inlineUsersInEvents; LoggingConfigurationFactory = builder._loggingConfigurationFactory; MobileKey = builder._mobileKey; Offline = builder._offline; PersistFlagValues = builder._persistFlagValues; - PrivateAttributeNames = builder._privateAttributeNames is null ? null : - builder._privateAttributeNames.ToImmutableHashSet(); UseReport = builder._useReport; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; DeviceInfo = builder._deviceInfo; - EventProcessor = builder._eventProcessor; FlagCacheManager = builder._flagCacheManager; FlagChangedEventManager = builder._flagChangedEventManager; PersistentStorage = builder._persistentStorage; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 845a313a..cca120d5 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -34,21 +34,16 @@ public sealed class ConfigurationBuilder internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); internal bool _autoAliasingOptOut = false; - internal bool _allAttributesPrivate = false; internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; internal IDataSourceFactory _dataSourceFactory = null; internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; - internal int _eventCapacity = Configuration.DefaultEventCapacity; - internal TimeSpan _eventFlushInterval = Configuration.DefaultEventFlushInterval; - internal Uri _eventsUri = Configuration.DefaultEventsUri; + internal IEventProcessorFactory _eventProcessorFactory = null; internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; - internal bool _inlineUsersInEvents = false; internal ILoggingConfigurationFactory _loggingConfigurationFactory = null; internal string _mobileKey; internal bool _offline = false; internal bool _persistFlagValues = true; - internal HashSet _privateAttributeNames = null; internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; internal bool _useReport = false; @@ -56,7 +51,6 @@ public sealed class ConfigurationBuilder internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; internal IDeviceInfo _deviceInfo; - internal IEventProcessor _eventProcessor; internal IFlagCacheManager _flagCacheManager; internal IFlagChangedEventManager _flagChangedEventManager; internal IPersistentStorage _persistentStorage; @@ -68,22 +62,17 @@ internal ConfigurationBuilder(string mobileKey) internal ConfigurationBuilder(Configuration copyFrom) { - _allAttributesPrivate = copyFrom.AllAttributesPrivate; _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; _connectionTimeout = copyFrom.ConnectionTimeout; + _dataSourceFactory = copyFrom.DataSourceFactory; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; - _eventCapacity = copyFrom.EventCapacity; - _eventFlushInterval = copyFrom.EventFlushInterval; - _eventsUri = copyFrom.EventsUri; + _eventProcessorFactory = copyFrom.EventProcessorFactory; _httpMessageHandler = copyFrom.HttpMessageHandler; - _inlineUsersInEvents = copyFrom.InlineUsersInEvents; _loggingConfigurationFactory = copyFrom.LoggingConfigurationFactory; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; - _privateAttributeNames = copyFrom.PrivateAttributeNames is null ? null : - new HashSet(copyFrom.PrivateAttributeNames); _readTimeout = copyFrom.ReadTimeout; _useReport = copyFrom.UseReport; } @@ -98,23 +87,6 @@ public Configuration Build() return new Configuration(this); } - /// - /// Sets whether or not user attributes (other than the key) should be private (not sent to - /// the LaunchDarkly server). - /// - /// - /// By default, this is . If , all of the user attributes - /// will be private, not just the attributes specified with - /// or with the method. - /// - /// true if all attributes should be private - /// the same builder - public ConfigurationBuilder AllAttributesPrivate(bool allAttributesPrivate) - { - _allAttributesPrivate = allAttributesPrivate; - return this; - } - /// /// Whether to disable the automatic sending of an alias event when the current user is changed /// to a non-anonymous user and the previous user was anonymous. @@ -211,45 +183,18 @@ public ConfigurationBuilder EvaluationReasons(bool evaluationReasons) } /// - /// Sets the capacity of the event buffer. + /// Sets the implementation of the component that processes analytics events. /// /// - /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded - /// before the buffer is flushed, events will be discarded. Increasing the capacity means that events - /// are less likely to be discarded, at the cost of consuming more memory. + /// The default is , but you may choose to set it to a customized + /// , a custom implementation (for instance, a test fixture), or + /// disable events with . /// - /// the capacity of the event buffer + /// a builder/factory object for event configuration /// the same builder - public ConfigurationBuilder EventCapacity(int eventCapacity) + public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) { - _eventCapacity = eventCapacity <= 0 ? Configuration.DefaultEventCapacity : eventCapacity; - return this; - } - - /// - /// Sets the time between flushes of the event buffer. - /// - /// - /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. The - /// default value is 5 seconds. - /// - /// the flush interval - /// the same builder - public ConfigurationBuilder EventFlushInterval(TimeSpan eventflushInterval) - { - _eventFlushInterval = eventflushInterval <= TimeSpan.Zero ? - Configuration.DefaultEventFlushInterval : eventflushInterval; - return this; - } - - /// - /// Sets the base URL of the LaunchDarkly analytics event server. - /// - /// the events URI - /// the same builder - public ConfigurationBuilder EventsUri(Uri eventsUri) - { - _eventsUri = eventsUri ?? Configuration.DefaultEventsUri; + _eventProcessorFactory = eventProcessorFactory; return this; } @@ -273,21 +218,6 @@ public ConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHan return this; } - /// - /// Sets whether to include full user details in every analytics event. - /// - /// - /// The default is : events will only include the user key, except for one - /// "index" event that provides the full details for the user. - /// - /// true or false - /// the same builder - public ConfigurationBuilder InlineUsersInEvents(bool inlineUsersInEvents) - { - _inlineUsersInEvents = inlineUsersInEvents; - return this; - } - /// /// Sets the SDK's logging destination. /// @@ -384,31 +314,6 @@ public ConfigurationBuilder PersistFlagValues(bool persistFlagValues) return this; } - /// - /// Marks an attribute name as private for all users. - /// - /// - /// - /// Any users sent to LaunchDarkly with this configuration active will have attributes with this name - /// removed, even if you did not use the - /// method in . - /// - /// - /// You may call this method repeatedly to mark multiple attributes as private. - /// - /// - /// the attribute - /// the same builder - public ConfigurationBuilder PrivateAttribute(UserAttribute privateAttribute) - { - if (_privateAttributeNames is null) - { - _privateAttributeNames = new HashSet(); - } - _privateAttributeNames.Add(privateAttribute); - return this; - } - /// /// Sets the timeout when reading data from the streaming connection. /// @@ -443,12 +348,6 @@ internal ConfigurationBuilder DeviceInfo(IDeviceInfo deviceInfo) return this; } - internal ConfigurationBuilder EventProcessor(IEventProcessor eventProcessor) - { - _eventProcessor = eventProcessor; - return this; - } - internal ConfigurationBuilder FlagCacheManager(IFlagCacheManager flagCacheManager) { _flagCacheManager = flagCacheManager; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs new file mode 100644 index 00000000..695690cd --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Internal.Events; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Events; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring delivery of analytics events. + /// + /// + /// The SDK normally buffers analytics events and sends them to LaunchDarkly at intervals. If you want + /// to customize this behavior, create a builder with , change its + /// properties with the methods of this class, and pass it to . + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .Events( + /// Components.SendEvents().Capacity(5000).FlushInterval(TimeSpan.FromSeconds(2)) + /// ) + /// .Build(); + /// + /// + public sealed class EventProcessorBuilder : IEventProcessorFactory + { + /// + /// The default value for . + /// + public const int DefaultCapacity = 100; + + /// + /// The default value for . + /// + public static readonly TimeSpan DefaultFlushInterval = TimeSpan.FromSeconds(5); + + internal static readonly Uri DefaultBaseUri = new Uri("https://mobile.launchdarkly.com"); + + internal bool _allAttributesPrivate = false; + internal Uri _baseUri = null; + internal int _capacity = DefaultCapacity; + internal TimeSpan _flushInterval = DefaultFlushInterval; + internal bool _inlineUsersInEvents = false; + internal HashSet _privateAttributes = new HashSet(); + internal IEventSender _eventSender = null; // used in testing + + /// + /// Sets whether or not all optional user attributes should be hidden from LaunchDarkly. + /// + /// + /// If this is , all user attribute values (other than the key) will be private, not just + /// the attributes specified in or on a per-user basis with + /// methods. By default, it is . + /// + /// true if all user attributes should be private + /// the builder + public EventProcessorBuilder AllAttributesPrivate(bool allAttributesPrivate) + { + _allAttributesPrivate = allAttributesPrivate; + return this; + } + + /// + /// Sets a custom base URI for the events service. + /// + /// + /// You will only need to change this value in the following cases: + /// + /// + /// + /// You are using the Relay Proxy. + /// Set BaseUri to the base URI of the Relay Proxy instance. + /// + /// + /// + /// + /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. + /// + /// + /// + /// + /// the base URI of the events service; null to use the default + /// the builder + public EventProcessorBuilder BaseUri(Uri baseUri) + { + _baseUri = baseUri; + return this; + } + + /// + /// Sets the capacity of the events buffer. + /// + /// + /// + /// The client buffers up to this many events in memory before flushing. If the capacity is exceeded before + /// the buffer is flushed (see ), events will be discarded. Increasing the + /// capacity means that events are less likely to be discarded, at the cost of consuming more memory. + /// + /// + /// The default value is . A zero or negative value will be changed to the default. + /// + /// + /// the capacity of the event buffer + /// the builder + public EventProcessorBuilder Capacity(int capacity) + { + _capacity = (capacity <= 0) ? DefaultCapacity : capacity; + return this; + } + + // Used only in testing + internal EventProcessorBuilder EventSender(IEventSender eventSender) + { + _eventSender = eventSender; + return this; + } + + /// + /// Sets the interval between flushes of the event buffer. + /// + /// + /// Decreasing the flush interval means that the event buffer is less likely to reach capacity. + /// The default value is . A zero or negative value will be changed to + /// the default. + /// + /// the flush interval + /// the builder + public EventProcessorBuilder FlushInterval(TimeSpan flushInterval) + { + _flushInterval = (flushInterval.CompareTo(TimeSpan.Zero) <= 0) ? + DefaultFlushInterval : flushInterval; + return this; + } + + /// + /// Sets whether to include full user details in every analytics event. + /// + /// + /// The default value is : events will only include the user key, except for one + /// "identify" event that provides the full details for the user. + /// + /// true if you want full user details in each event + /// the builder + public EventProcessorBuilder InlineUsersInEvents(bool inlineUsersInEvents) + { + _inlineUsersInEvents = inlineUsersInEvents; + return this; + } + + /// + /// Marks a set of attribute names as private. + /// + /// + /// Any users sent to LaunchDarkly with this configuration active will have attributes with these + /// names removed. This is in addition to any attributes that were marked as private for an + /// individual user with methods. + /// + /// a set of attributes that will be removed from user data set to LaunchDarkly + /// the builder + /// + public EventProcessorBuilder PrivateAttributes(params UserAttribute[] attributes) + { + foreach (var a in attributes) + { + _privateAttributes.Add(a); + } + return this; + } + + /// + /// Marks a set of attribute names as private. + /// + /// + /// + /// Any users sent to LaunchDarkly with this configuration active will have attributes with these + /// names removed. This is in addition to any attributes that were marked as private for an + /// individual user with methods. + /// + /// + /// Using is preferable to avoid the possibility of + /// misspelling a built-in attribute. + /// + /// + /// a set of names that will be removed from user data set to LaunchDarkly + /// the builder + /// + public EventProcessorBuilder PrivateAttributeNames(params string[] attributes) + { + foreach (var a in attributes) + { + _privateAttributes.Add(UserAttribute.ForName(a)); + } + return this; + } + + /// + public IEventProcessor CreateEventProcessor(LdClientContext context) + { + var uri = _baseUri ?? DefaultBaseUri; + var eventsConfig = new EventsConfiguration + { + AllAttributesPrivate = _allAttributesPrivate, + EventCapacity = _capacity, + EventFlushInterval = _flushInterval, + EventsUri = uri.AddPath(Constants.EVENTS_PATH), + //DiagnosticUri = uri.AddPath("diagnostic"), // no diagnostic events yet + InlineUsersInEvents = _inlineUsersInEvents, + PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), + RetryInterval = TimeSpan.FromSeconds(1) + }; + var logger = context.BaseLogger.SubLogger(LogNames.EventsSubLog); + var eventSender = _eventSender ?? + new DefaultEventSender( + context.HttpProperties, + eventsConfig, + logger + ); + return new DefaultEventProcessorWrapper( + new EventProcessor( + eventsConfig, + eventSender, + null, // no user deduplicator, because the client-side SDK doesn't send index events + null, // diagnostic store would go here, but we haven't implemented diagnostic events + null, + logger, + null + )); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs rename to src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs index a5d7459b..8cfa1c5f 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventProcessorTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Client.Internal.Events +namespace LaunchDarkly.Sdk.Client.Interfaces { /// /// Parameter types for use by implementations. @@ -9,7 +9,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.Events /// functionality. They are provided to allow a custom implementation /// or test fixture to be substituted for the SDK's normal analytics event logic. /// - internal static class EventProcessorTypes + public static class EventProcessorTypes { /// /// Parameters for . diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs new file mode 100644 index 00000000..c24e544c --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs @@ -0,0 +1,65 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for an object that can send or store analytics events. + /// + /// + /// + /// Application code normally does not need to interact with or its + /// related parameter types. They are provided to allow a custom implementation or test fixture to be + /// substituted for the SDK's normal analytics event logic. + /// + /// + /// All of the Record methods must return as soon as possible without waiting for events to be + /// delivered; event delivery is done asynchronously by a background task. + /// + /// + public interface IEventProcessor : IDisposable + { + /// + /// Records the action of evaluating a feature flag. + /// + /// + /// Depending on the feature flag properties and event properties, this may be transmitted to the + /// events service as an individual event, or may only be added into summary data. + /// + /// parameters for an evaluation event + void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e); + + /// + /// Records a set of user properties. + /// + /// parameters for an identify event + void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e); + + /// + /// Records a custom event. + /// + /// parameters for a custom event + void RecordCustomEvent(EventProcessorTypes.CustomEvent e); + + /// + /// Records an alias event. + /// + void RecordAliasEvent(EventProcessorTypes.AliasEvent e); + + /// + /// Puts the component into offline mode if appropriate. + /// + /// true if the SDK has been put offline + void SetOffline(bool offline); + + /// + /// Specifies that any buffered events should be sent as soon as possible, rather than waiting + /// for the next flush interval. + /// + /// + /// This method triggers an asynchronous task, so events still may not be sent until a later + /// until a later time. However, calling will synchronously + /// deliver any events that were not yet delivered prior to shutting down. + /// + void Flush(); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs new file mode 100644 index 00000000..b75f3eff --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs @@ -0,0 +1,17 @@ + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for a factory that creates some implementation of . + /// + public interface IEventProcessorFactory + { + /// + /// Called internally by the SDK to create an implementation instance. Applications do not need + /// to call this method. + /// + /// configuration of the current client instance + /// an IEventProcessor instance + IEventProcessor CreateEventProcessor(LdClientContext context); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index d2ebbd30..70e3625b 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Integrations; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -361,7 +362,7 @@ public interface ILdClient : IDisposable /// When the LaunchDarkly client generates analytics events (from flag evaluations, or from /// or ), they are queued on a worker thread. /// The event thread normally sends all queued events to LaunchDarkly at regular intervals, controlled by the - /// option. Calling triggers a send + /// option. Calling triggers a send /// without waiting for the next interval. /// /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index 896cbc36..5f5ac3a3 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal @@ -25,5 +24,32 @@ public void Dispose() { } public Task Start() => Task.FromResult(true); } + + internal sealed class NullEventProcessorFactory : IEventProcessorFactory + { + internal static NullEventProcessorFactory Instance = new NullEventProcessorFactory(); + + public IEventProcessor CreateEventProcessor(LdClientContext context) => + NullEventProcessor.Instance; + } + + internal sealed class NullEventProcessor : IEventProcessor + { + internal static NullEventProcessor Instance = new NullEventProcessor(); + + public void Dispose() { } + + public void Flush() { } + + public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) { } + + public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) { } + + public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) { } + + public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) { } + + public void SetOffline(bool offline) { } + } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs index 0eb51cf7..466ae9a3 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -1,4 +1,5 @@ -using LaunchDarkly.Sdk.Internal.Events; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal.Events; namespace LaunchDarkly.Sdk.Client.Internal.Events { diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs index 03a8e04e..e66a8004 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs @@ -1,6 +1,6 @@  using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Internal.Events.EventProcessorTypes; +using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client.Internal.Events { diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs deleted file mode 100644 index bc25f7b4..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/IEventProcessor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace LaunchDarkly.Sdk.Client.Internal.Events -{ - internal interface IEventProcessor : IDisposable - { - void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e); - - void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e); - - void RecordCustomEvent(EventProcessorTypes.CustomEvent e); - - void RecordAliasEvent(EventProcessorTypes.AliasEvent e); - - void SetOffline(bool offline); - - void Flush(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index 4d3ed983..d585aa84 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -1,11 +1,7 @@ -using System; -using LaunchDarkly.Logging; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal.DataSources; using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using LaunchDarkly.Sdk.Internal; -using LaunchDarkly.Sdk.Internal.Events; namespace LaunchDarkly.Sdk.Client.Internal { @@ -34,39 +30,6 @@ internal static IConnectivityStateManager CreateConnectivityStateManager(Configu return configuration.ConnectivityStateManager ?? new DefaultConnectivityStateManager(); } - internal static IEventProcessor CreateEventProcessor(Configuration configuration, Logger baseLog) - { - if (configuration.EventProcessor != null) - { - return configuration.EventProcessor; - } - - var log = baseLog.SubLogger(LogNames.EventsSubLog); - var eventsConfig = new EventsConfiguration - { - AllAttributesPrivate = configuration.AllAttributesPrivate, - DiagnosticRecordingInterval = TimeSpan.FromMinutes(15), // TODO - DiagnosticUri = null, - EventCapacity = configuration.EventCapacity, - EventFlushInterval = configuration.EventFlushInterval, - EventsUri = configuration.EventsUri.AddPath(Constants.EVENTS_PATH), - InlineUsersInEvents = configuration.InlineUsersInEvents, - PrivateAttributeNames = configuration.PrivateAttributeNames, - RetryInterval = TimeSpan.FromSeconds(1) - }; - var httpProperties = configuration.HttpProperties; - var eventProcessor = new EventProcessor( - eventsConfig, - new DefaultEventSender(httpProperties, eventsConfig, log), - null, - null, - null, - log, - null - ); - return new DefaultEventProcessorWrapper(eventProcessor); - } - internal static IPersistentStorage CreatePersistentStorage(Configuration configuration, Logger log) { return configuration.PersistentStorage ?? new DefaultPersistentStorage(log); diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 8ba9099b..c583dd45 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -3,7 +3,7 @@ 2.0.0-alpha.1 - netstandard2.0;xamarin.ios10;monoandroid81 + netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 $(BaseTargetFrameworks);net452 $(BaseTargetFrameworks) $(LD_TARGET_FRAMEWORKS) @@ -69,4 +69,9 @@ + + + Code + + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 9bfabb3f..3a72ebbe 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -35,7 +35,7 @@ public sealed class LdClient : ILdClient readonly IBackgroundModeManager _backgroundModeManager; readonly IDeviceInfo deviceInfo; readonly IConnectivityStateManager _connectivityStateManager; - readonly IEventProcessor eventProcessor; + readonly IEventProcessor _eventProcessor; readonly IFlagCacheManager flagCacheManager; internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing readonly IPersistentStorage persister; @@ -164,18 +164,20 @@ public event EventHandler FlagChanged { _log.Debug("Setting online to {0} due to a connectivity change event", networkAvailable); _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result - eventProcessor.SetOffline(!networkAvailable || _connectionManager.ForceOffline); + _eventProcessor.SetOffline(!networkAvailable || _connectionManager.ForceOffline); }; var isConnected = _connectivityStateManager.IsConnected; _connectionManager.SetNetworkEnabled(isConnected); - eventProcessor = Factory.CreateEventProcessor(configuration, _log); - eventProcessor.SetOffline(configuration.Offline || !isConnected); + _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) + .CreateEventProcessor(_context); + _eventProcessor.SetOffline(configuration.Offline || !isConnected); // Send an initial identify event, but only if we weren't explicitly set to be offline + if (!configuration.Offline) { - eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent + _eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { Timestamp = UnixMillisecondTime.Now, User = user @@ -344,7 +346,7 @@ public bool SetOffline(bool value, TimeSpan maxWaitTime) /// public async Task SetOfflineAsync(bool value) { - eventProcessor.SetOffline(value || !_connectionManager.NetworkEnabled); + _eventProcessor.SetOffline(value || !_connectionManager.NetworkEnabled); await _connectionManager.SetForceOffline(value); } @@ -469,10 +471,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => private void SendEvaluationEventIfOnline(EventProcessorTypes.EvaluationEvent e) { - if (!_connectionManager.ForceOffline) - { - eventProcessor.RecordEvaluationEvent(e); - } + EventProcessorIfEnabled().RecordEvaluationEvent(e); } /// @@ -485,32 +484,26 @@ public IDictionary AllFlags() /// public void Track(string eventName, LdValue data, double metricValue) { - if (!_connectionManager.ForceOffline) + EventProcessorIfEnabled().RecordCustomEvent(new EventProcessorTypes.CustomEvent { - eventProcessor.RecordCustomEvent(new EventProcessorTypes.CustomEvent - { - Timestamp = UnixMillisecondTime.Now, - EventKey = eventName, - User = User, - Data = data, - MetricValue = metricValue - }); - } + Timestamp = UnixMillisecondTime.Now, + EventKey = eventName, + User = User, + Data = data, + MetricValue = metricValue + }); } /// public void Track(string eventName, LdValue data) { - if (!_connectionManager.ForceOffline) + EventProcessorIfEnabled().RecordCustomEvent(new EventProcessorTypes.CustomEvent { - eventProcessor.RecordCustomEvent(new EventProcessorTypes.CustomEvent - { - Timestamp = UnixMillisecondTime.Now, - EventKey = eventName, - User = User, - Data = data - }); - } + Timestamp = UnixMillisecondTime.Now, + EventKey = eventName, + User = User, + Data = data + }); } /// @@ -522,7 +515,7 @@ public void Track(string eventName) /// public void Flush() { - eventProcessor.Flush(); // eventProcessor will ignore this if it is offline + _eventProcessor.Flush(); // eventProcessor will ignore this if it is offline } /// @@ -553,22 +546,19 @@ public async Task IdentifyAsync(User user) _user = newUser; }); - if (!_connectionManager.ForceOffline) + EventProcessorIfEnabled().RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { - eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent + Timestamp = UnixMillisecondTime.Now, + User = user + }); + if (oldUser.Anonymous && !newUser.Anonymous && !_config.AutoAliasingOptOut) + { + EventProcessorIfEnabled().RecordAliasEvent(new EventProcessorTypes.AliasEvent { Timestamp = UnixMillisecondTime.Now, - User = user + User = user, + PreviousUser = oldUser }); - if (oldUser.Anonymous && !newUser.Anonymous && !_config.AutoAliasingOptOut) - { - eventProcessor.RecordAliasEvent(new EventProcessorTypes.AliasEvent - { - Timestamp = UnixMillisecondTime.Now, - User = user, - PreviousUser = oldUser - }); - } } return await _connectionManager.SetDataSourceConstructor( @@ -588,14 +578,11 @@ public void Alias(User user, User previousUser) { throw new ArgumentNullException(nameof(previousUser)); } - if (!_connectionManager.ForceOffline) + EventProcessorIfEnabled().RecordAliasEvent(new EventProcessorTypes.AliasEvent { - eventProcessor.RecordAliasEvent(new EventProcessorTypes.AliasEvent - { - User = user, - PreviousUser = previousUser - }); - } + User = user, + PreviousUser = previousUser + }); } User DecorateUser(User user) @@ -656,7 +643,7 @@ void Dispose(bool disposing) _backgroundModeManager.BackgroundModeChanged -= OnBackgroundModeChanged; _connectionManager.Dispose(); - eventProcessor.Dispose(); + _eventProcessor.Dispose(); // Reset the static Instance to null *if* it was referring to this instance DetachInstance(); @@ -703,6 +690,12 @@ await _connectionManager.SetDataSourceConstructor( ); } + // Returns our configured event processor (which might be the null implementation, if configured + // with NoEvents)-- or, a stub if we have been explicitly put offline. This way, during times + // when the application does not want any network activity, we won't bother buffering events. + internal IEventProcessor EventProcessorIfEnabled() => + Offline ? ComponentsImpl.NullEventProcessor.Instance : _eventProcessor; + internal Func MakeDataSourceConstructor(User user, bool background) { return () => _dataSourceFactory.CreateDataSource( diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs b/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs new file mode 100644 index 00000000..18f0b48f --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs @@ -0,0 +1,283 @@ +using System; +using Xunit; + +// THIS CODE WILL BE MOVED into the dotnet-test-helpers project where it can be shared + +namespace LaunchDarkly.TestHelpers +{ + /// + /// Factories for helper classes that provide useful test patterns for builder types. + /// + /// + /// + /// These helpers make it easier to provide thorough test coverage of builder types. + /// It is easy when implementing builders to make basic mistakes like not setting the + /// right property in a setter, not enforcing desired constraints, or not copying all + /// the properties in a copy constructor. + /// + /// + /// The general pattern consists of creating a generic helper for the builder type and + /// the type that it builds, then creating a property helper for each settable property + /// and performing standard assertions with it. Example: + /// + ///

+    ///     // This assumes there is a type MyBuilder whose Build method creates an
+    ///     // instance of MyType, with properties Height and Weight.
+    ///     
+    ///     var tester = BuilderBehavior.For(() => new MyBuilder(), b => b.Build());
+    ///
+    ///     var height = tester.Property(x => x.Height, b => (b, value) => b.Height(value));
+    ///     height.AssertDefault(DefaultHeight);
+    ///     height.AssertCanSet(72);
+    ///     height.AssertSetIsChangedTo(-1, 0); // setter should enforce minimum height of 0
+    ///
+    ///     var weight = tester.Property(x => x.Weight, b => (b, value) => b.Weight(value));
+    ///     weight.AssertDefault(DefaultWeight);
+    ///     weight.AssertCanSet(200);
+    ///     weight.AssertSetIsChangedTo(-1, 0); // setter should enforce minimum weight of 0
+    /// 
+ /// + /// It uses Xunit assertion methods. + /// + ///
+ public static class BuilderBehavior + { + /// + /// Provides a generic for testing + /// methods of a builder against properties of the type it builds. + /// + /// the builder type + /// the type that it builds + /// function that constructs a + /// function that creates a + /// from a + /// a instance + public static BuildTester For( + Func constructor, Func buildMethod) => + new BuildTester(constructor, buildMethod, null); + + /// + /// Provides a generic for testing + /// methods of a builder against the builder's own internal state. + /// + /// + /// This can be used in cases where it is not feasible for the test code to actually + /// call the builder's build method, for instance if it has unwanted side effects. + /// + /// the builder type + /// function that constructs a + /// an instance + // Use this when we want to test the builder's internal state directly, without + // calling Build - i.e. if the object is difficult to inspect after it's built. + public static InternalStateTester For(Func constructor) => + new InternalStateTester(constructor); + + /// + /// Helper class that provides useful test patterns for a builder type and the + /// type that it builds. + /// + /// + /// Create instances of this class with + /// . + /// + /// the builder type + /// the type that it builds + public sealed class BuildTester + { + private readonly Func _constructor; + internal readonly Func _buildMethod; + internal readonly Func _copyConstructor; + + internal BuildTester(Func constructor, + Func buildMethod, + Func copyConstructor + ) + { + _constructor = constructor; + _buildMethod = buildMethod; + _copyConstructor = copyConstructor; + } + + /// + /// Creates a helper for testing a specific property of the builder. + /// + /// type of the property + /// function that gets that property from the built object + /// function that sets the property in the builder + /// + public IPropertyAssertions Property( + Func getter, + Action builderSetter + ) => + new BuildTesterProperty( + this, getter, builderSetter); + + /// + /// Creates an instance of the builder. + /// + /// a new instance + public TBuilder New() => _constructor(); + + /// + /// Adds the ability to test the builder's copy constructor. + /// + /// + /// The effect of this is that all + /// assertions created from the resulting helper will also verify that copying + /// the builder also copies the value of this property. + /// + /// function that should create a new builder with an + /// identical state to the existing one + /// a copy of the BuilderTestHelper with this additional behavior + public BuildTester WithCopyConstructor( + Func copyConstructor + ) => + new BuildTester(_constructor, _buildMethod, copyConstructor); + } + + /// + /// Similar to , but instead of testing the values of + /// properties in the built object, it inspects the builder directly. + /// + /// + /// Create instances of this class with . + /// + /// the builder type + public class InternalStateTester + { + private readonly Func _constructor; + + internal InternalStateTester(Func constructor) + { + _constructor = constructor; + } + + /// + /// Creates a helper for testing a specific property of the builder. + /// + /// type of the property + /// function that gets that property from the builder's internal state + /// function that sets the property in the builder + /// + public IPropertyAssertions Property( + Func builderGetter, + Action builderSetter + ) => + new InternalStateTesterProperty(this, + builderGetter, builderSetter); + + /// + /// Creates an instance of the builder. + /// + /// a new instance + public TBuilder New() => _constructor(); + } + + /// + /// Assertions provided by the property-specific helpers. + /// + /// type of the property + public interface IPropertyAssertions + { + /// + /// Asserts that the property has the expected value when it has not been set. + /// + /// the expected value + void AssertDefault(TValue defaultValue); + + /// + /// Asserts that calling the setter for a specific value causes the property + /// to have that value. + /// + /// the expected value + void AssertCanSet(TValue newValue); + + /// + /// Asserts that calling the setter for a specific value causes the property + /// to have another specific value for the corresponding property. + /// + /// the value to pass to the setter + /// the expected result value + void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue); + } + + internal class BuildTesterProperty : IPropertyAssertions + { + private readonly BuildTester _owner; + private readonly Func _getter; + private readonly Action _builderSetter; + + internal BuildTesterProperty(BuildTester owner, + Func getter, + Action builderSetter) + { + _owner = owner; + _getter = getter; + _builderSetter = builderSetter; + } + + public void AssertDefault(TValue defaultValue) + { + var b = _owner.New(); + AssertValue(b, defaultValue); + } + + public void AssertCanSet(TValue newValue) + { + AssertSetIsChangedTo(newValue, newValue); + } + + public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) + { + var b = _owner.New(); + _builderSetter(b, attemptedValue); + AssertValue(b, resultingValue); + } + + private void AssertValue(TBuilder b, TValue v) + { + var o = _owner._buildMethod(b); + Assert.Equal(v, _getter(o)); + if (_owner._copyConstructor != null) + { + var b1 = _owner._copyConstructor(o); + var o1 = _owner._buildMethod(b1); + Assert.Equal(v, _getter(o1)); + } + } + } + + internal class InternalStateTesterProperty : IPropertyAssertions + { + private readonly InternalStateTester _owner; + private readonly Func _builderGetter; + private readonly Action _builderSetter; + + internal InternalStateTesterProperty(InternalStateTester owner, + Func builderGetter, + Action builderSetter) + { + _owner = owner; + _builderGetter = builderGetter; + _builderSetter = builderSetter; + } + + public void AssertDefault(TValue defaultValue) + { + Assert.Equal(defaultValue, _builderGetter(_owner.New())); + } + + public void AssertCanSet(TValue newValue) + { + AssertSetIsChangedTo(newValue, newValue); + } + + public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) + { + var b = _owner.New(); + _builderSetter(b, attemptedValue); + Assert.Equal(resultingValue, _builderGetter(b)); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 01b78307..fefefe59 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Immutable; using System.Net.Http; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -10,8 +10,8 @@ namespace LaunchDarkly.Sdk.Client { public class ConfigurationTest : BaseTest { - private readonly BuilderTestUtil _tester = - BuilderTestUtil.For(() => Configuration.Builder(mobileKey), b => b.Build()) + private readonly BuilderBehavior.BuildTester _tester = + BuilderBehavior.For(() => Configuration.Builder(mobileKey), b => b.Build()) .WithCopyConstructor(c => Configuration.Builder(c)); const string mobileKey = "any-key"; @@ -49,40 +49,27 @@ public void DataSource() } [Fact] - public void AllAttributesPrivate() + public void EnableBackgroundUpdating() { - var prop = _tester.Property(b => b.AllAttributesPrivate, (b, v) => b.AllAttributesPrivate(v)); - prop.AssertDefault(false); - prop.AssertCanSet(true); - } - - [Fact] - public void EventCapacity() - { - var prop = _tester.Property(b => b.EventCapacity, (b, v) => b.EventCapacity(v)); - prop.AssertDefault(Configuration.DefaultEventCapacity); - prop.AssertCanSet(1); - prop.AssertSetIsChangedTo(0, Configuration.DefaultEventCapacity); - prop.AssertSetIsChangedTo(-1, Configuration.DefaultEventCapacity); + var prop = _tester.Property(c => c.EnableBackgroundUpdating, (b, v) => b.EnableBackgroundUpdating(v)); + prop.AssertDefault(true); + prop.AssertCanSet(false); } [Fact] - public void EventsUri() + public void EvaluationReasons() { - var prop = _tester.Property(b => b.EventsUri, (b, v) => b.EventsUri(v)); - prop.AssertDefault(Configuration.DefaultEventsUri); - prop.AssertCanSet(new Uri("http://x")); - prop.AssertSetIsChangedTo(null, Configuration.DefaultEventsUri); + var prop = _tester.Property(c => c.EvaluationReasons, (b, v) => b.EvaluationReasons(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); } [Fact] - public void FlushInterval() + public void Events() { - var prop = _tester.Property(b => b.EventFlushInterval, (b, v) => b.EventFlushInterval(v)); - prop.AssertDefault(Configuration.DefaultEventFlushInterval); - prop.AssertCanSet(TimeSpan.FromMinutes(7)); - prop.AssertSetIsChangedTo(TimeSpan.Zero, Configuration.DefaultEventFlushInterval); - prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(-1), Configuration.DefaultEventFlushInterval); + var prop = _tester.Property(c => c.EventProcessorFactory, (b, v) => b.Events(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new ComponentsImpl.NullEventProcessorFactory()); } [Fact] @@ -93,14 +80,6 @@ public void HttpMessageHandler() prop.AssertCanSet(new HttpClientHandler()); } - [Fact] - public void InlineUsersInEvents() - { - var prop = _tester.Property(b => b.InlineUsersInEvents, (b, v) => b.InlineUsersInEvents(v)); - prop.AssertDefault(false); - prop.AssertCanSet(true); - } - [Fact] public void Logging() { @@ -134,15 +113,11 @@ public void Offline() } [Fact] - public void PrivateAttributes() + public void PersistFlagValues() { - var b = _tester.New(); - Assert.Null(b.Build().PrivateAttributeNames); - b.PrivateAttribute(UserAttribute.Name); - b.PrivateAttribute(UserAttribute.Email); - b.PrivateAttribute(UserAttribute.ForName("other")); - Assert.Equal(ImmutableHashSet.Create( - UserAttribute.Name, UserAttribute.Email, UserAttribute.ForName("other")), b.Build().PrivateAttributeNames); + var prop = _tester.Property(c => c.PersistFlagValues, (b, v) => b.PersistFlagValues(v)); + prop.AssertDefault(true); + prop.AssertCanSet(false); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs new file mode 100644 index 00000000..985c691d --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class EventProcessorBuilderTest : BaseTest + { + private readonly BuilderBehavior.InternalStateTester _tester = + BuilderBehavior.For(Components.SendEvents); + + public EventProcessorBuilderTest(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void AllAttributesPrivate() + { + var prop = _tester.Property(b => b._allAttributesPrivate, (b, v) => b.AllAttributesPrivate(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } + + [Fact] + public void EventCapacity() + { + var prop = _tester.Property(b => b._capacity, (b, v) => b.Capacity(v)); + prop.AssertDefault(EventProcessorBuilder.DefaultCapacity); + prop.AssertCanSet(1); + prop.AssertSetIsChangedTo(0, EventProcessorBuilder.DefaultCapacity); + prop.AssertSetIsChangedTo(-1, EventProcessorBuilder.DefaultCapacity); + } + + [Fact] + public void EventsUri() + { + var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new Uri("http://x")); + } + + [Fact] + public void FlushInterval() + { + var prop = _tester.Property(b => b._flushInterval, (b, v) => b.FlushInterval(v)); + prop.AssertDefault(EventProcessorBuilder.DefaultFlushInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(7)); + prop.AssertSetIsChangedTo(TimeSpan.Zero, EventProcessorBuilder.DefaultFlushInterval); + prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(-1), EventProcessorBuilder.DefaultFlushInterval); + } + + [Fact] + public void InlineUsersInEvents() + { + var prop = _tester.Property(b => b._inlineUsersInEvents, (b, v) => b.InlineUsersInEvents(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } + + [Fact] + public void PrivateAttributes() + { + var b = _tester.New(); + Assert.Empty(b._privateAttributes); + b.PrivateAttributes(UserAttribute.Name); + b.PrivateAttributes(UserAttribute.Email, UserAttribute.ForName("other")); + b.PrivateAttributeNames("country"); + Assert.Equal( + new HashSet + { + UserAttribute.Name, UserAttribute.Email, UserAttribute.Country, UserAttribute.ForName("other") + }, + b._privateAttributes); + } + } +} \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs index 76d0a579..ea757975 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs @@ -1,12 +1,13 @@ using System; +using LaunchDarkly.TestHelpers; using Xunit; namespace LaunchDarkly.Sdk.Client.Integrations { public class PollingDataSourceBuilderTest { - private readonly BuilderInternalTestUtil _tester = - BuilderTestUtil.For(Components.PollingDataSource); + private readonly BuilderBehavior.InternalStateTester _tester = + BuilderBehavior.For(Components.PollingDataSource); [Fact] public void BackgroundPollInterval() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs index 656ae502..3b07f67e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs @@ -1,12 +1,13 @@ using System; +using LaunchDarkly.TestHelpers; using Xunit; namespace LaunchDarkly.Sdk.Client.Integrations { public class StreamingDataSourceBuilderTest { - private readonly BuilderInternalTestUtil _tester = - BuilderTestUtil.For(Components.StreamingDataSource); + private readonly BuilderBehavior.InternalStateTester _tester = + BuilderBehavior.For(Components.StreamingDataSource); [Fact] public void BackgroundPollInterval() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index e64587e1..e7d86ac4 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -266,7 +266,7 @@ string expectedPath { var config = Configuration.Builder(_mobileKey) .DataSource(MockPollingProcessor.Factory("{}")) - .EventsUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) + .Events(Components.SendEvents().BaseUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath))) .PersistFlagValues(false) .Build(); @@ -480,7 +480,7 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) private Configuration BaseConfig(Func extraConfig = null) { var builderInternal = Configuration.Builder(_mobileKey) - .EventProcessor(new MockEventProcessor()); + .Events(new SingleEventProcessorFactory(new MockEventProcessor())); builderInternal .Logging(testLogging) .PersistFlagValues(false); // unless we're specifically testing flag caching, this helps to prevent test state contamination diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 5644cbd0..3f41bb7d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -1,8 +1,9 @@ using System; +using LaunchDarkly.Sdk.Client.Interfaces; using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Internal.Events.EventProcessorTypes; +using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client { @@ -10,13 +11,16 @@ public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); private MockEventProcessor eventProcessor = new MockEventProcessor(); + private IEventProcessorFactory _factory; - public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { } + public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { + _factory = new SingleEventProcessorFactory(eventProcessor); + } public LdClient MakeClient(User user, string flagsJson) { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); - config.EventProcessor(eventProcessor).Logging(testLogging); + config.Events(_factory).Logging(testLogging); return TestUtil.CreateClient(config.Build(), user); } @@ -138,7 +142,7 @@ public void IdentifyDoesNotSendAliasEventIfOptedOUt() User newUser = User.WithKey("real-key"); var config = TestUtil.ConfigWithFlagsJson(oldUser, "appkey", "{}"); - config.EventProcessor(eventProcessor).Logging(testLogging); + config.Events(_factory).Logging(testLogging); config.AutoAliasingOptOut(true); using (LdClient client = TestUtil.CreateClient(config.Build(), oldUser)) @@ -270,7 +274,7 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) - .EventProcessor(eventProcessor) + .Events(_factory) .Logging(testLogging); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) @@ -378,7 +382,7 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) - .EventProcessor(eventProcessor) + .Events(_factory) .Logging(testLogging); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 35093d53..62bff46f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -467,7 +467,7 @@ public void EventProcessorIsOnlineByDefault() { var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") - .EventProcessor(eventProcessor) + .Events(new SingleEventProcessorFactory(eventProcessor)) .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) @@ -483,7 +483,7 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .ConnectivityStateManager(connectivityStateManager) - .EventProcessor(eventProcessor) + .Events(new SingleEventProcessorFactory(eventProcessor)) .Offline(true) .Logging(testLogging) .Build(); @@ -513,7 +513,7 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() var eventProcessor = new MockEventProcessor(); var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") .ConnectivityStateManager(connectivityStateManager) - .EventProcessor(eventProcessor) + .Events(new SingleEventProcessorFactory(eventProcessor)) .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index e8c4e1bd..b246d900 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -100,6 +100,18 @@ public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) => Events.Add(e); } + internal class SingleEventProcessorFactory : IEventProcessorFactory + { + private readonly IEventProcessor _instance; + + public SingleEventProcessorFactory(IEventProcessor instance) + { + _instance = instance; + } + + public IEventProcessor CreateEventProcessor(LdClientContext context) => _instance; + } + internal class MockFeatureFlagRequestor : IFeatureFlagRequestor { private readonly string _jsonFlags; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index d6b1ca4a..36bfa929 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; @@ -19,6 +20,8 @@ public static class TestUtil private static ThreadLocal InClientLock = new ThreadLocal(); + public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key")); + public static T WithClientLock(Func f) { // This cumbersome combination of a ThreadLocal and a SemaphoreSlim is simply because 1. we have to use @@ -144,7 +147,7 @@ internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKe return Configuration.Builder(appKey) .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .EventProcessor(new MockEventProcessor()) + .Events(new SingleEventProcessorFactory(new MockEventProcessor())) .DataSource(MockPollingProcessor.Factory(null)) .PersistentStorage(new MockPersistentStorage()) .DeviceInfo(new MockDeviceInfo()); From 22c49bc35df8c5256fc21580fe28bb417c401b53 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 13 Sep 2021 10:09:40 -0700 Subject: [PATCH 357/499] (#6) scoped configuration for HTTP (#126) --- src/LaunchDarkly.ClientSdk/Components.cs | 32 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 54 +--- .../ConfigurationBuilder.cs | 78 ++--- .../Integrations/EventProcessorBuilder.cs | 2 +- .../Integrations/HttpConfigurationBuilder.cs | 291 ++++++++++++++++++ .../LoggingConfigurationBuilder.cs | 10 +- .../Integrations/PollingDataSourceBuilder.cs | 3 +- .../StreamingDataSourceBuilder.cs | 6 +- .../Interfaces/BasicConfiguration.cs | 23 ++ .../Interfaces/HttpConfiguration.cs | 200 ++++++++++++ .../ILoggingConfigurationFactory.cs | 16 - .../Interfaces/LdClientContext.cs | 20 +- .../DataSources/FeatureFlagRequestor.cs | 10 +- .../DataSources/StreamingDataSource.cs | 7 +- .../LaunchDarkly.ClientSdk.csproj | 6 +- .../ConfigurationTest.cs | 13 +- .../HttpConfigurationBuilderTest.cs | 123 ++++++++ .../DataSources/FeatureFlagRequestorTests.cs | 99 +++--- .../DataSources/StreamingDataSourceTest.cs | 3 +- 19 files changed, 777 insertions(+), 219 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/ILoggingConfigurationFactory.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index cf7b7c29..6eb0c42b 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -12,21 +12,41 @@ namespace LaunchDarkly.Sdk.Client /// Some of the configuration options in affect the entire SDK, /// but others are specific to one area of functionality, such as how the SDK receives feature flag /// updates or processes analytics events. For the latter, the standard way to specify a configuration - /// is to call one of the static methods in (such as ), + /// is to call one of the static methods in (such as ), /// apply any desired configuration change to the object that that method returns (such as - /// ), and then use the + /// ), and then use the /// corresponding method in (such as - /// ) to use that + /// ) to use that /// configured component in the SDK. /// public static class Components { + /// + /// Returns a configuration builder for the SDK's networking configuration. + /// + /// + /// Passing this to applies this + /// configuration to all HTTP/HTTPS requests made by the SDK. + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .Http( + /// Components.HttpConfiguration() + /// .ConnectTimeout(TimeSpan.FromMilliseconds(3000)) + /// ) + /// .Build(); + /// + /// + /// a builder + public static HttpConfigurationBuilder HttpConfiguration() => new HttpConfigurationBuilder(); + /// /// Returns a configuration builder for the SDK's logging configuration. /// /// /// - /// Passing this to , + /// Passing this to , /// after setting any desired properties on the builder, applies this configuration to the SDK. /// /// @@ -45,7 +65,7 @@ public static class Components ///
/// /// a configurable factory object - /// + /// /// /// public static LoggingConfigurationBuilder Logging() => @@ -82,7 +102,7 @@ public static LoggingConfigurationBuilder Logging() => /// /// an ILogAdapter for the desired logging implementation /// a configurable factory object - /// + /// /// /// /// diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 93942865..d3b5cdd6 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Immutable; -using System.Net.Http; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using LaunchDarkly.Sdk.Internal; -using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client { @@ -54,11 +49,6 @@ public sealed class Configuration /// public bool AutoAliasingOptOut { get; } - /// - /// The connection timeout to the LaunchDarkly server. - /// - public TimeSpan ConnectionTimeout { get; } - ///
/// A factory object that creates an implementation of , which will /// receive feature flag data. @@ -93,11 +83,14 @@ public sealed class Configuration public IEventProcessorFactory EventProcessorFactory { get; } /// - /// The object to be used for sending HTTP requests, if a specific implementation is desired. + /// HTTP configuration properties for the SDK. /// - public HttpMessageHandler HttpMessageHandler { get; } + public HttpConfigurationBuilder HttpConfigurationBuilder { get; } - internal ILoggingConfigurationFactory LoggingConfigurationFactory { get; } + /// + /// Logging configuration properties for the SDK. + /// + public LoggingConfigurationBuilder LoggingConfigurationBuilder { get; } /// /// The key for your LaunchDarkly environment. @@ -121,26 +114,6 @@ public sealed class Configuration /// public bool PersistFlagValues { get; } - /// - /// The timeout when reading data from the streaming connection. - /// - public TimeSpan ReadTimeout { get; } - - /// - /// Whether to use the HTTP REPORT method for feature flag requests. - /// - /// - /// By default, polling and streaming connections are made with the GET method, witht the user data - /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body instead. - /// However, some network gateways do not support REPORT. - /// - internal bool UseReport { get; } - // UseReport is currently disabled due to Android HTTP issues (ch47341), but it's still implemented internally - - internal static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromMinutes(5); - internal static readonly TimeSpan DefaultReconnectTime = TimeSpan.FromSeconds(1); - internal static readonly TimeSpan DefaultConnectionTimeout = TimeSpan.FromSeconds(10); - /// /// Creates a configuration with all parameters set to the default. /// @@ -192,19 +165,15 @@ public static ConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { AutoAliasingOptOut = builder._autoAliasingOptOut; - ConnectionTimeout = builder._connectionTimeout; DataSourceFactory = builder._dataSourceFactory; EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; EventProcessorFactory = builder._eventProcessorFactory; - HttpMessageHandler = object.ReferenceEquals(builder._httpMessageHandler, ConfigurationBuilder.DefaultHttpMessageHandlerInstance) ? - PlatformSpecific.Http.CreateHttpMessageHandler(builder._connectionTimeout, builder._readTimeout) : - builder._httpMessageHandler; - LoggingConfigurationFactory = builder._loggingConfigurationFactory; + HttpConfigurationBuilder = builder._httpConfigurationBuilder; + LoggingConfigurationBuilder = builder._loggingConfigurationBuilder; MobileKey = builder._mobileKey; Offline = builder._offline; PersistFlagValues = builder._persistFlagValues; - UseReport = builder._useReport; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; @@ -213,12 +182,5 @@ internal Configuration(ConfigurationBuilder builder) FlagChangedEventManager = builder._flagChangedEventManager; PersistentStorage = builder._persistentStorage; } - - internal HttpProperties HttpProperties => HttpProperties.Default - .WithAuthorizationKey(this.MobileKey) - .WithConnectTimeout(this.ConnectionTimeout) - .WithHttpMessageHandlerFactory(_ => this.HttpMessageHandler) - .WithReadTimeout(this.ReadTimeout) - .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))); } } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index cca120d5..2f57fbc7 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -34,18 +34,15 @@ public sealed class ConfigurationBuilder internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); internal bool _autoAliasingOptOut = false; - internal TimeSpan _connectionTimeout = Configuration.DefaultConnectionTimeout; internal IDataSourceFactory _dataSourceFactory = null; internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; internal IEventProcessorFactory _eventProcessorFactory = null; - internal HttpMessageHandler _httpMessageHandler = DefaultHttpMessageHandlerInstance; - internal ILoggingConfigurationFactory _loggingConfigurationFactory = null; + internal HttpConfigurationBuilder _httpConfigurationBuilder = null; + internal LoggingConfigurationBuilder _loggingConfigurationBuilder = null; internal string _mobileKey; internal bool _offline = false; internal bool _persistFlagValues = true; - internal TimeSpan _readTimeout = Configuration.DefaultReadTimeout; - internal bool _useReport = false; // Internal properties only settable for testing internal IBackgroundModeManager _backgroundModeManager; @@ -63,18 +60,15 @@ internal ConfigurationBuilder(string mobileKey) internal ConfigurationBuilder(Configuration copyFrom) { _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; - _connectionTimeout = copyFrom.ConnectionTimeout; _dataSourceFactory = copyFrom.DataSourceFactory; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; _eventProcessorFactory = copyFrom.EventProcessorFactory; - _httpMessageHandler = copyFrom.HttpMessageHandler; - _loggingConfigurationFactory = copyFrom.LoggingConfigurationFactory; + _httpConfigurationBuilder = copyFrom.HttpConfigurationBuilder; + _loggingConfigurationBuilder = copyFrom.LoggingConfigurationBuilder; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistFlagValues = copyFrom.PersistFlagValues; - _readTimeout = copyFrom.ReadTimeout; - _useReport = copyFrom.UseReport; } /// @@ -107,20 +101,6 @@ public ConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) return this; } - /// - /// Sets the connection timeout for all HTTP requests. - /// - /// - /// The default value is 10 seconds. - /// - /// the connection timeout - /// the same builder - public ConfigurationBuilder ConnectionTimeout(TimeSpan connectionTimeout) - { - _connectionTimeout = connectionTimeout; - return this; - } - /// /// Sets the implementation of the component that receives feature flag data from LaunchDarkly, /// using a factory object. @@ -199,22 +179,15 @@ public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) } /// - /// Sets the object to be used for sending HTTP requests, if a specific implementation is desired. + /// Sets the SDK's networking configuration, using a configuration builder obtained from + /// . The builder has methods for setting + /// individual HTTP-related properties. /// - /// - /// This is exposed mainly for testing purposes; you should not normally need to change it. The default - /// value is an , but if you do not change this value, - /// on mobile platforms it will be replaced by the appropriate native HTTP handler for the current - /// current platform, if any (e.g. Xamarin.Android.Net.AndroidClientHandler). If you set it - /// explicitly to , the SDK will call the default - /// constructor without specifying a handler, which may or may not result in using a native HTTP handler - /// (depending on your application configuration). - /// - /// the to use + /// a builder for HTTP configuration /// the same builder - public ConfigurationBuilder HttpMessageHandler(HttpMessageHandler httpMessageHandler) + public ConfigurationBuilder Http(HttpConfigurationBuilder httpConfigurationBuilder) { - _httpMessageHandler = httpMessageHandler; + _httpConfigurationBuilder = httpConfigurationBuilder; return this; } @@ -242,15 +215,14 @@ public ConfigurationBuilder Logging(ILogAdapter logAdapter) => Logging(Components.Logging(logAdapter)); /// - /// Sets the SDK's logging configuration, using a factory object. + /// Sets the SDK's logging configuration, using a configuration builder obtained from. + /// . /// /// /// - /// This object is normally a configuration builder obtained from - /// which has methods for setting individual logging-related properties. As a shortcut for disabling - /// logging, you may use instead. If all you want to do is to set - /// the basic logging destination, and you do not need to set other logging properties, you can use - /// instead. + /// As a shortcut for disabling logging, you may use instead. + /// If all you want to do is to set the basic logging destination, and you do not need to set other + /// logging properties, you can use instead. /// /// /// For more about how logging works in the SDK, see the LaunchDarkly @@ -262,15 +234,15 @@ public ConfigurationBuilder Logging(ILogAdapter logAdapter) => /// .Logging(Components.Logging().Level(LogLevel.Warn))) /// .Build(); /// - /// the factory object + /// the builder object /// the same builder /// /// /// /// - public ConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) + public ConfigurationBuilder Logging(LoggingConfigurationBuilder loggingConfigurationBuilder) { - _loggingConfigurationFactory = loggingConfigurationFactory; + _loggingConfigurationBuilder = loggingConfigurationBuilder; return this; } @@ -314,20 +286,6 @@ public ConfigurationBuilder PersistFlagValues(bool persistFlagValues) return this; } - /// - /// Sets the timeout when reading data from the streaming connection. - /// - /// - /// The default value is 5 minutes. - /// - /// the read timeout - /// the same builder - public ConfigurationBuilder ReadTimeout(TimeSpan readTimeout) - { - _readTimeout = readTimeout; - return this; - } - // The following properties are internal and settable only for testing. internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backgroundModeManager) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 695690cd..9a5bd159 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -215,7 +215,7 @@ public IEventProcessor CreateEventProcessor(LdClientContext context) var logger = context.BaseLogger.SubLogger(LogNames.EventsSubLog); var eventSender = _eventSender ?? new DefaultEventSender( - context.HttpProperties, + context.Http.HttpProperties, eventsConfig, logger ); diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs new file mode 100644 index 00000000..f791bbe8 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring the SDK's networking behavior. + /// + /// + /// + /// If you want to set non-default values for any of these properties, create a builder with + /// , change its properties with the methods of this class, and + /// pass it to : + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .Http( + /// Components.HttpConfiguration() + /// .ConnectTimeout(TimeSpan.FromMilliseconds(3000)) + /// ) + /// .Build(); + /// + /// + /// + public sealed class HttpConfigurationBuilder + { + /// + /// The default value for : 10 seconds. + /// + public static readonly TimeSpan DefaultConnectTimeout = TimeSpan.FromSeconds(10); + // deliberately longer than the server-side SDK's default connection timeout + + /// + /// The default value for : 10 seconds. + /// + public static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromSeconds(10); + + /// + /// The default value for : 10 seconds. + /// + public static readonly TimeSpan DefaultResponseStartTimeout = TimeSpan.FromSeconds(10); + + internal TimeSpan _connectTimeout = DefaultConnectTimeout; + internal List> _customHeaders = new List>(); + internal HttpMessageHandler _messageHandler = null; + internal IWebProxy _proxy = null; + internal TimeSpan _readTimeout = DefaultReadTimeout; + internal TimeSpan _responseStartTimeout = DefaultResponseStartTimeout; + internal string _wrapperName = null; + internal string _wrapperVersion = null; + internal bool _useReport = false; + + /// + /// Sets the network connection timeout. + /// + /// + /// + /// This is the time allowed for the underlying HTTP client to connect to the + /// LaunchDarkly server, for any individual network connection. + /// + /// + /// It is not the same as the timeout parameter to , + /// which limits the time for initializing the SDK regardless of how many individual HTTP requests + /// are done in that time. + /// + /// + /// Not all .NET platforms support setting a connection timeout. It is supported in + /// .NET Core 2.1+, .NET 5+, and Xamarin Android, but not in Xamarin iOS. On platforms + /// where it is not supported, only will be used. + /// + /// + /// Also, since this is implemented (on supported platforms) as part of the standard + /// HttpMessageHandler implementation for those platforms, if you have specified + /// some other HTTP handler implementation with , + /// the here will be ignored. + /// + /// + /// the timeout + /// the builder + /// + public HttpConfigurationBuilder ConnectTimeout(TimeSpan connectTimeout) + { + _connectTimeout = connectTimeout; + return this; + } + + /// + /// Specifies a custom HTTP header that should be added to all SDK requests. + /// + /// + /// This may be helpful if you are using a gateway or proxy server that requires a specific header in + /// requests. You may add any number of headers. + /// + /// the header name + /// the header value + /// the builder + public HttpConfigurationBuilder CustomHeader(string name, string value) + { + _customHeaders.Add(new KeyValuePair(name, value)); + return this; + } + + /// + /// Specifies a custom HTTP message handler implementation. + /// + /// + /// This is mainly useful for testing, to cause the SDK to use custom logic instead of actual HTTP requests, + /// but can also be used to customize HTTP behavior on platforms where the default handler is not optimal. + /// The default is the usual native HTTP handler for the current platform, if any (for instance, + /// Xamarin.Android.Net.AndroidClientHandler), or else . + /// + /// the message handler, or null to use the platform's default handler + /// the builder + public HttpConfigurationBuilder MessageHandler(HttpMessageHandler messageHandler) + { + _messageHandler = messageHandler; + return this; + } + + /// + /// Sets an HTTP proxy for making connections to LaunchDarkly. + /// + /// + /// This is ignored if you have specified a custom message handler with , + /// since proxy behavior is implemented by the message handler. + /// + /// + /// + /// // Example of using an HTTP proxy with basic authentication + /// + /// var proxyUri = new Uri("http://my-proxy-host:8080"); + /// var proxy = new System.Net.WebProxy(proxyUri); + /// var credentials = new System.Net.CredentialCache(); + /// credentials.Add(proxyUri, "Basic", + /// new System.Net.NetworkCredential("username", "password")); + /// proxy.Credentials = credentials; + /// + /// var config = Configuration.Builder("my-sdk-key") + /// .Http( + /// Components.HttpConfiguration().Proxy(proxy) + /// ) + /// .Build(); + /// + /// + /// any implementation of System.Net.IWebProxy + /// the builder + public HttpConfigurationBuilder Proxy(IWebProxy proxy) + { + _proxy = proxy; + return this; + } + + /// + /// Sets the socket read timeout. + /// + /// + /// + /// Sets the socket timeout. This is the amount of time without receiving data on a connection that the + /// SDK will tolerate before signaling an error. This does not apply to the streaming connection + /// used by , which has its own non-configurable read timeout + /// based on the expected behavior of the LaunchDarkly streaming service. + /// + /// + /// Not all .NET platforms support setting a sockettimeout. It is supported in + /// .NET Core 2.1+, .NET 5+, and Xamarin Android, but not in Xamarin iOS. On platforms + /// where it is not supported, this parameter is ignored. + /// + /// + /// the socket read timeout + /// the builder + public HttpConfigurationBuilder ReadTimeout(TimeSpan readTimeout) + { + _readTimeout = readTimeout; + return this; + } + + /// + /// Sets the maximum amount of time to wait for the beginning of an HTTP response. + /// + /// + /// + /// This limits how long the SDK will wait from the time it begins trying to make a + /// network connection for an individual HTTP request to the time it starts receiving + /// any data from the server. It is equivalent to the Timeout property in + /// HttpClient. + /// + /// + /// It is not the same as the timeout parameter to, + /// which limits the time for initializing the SDK regardless of how many individual HTTP requests + /// are done in that time. + /// + /// + /// the timeout + /// the builder + /// + public HttpConfigurationBuilder ResponseStartTimeout(TimeSpan responseStartTimeout) + { + _responseStartTimeout = responseStartTimeout; + return this; + } + + // NOTE: UseReport is currently internal rather than public because the REPORT verb does not + // work on every platform (ch47341) + /// + /// Sets whether to use the HTTP REPORT method for feature flag requests. + /// + /// + /// + /// By default, polling and streaming connections are made with the GET method, with the user data + /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body + /// instead, which is somewhat more secure and efficient. + /// + /// + /// However, the REPORT method is not always supported: Android (in the versions tested so far) + /// does not allow it, and some network gateways do not allow it. Therefore it is disabled in the SDK + /// by default. You can enable it if you know your code will not be running on Android and not connecting + /// through a gateway/proxy that disallows REPORT. + /// + /// + /// true to enable the REPORT method + /// the builder + internal HttpConfigurationBuilder UseReport(bool useReport) + { + _useReport = useReport; + return this; + } + + /// + /// For use by wrapper libraries to set an identifying name for the wrapper being used. + /// + /// + /// This will be included in a header during requests to the LaunchDarkly servers to allow recording + /// metrics on the usage of these wrapper libraries. + /// + /// an identifying name for the wrapper library + /// version string for the wrapper library + /// the builder + public HttpConfigurationBuilder Wrapper(string wrapperName, string wrapperVersion) + { + _wrapperName = wrapperName; + _wrapperVersion = wrapperVersion; + return this; + } + + /// + /// Called internally by the SDK to create an implementation instance. Applications do not need + /// to call this method. + /// + /// provides the basic SDK configuration properties + /// an + public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfiguration) + { + Func handlerFn; + if (_messageHandler is null) + { + handlerFn = p => PlatformSpecific.Http.CreateHttpMessageHandler(p.ConnectTimeout, p.ReadTimeout); + } + else + { + handlerFn = p => _messageHandler; + } + + var httpProperties = HttpProperties.Default + .WithAuthorizationKey(basicConfiguration.MobileKey) + .WithConnectTimeout(_connectTimeout) + .WithHttpMessageHandlerFactory(handlerFn) + .WithProxy(_proxy) + .WithReadTimeout(_readTimeout) + .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) + .WithWrapper(_wrapperName, _wrapperVersion); + + foreach (var kv in _customHeaders) + { + httpProperties = httpProperties.WithHeader(kv.Key, kv.Value); + } + + return new HttpConfiguration( + httpProperties, + _messageHandler, + _responseStartTimeout, + _useReport + ); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs index 6bb4c75f..910eca9a 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// If you want to set non-default values for any of these properties, create a builder with /// , change its properties with the methods of this class, and pass it - /// to . + /// to . /// /// /// The default behavior, if you do not change any properties, depends on the runtime platform: @@ -41,7 +41,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class LoggingConfigurationBuilder : ILoggingConfigurationFactory + public sealed class LoggingConfigurationBuilder { private string _baseLoggerName = null; private ILogAdapter _logAdapter = null; @@ -173,7 +173,11 @@ public LoggingConfigurationBuilder Level(LogLevel minimumLevel) return this; } - /// + /// + /// Called internally by the SDK to create a configuration instance. Applications do not need + /// to call this method. + /// + /// the logging configuration public LoggingConfiguration CreateLoggingConfiguration() { ILogAdapter logAdapter; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 4b669d3d..8c3dbfa7 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -130,9 +130,8 @@ bool inBackground var requestor = new FeatureFlagRequestor( _baseUri ?? DefaultBaseUri, currentUser, - context.UseReport, context.EvaluationReasons, - context.HttpProperties, + context.Http, logger ); diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index e27b1c30..de29c60e 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -185,9 +185,8 @@ bool inBackground var requestor = new FeatureFlagRequestor( pollingBaseUri, currentUser, - context.UseReport, context.EvaluationReasons, - context.HttpProperties, + context.Http, logger ); @@ -195,11 +194,10 @@ bool inBackground updateSink, currentUser, baseUri, - context.UseReport, context.EvaluationReasons, _initialReconnectDelay, requestor, - context.HttpProperties, + context.Http, logger, _eventSourceCreator ); diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs new file mode 100644 index 00000000..7a5298ea --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs @@ -0,0 +1,23 @@ + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// The most basic properties of the SDK client that are available to all SDK components. + /// + public sealed class BasicConfiguration + { + /// + /// The configured mobile key. + /// + public string MobileKey { get; } + + /// + /// Creates an instance. + /// + /// the configured mobile key + public BasicConfiguration(string mobileKey) + { + MobileKey = mobileKey; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs new file mode 100644 index 00000000..96d69175 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Integrations; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Encapsulates top-level HTTP configuration that applies to all SDK components. + /// + /// + /// Use to construct an instance. + /// + public sealed class HttpConfiguration + { + /// + /// The network connection timeout. + /// + /// + /// + /// This is the time allowed for the underlying HTTP client to connect to the + /// LaunchDarkly server, for any individual network connection. + /// + /// + /// Not all .NET platforms support setting a connection timeout. It is implemented as + /// a property of System.Net.Http.SocketsHttpHandler in .NET Core 2.1+ and .NET + /// 5+, but is unavailable in .NET Framework and .NET Standard. On platforms where it + /// is not supported, only will be used. + /// + /// + /// Since this is implemented only in SocketsHttpHandler, if you have + /// specified some other HTTP handler implementation with , + /// the here will be ignored. + /// + /// + /// + public TimeSpan ConnectTimeout { get; } + + /// + /// HTTP headers to be added to all HTTP requests made by the SDK. + /// + /// + /// These include Authorization, User-Agent, and any headers that were + /// specified with . + /// + public IEnumerable> DefaultHeaders { get; } + + /// + /// A custom handler for HTTP requests, or null to use the platform's default handler. + /// + public HttpMessageHandler MessageHandler { get; } + + /// + /// The proxy configuration, if any. + /// + /// + /// This is only present if a proxy was specified programmatically with + /// , not if it was + /// specified with an environment variable. + /// + public IWebProxy Proxy { get; } + + /// + /// The network read timeout (socket timeout). + /// + /// + /// This is the amount of time without receiving data on a connection that the + /// SDK will tolerate before signaling an error. This does not apply to + /// the streaming connection used by , + /// which has its own non-configurable read timeout based on the expected behavior + /// of the LaunchDarkly streaming service. + /// + public TimeSpan ReadTimeout { get; } + + /// + /// The maximum amount of time to wait for the beginning of an HTTP response. + /// + /// + /// + /// This limits how long the SDK will wait from the time it begins trying to make a + /// network connection for an individual HTTP request to the time it starts receiving + /// any data from the server. It is equivalent to the Timeout property in + /// HttpClient. + /// + /// + /// It is not the same as the timeout parameter to, + /// which limits the time for initializing the SDK regardless of how many individual HTTP + /// requests are done in that time. + /// + /// + /// + public TimeSpan ResponseStartTimeout { get; } + + /// + /// Used internally by SDK code that uses the HttpProperties abstraction from LaunchDarkly.InternalSdk. + /// + internal HttpProperties HttpProperties { get; } + + // NOTE: UseReport is currently internal rather than public because the REPORT verb does not + // work on every platform (ch47341) + /// + /// Whether to use the HTTP REPORT method for feature flag requests. + /// + /// + /// + /// By default, polling and streaming connections are made with the GET method, with the user data + /// encoded into the request URI. Using REPORT allows the user data to be sent in the request body + /// instead, which is somewhat more secure and efficient. + /// + /// + /// However, some mobile platforms and some network gateways do not support REPORT, so it is + /// disabled by default. You can enable it if you know it is supported in your environment. + /// + /// + internal bool UseReport { get; } + + /// + /// Constructs an instance, setting all properties. + /// + /// value for + /// value for + /// value for + /// value for + /// value for + /// value for + /// value for + public HttpConfiguration( + TimeSpan connectTimeout, + IEnumerable> defaultHeaders, + HttpMessageHandler messageHandler, + IWebProxy proxy, + TimeSpan readTimeout, + TimeSpan responseStartTimeout, + bool useReport + ) : + this( + MakeHttpProperties(connectTimeout, defaultHeaders, messageHandler, readTimeout), + messageHandler, + responseStartTimeout, + useReport + ) + { } + + internal HttpConfiguration( + HttpProperties httpProperties, + HttpMessageHandler messageHandler, + TimeSpan responseStartTimeout, + bool useReport + ) + { + HttpProperties = httpProperties; + ConnectTimeout = httpProperties.ConnectTimeout; + DefaultHeaders = httpProperties.BaseHeaders; + MessageHandler = messageHandler; + Proxy = httpProperties.Proxy; + ReadTimeout = httpProperties.ReadTimeout; + ResponseStartTimeout = responseStartTimeout; + UseReport = useReport; + } + + /// + /// Helper method for creating an HTTP client instance using the configured properties. + /// + /// a client instance + public HttpClient NewHttpClient() + { + var httpClient = MessageHandler is null ? + new HttpClient() : + new HttpClient(MessageHandler, false); + foreach (var h in DefaultHeaders) + { + httpClient.DefaultRequestHeaders.Add(h.Key, h.Value); + } + httpClient.Timeout = ResponseStartTimeout; + return httpClient; + } + + internal static HttpProperties MakeHttpProperties( + TimeSpan connectTimeout, + IEnumerable> defaultHeaders, + HttpMessageHandler messageHandler, + TimeSpan readTimeout + ) + { + var ret = HttpProperties.Default + .WithConnectTimeout(connectTimeout) + .WithReadTimeout(readTimeout) + .WithHttpMessageHandlerFactory(messageHandler is null ? + (Func)null : + _ => messageHandler); + foreach (var kv in defaultHeaders) + { + ret = ret.WithHeader(kv.Key, kv.Value); + } + return ret; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILoggingConfigurationFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILoggingConfigurationFactory.cs deleted file mode 100644 index 6acdd65d..00000000 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILoggingConfigurationFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.Interfaces -{ - /// - /// Interface for a factory that creates a . - /// - public interface ILoggingConfigurationFactory - { - /// - /// Called internally by the SDK to create a configuration instance. Applications do not need - /// to call this method. - /// - /// the logging configuration - LoggingConfiguration CreateLoggingConfiguration(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index ec2c1c76..3cf8bfc2 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -1,6 +1,5 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -15,9 +14,10 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// public sealed class LdClientContext { - internal Configuration Configuration { get; } - - internal HttpProperties HttpProperties { get; } + /// + /// The basic properties common to all components. + /// + public BasicConfiguration Basic { get; } /// /// The configured logger for the SDK. @@ -30,9 +30,9 @@ public sealed class LdClientContext public bool EvaluationReasons { get; } /// - /// True if the HTTP REPORT method is enabled. + /// The HTTP configuration properties. /// - public bool UseReport { get; } + public HttpConfiguration Http { get; } /// /// Creates an instance. @@ -42,14 +42,16 @@ public LdClientContext( Configuration configuration ) { - var logConfig = (configuration.LoggingConfigurationFactory ?? Components.Logging()) + this.Basic = new BasicConfiguration(configuration.MobileKey); + + var logConfig = (configuration.LoggingConfigurationBuilder ?? Components.Logging()) .CreateLoggingConfiguration(); var logAdapter = logConfig.LogAdapter ?? Logs.None; this.BaseLogger = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); this.EvaluationReasons = configuration.EvaluationReasons; - this.HttpProperties = configuration.HttpProperties; - this.UseReport = configuration.UseReport; + this.Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) + .CreateHttpConfiguration(this.Basic); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index c40f6d3a..62c8f059 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; @@ -46,17 +47,16 @@ internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor internal FeatureFlagRequestor( Uri baseUri, User user, - bool useReport, bool withReasons, - HttpProperties httpProperties, + HttpConfiguration httpConfig, Logger log ) { this._baseUri = baseUri; - this._httpProperties = httpProperties; - this._httpClient = httpProperties.NewHttpClient(); + this._httpProperties = httpConfig.HttpProperties; + this._httpClient = _httpProperties.NewHttpClient(); this._currentUser = user; - this._useReport = useReport; + this._useReport = httpConfig.UseReport; this._withReasons = withReasons; this._log = log; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index d9651354..b2c5848e 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -50,11 +50,10 @@ internal StreamingDataSource( IDataSourceUpdateSink updateSink, User user, Uri baseUri, - bool useReport, bool withReasons, TimeSpan initialReconnectDelay, IFeatureFlagRequestor requestor, - HttpProperties httpProperties, + HttpConfiguration httpConfig, Logger log, EventSourceCreator eventSourceCreator // used only in tests ) @@ -62,11 +61,11 @@ EventSourceCreator eventSourceCreator // used only in tests this._updateSink = updateSink; this._user = user; this._baseUri = baseUri; - this._useReport = useReport; + this._useReport = httpConfig.UseReport; this._withReasons = withReasons; this._initialReconnectDelay = initialReconnectDelay; this._requestor = requestor; - this._httpProperties = httpProperties; + this._httpProperties = httpConfig.HttpProperties; this._initTask = new TaskCompletionSource(); this._log = log; this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index c583dd45..8b013121 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -69,9 +69,5 @@ - - - Code - - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index fefefe59..18a90e3c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -1,5 +1,4 @@ using System; -using System.Net.Http; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.TestHelpers; @@ -73,17 +72,17 @@ public void Events() } [Fact] - public void HttpMessageHandler() + public void Http() { - var prop = _tester.Property(c => c.HttpMessageHandler, (b, v) => b.HttpMessageHandler(v)); - // Can't test the default here because the default is platform-dependent. - prop.AssertCanSet(new HttpClientHandler()); + var prop = _tester.Property(c => c.HttpConfigurationBuilder, (b, v) => b.Http(v)); + prop.AssertDefault(null); + prop.AssertCanSet(Components.HttpConfiguration()); } [Fact] public void Logging() { - var prop = _tester.Property(c => c.LoggingConfigurationFactory, (b, v) => b.Logging(v)); + var prop = _tester.Property(c => c.LoggingConfigurationBuilder, (b, v) => b.Logging(v)); prop.AssertDefault(null); prop.AssertCanSet(Components.Logging(Logs.ToWriter(Console.Out))); } @@ -93,7 +92,7 @@ public void LoggingAdapterShortcut() { var adapter = Logs.ToWriter(Console.Out); var config = Configuration.Builder("key").Logging(adapter).Build(); - var logConfig = config.LoggingConfigurationFactory.CreateLoggingConfiguration(); + var logConfig = config.LoggingConfigurationBuilder.CreateLoggingConfiguration(); Assert.Same(adapter, logConfig.LogAdapter); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs new file mode 100644 index 00000000..547a67dd --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.TestHelpers; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class HttpConfigurationBuilderTest + { + private static readonly BasicConfiguration basicConfig = + new BasicConfiguration("mobile-key"); + + private readonly BuilderBehavior.BuildTester _tester = + BuilderBehavior.For(() => Components.HttpConfiguration(), + b => b.CreateHttpConfiguration(basicConfig)); + + [Fact] + public void ConnectTimeout() + { + var prop = _tester.Property(c => c.ConnectTimeout, (b, v) => b.ConnectTimeout(v)); + prop.AssertDefault(HttpConfigurationBuilder.DefaultConnectTimeout); + prop.AssertCanSet(TimeSpan.FromSeconds(7)); + } + + [Fact] + public void CustomHeaders() + { + var config = Components.HttpConfiguration() + .CustomHeader("header1", "value1") + .CustomHeader("header2", "value2") + .CreateHttpConfiguration(basicConfig); + Assert.Equal("value1", HeadersAsMap(config.DefaultHeaders)["header1"]); + Assert.Equal("value2", HeadersAsMap(config.DefaultHeaders)["header2"]); + } + + [Fact] + public void MessageHandler() + { + var prop = _tester.Property(c => c.MessageHandler, (b, v) => b.MessageHandler(v)); + // Can't test the default here because the default is platform-dependent. + prop.AssertCanSet(new HttpClientHandler()); + } + + [Fact] + public void MobileKeyHeader() + { + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); + Assert.Equal(basicConfig.MobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); + } + + [Fact] + public void ReadTimeout() + { + var prop = _tester.Property(c => c.ReadTimeout, (b, v) => b.ReadTimeout(v)); + prop.AssertDefault(HttpConfigurationBuilder.DefaultReadTimeout); + prop.AssertCanSet(TimeSpan.FromSeconds(7)); + } + + [Fact] + public void ResponseStartTimeout() + { + var value = TimeSpan.FromMilliseconds(789); + var prop = _tester.Property(c => c.ResponseStartTimeout, (b, v) => b.ResponseStartTimeout(v)); + prop.AssertDefault(HttpConfigurationBuilder.DefaultResponseStartTimeout); + prop.AssertCanSet(value); + + var config = Components.HttpConfiguration().ResponseStartTimeout(value) + .CreateHttpConfiguration(basicConfig); + using (var client = config.NewHttpClient()) + { + Assert.Equal(value, client.Timeout); + } + } + + [Fact] + public void UseReport() + { + var prop = _tester.Property(c => c.UseReport, (b, v) => b.UseReport(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } + + [Fact] + public void UserAgentHeader() + { + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); + Assert.Equal("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)), + HeadersAsMap(config.DefaultHeaders)["user-agent"]); // not configurable + } + + [Fact] + public void WrapperDefaultNone() + { + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); + Assert.False(HeadersAsMap(config.DefaultHeaders).ContainsKey("x-launchdarkly-wrapper")); + } + + [Fact] + public void WrapperNameOnly() + { + var config = Components.HttpConfiguration().Wrapper("w", null) + .CreateHttpConfiguration(basicConfig); + Assert.Equal("w", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); + } + + [Fact] + public void WrapperNameAndVersion() + { + var config = Components.HttpConfiguration().Wrapper("w", "1.0") + .CreateHttpConfiguration(basicConfig); + Assert.Equal("w/1.0", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); + } + + private static Dictionary HeadersAsMap(IEnumerable> headers) + { + return headers.ToDictionary(kv => kv.Key.ToLower(), kv => kv.Value); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 0bef3b2d..5cc99820 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Json; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; @@ -40,12 +42,13 @@ string expectedQuery { var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); + var config = Configuration.Default(_mobileKey); + using (var requestor = new FeatureFlagRequestor( baseUri, _user, - false, withReasons, - Configuration.Builder(_mobileKey).Build().HttpProperties, + new LdClientContext(config).Http, testLogger)) { var resp = await requestor.FeatureFlagsAsync(); @@ -62,53 +65,51 @@ string expectedQuery } } - // Report mode is currently disabled - ch47341 - //[Fact] - //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync() - //{ - // using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) - // { - // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - // .UseReport(true).Build(); - // - // using (var requestor = new FeatureFlagRequestor(config, _user)) - // { - // var resp = await requestor.FeatureFlagsAsync(); - // Assert.Equal(200, resp.statusCode); - // Assert.Equal(_allDataJson, resp.jsonResponse); - // - // var req = server.Recorder.RequireRequest(); - // Assert.Equal("REPORT", req.Method); - // Assert.Equal($"/msdk/evalx/user", req.Path); - // Assert.Equal("", req.Query); - // Assert.Equal(_mobileKey, req.Headers["Authorization"]); - // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); - // } - // }); - //} + // REPORT mode is known to fail in Android (ch47341) +#if !__ANDROID__ + [Theory] + [InlineData("", false, "/msdk/evalx/user", "")] + [InlineData("", true, "/msdk/evalx/user", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/msdk/evalx/user", "")] + [InlineData("/basepath", true, "/basepath/msdk/evalx/user", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/msdk/evalx/user", "")] + [InlineData("/basepath/", true, "/basepath/msdk/evalx/user", "?withReasons=true")] + public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync( + string baseUriExtraPath, + bool withReasons, + string expectedPath, + string expectedQuery + ) + { + using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) + { + var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); + + var config = Configuration.Builder(_mobileKey) + .Http(Components.HttpConfiguration().UseReport(true)) + .Build(); - //[Fact] - //public async Task GetFlagsUsesCorrectUriAndMethodInReportModeWithReasonsAsync() - //{ - // using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) - // { - // var config = Configuration.Builder(_mobileKey).BaseUri(new Uri(server.GetUrl())) - // .UseReport(true).EvaluationReasons(true).Build(); - // - // using (var requestor = new FeatureFlagRequestor(config, _user)) - // { - // var resp = await requestor.FeatureFlagsAsync(); - // Assert.Equal(200, resp.statusCode); - // Assert.Equal(_allDataJson, resp.jsonResponse); - // - // var req = server.GetLastRequest(); - // Assert.Equal("REPORT", req.Method); - // Assert.Equal($"/msdk/evalx/user", req.Path); - // Assert.Equal("?withReasons=true", req.Query); - // Assert.Equal(_mobileKey, req.Headers["Authorization"]); - // TestUtil.AssertJsonEquals(LdValue.Parse(_userJson), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); - // } - // }); - //} + using (var requestor = new FeatureFlagRequestor( + baseUri, + _user, + withReasons, + new LdClientContext(config).Http, + testLogger)) + { + var resp = await requestor.FeatureFlagsAsync(); + Assert.Equal(200, resp.statusCode); + Assert.Equal(_allDataJson, resp.jsonResponse); + + var req = server.Recorder.RequireRequest(); + Assert.Equal("REPORT", req.Method); + Assert.Equal(expectedPath, req.Path); + Assert.Equal(expectedQuery, req.Query); + Assert.Equal(_mobileKey, req.Headers["Authorization"]); + TestUtil.AssertJsonEquals(LdValue.Parse(LdJsonSerialization.SerializeObject(_user)), + TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); + } + } + } +#endif } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 192d536c..65c928c8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -45,11 +45,10 @@ private StreamingDataSource MakeStartedStreamingDataSource() new DataSourceUpdateSinkImpl(mockFlagCacheMgr), user, baseUri, - false, withReasons, initialReconnectDelay, mockRequestor, - Configuration.Builder("key").Build().HttpProperties, + TestUtil.SimpleContext.Http, testLogger, eventSourceFactory.Create() ); From 1f6a1f3aee6fc01d05d0d55879d23b6c5aebb6bd Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 22 Sep 2021 16:00:24 -0700 Subject: [PATCH 358/499] (#1) reimplement data store components for cleaner abstraction of persistence, make it configurable (#127) --- src/LaunchDarkly.ClientSdk/Components.cs | 21 ++ src/LaunchDarkly.ClientSdk/Configuration.cs | 15 +- .../ConfigurationBuilder.cs | 41 ++- src/LaunchDarkly.ClientSdk/DataModel.cs | 18 ++ .../FlagChangedEvent.cs | 15 +- .../Interfaces/DataStoreTypes.cs | 76 ++++- .../Interfaces/IDataSourceUpdateSink.cs | 20 +- .../Interfaces/IPersistentDataStore.cs | 64 ++++ .../Interfaces/IPersistentDataStoreFactory.cs | 18 ++ .../Internal/ComponentsImpl.cs | 19 ++ .../Internal/DataModelSerialization.cs | 107 +++++++ .../DataSources/DataSourceUpdateSinkImpl.cs | 118 ++++++-- .../DataSources/FeatureFlagRequestor.cs | 4 +- .../Internal/DataSources/PollingDataSource.cs | 6 +- .../DataSources/StreamingDataSource.cs | 41 +-- .../DataStores/DefaultPersistentDataStore.cs | 29 ++ .../DataStores/DefaultPersistentStorage.cs | 22 -- .../Internal/DataStores/FlagCacheManager.cs | 153 ---------- .../Internal/DataStores/InMemoryDataStore.cs | 60 ++++ .../DataStores/PersistentDataStoreWrapper.cs | 116 +++++++ .../DataStores/UserFlagDeviceCache.cs | 54 ---- .../DataStores/UserFlagInMemoryCache.cs | 33 -- .../Internal/Factory.cs | 24 -- .../Internal/Interfaces/IDataStore.cs | 65 ++++ .../Internal/Interfaces/IFlagCacheManager.cs | 15 - .../Internal/Interfaces/IPersistentStorage.cs | 14 - .../Internal/Interfaces/IUserFlagCache.cs | 18 -- .../Internal/JsonUtil.cs | 50 ---- src/LaunchDarkly.ClientSdk/LdClient.cs | 32 +- .../Preferences.netstandard.cs | 26 +- .../AndroidSpecificTests.cs | 4 +- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 2 +- .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 23 +- .../BuilderBehavior.cs | 283 ------------------ .../BuilderTestUtil.cs | 148 --------- .../ConfigurationTest.cs | 8 +- .../FeatureFlagBuilder.cs | 62 ---- .../FlagChangedEventTests.cs | 85 ++---- .../Internal/DataModelSerializationTest.cs | 97 ++++++ .../DataSources/DataSourceUpdateSinkTest.cs | 260 ++++++++++++++++ .../DataSources/FeatureFlagRequestorTests.cs | 4 +- .../DataSources/PollingDataSourceTest.cs | 11 +- .../DataSources/StreamingDataSourceTest.cs | 92 ++---- .../DataStores/FlagCacheManagerTests.cs | 122 -------- .../DataStores/InMemoryDataStoreTest.cs | 193 ++++++++++++ .../PersistentDataStoreWrapperTest.cs | 135 +++++++++ .../Internal/DataStores/UserFlagCacheTests.cs | 24 -- .../LDClientEndToEndTests.cs | 85 +++--- .../LaunchDarkly.ClientSdk.Tests.csproj | 2 +- .../LdClientEventTests.cs | 79 ++--- .../LdClientTests.cs | 61 ++-- .../MockComponents.cs | 110 +++---- .../ModelBuilders.cs | 102 +++++++ .../TestLogging.cs | 18 +- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 71 +++-- .../IOsSpecificTests.cs | 4 +- .../LaunchDarkly.ClientSdk.iOS.Tests.csproj | 2 +- 57 files changed, 1818 insertions(+), 1563 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index 6eb0c42b..ffa3c1f6 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -124,6 +124,8 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// .Build(); /// /// + /// + /// public static IEventProcessorFactory NoEvents => ComponentsImpl.NullEventProcessorFactory.Instance; @@ -140,9 +142,26 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// .Build(); /// /// + /// + /// + /// public static LoggingConfigurationBuilder NoLogging => new LoggingConfigurationBuilder().Adapter(Logs.None); + /// + /// A configuration object that disables persistent storage. + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .Persistence(Components.NoPersistence) + /// .Build(); + /// + /// + /// + public static IPersistentDataStoreFactory NoPersistence => + ComponentsImpl.NullPersistentDataStoreFactory.Instance; + /// /// Returns a configurable factory for using polling mode to get feature flag data. /// @@ -206,6 +225,8 @@ public static PollingDataSourceBuilder PollingDataSource() => /// /// /// a builder for setting event properties + /// + /// public static EventProcessorBuilder SendEvents() => new EventProcessorBuilder(); /// diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index d3b5cdd6..27134958 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -31,9 +31,7 @@ public sealed class Configuration internal IBackgroundModeManager BackgroundModeManager { get; } internal IConnectivityStateManager ConnectivityStateManager { get; } internal IDeviceInfo DeviceInfo { get; } - internal IFlagCacheManager FlagCacheManager { get; } internal IFlagChangedEventManager FlagChangedEventManager { get; } - internal IPersistentStorage PersistentStorage { get; } /// /// Whether to disable the automatic sending of an alias event when the current user is changed @@ -106,13 +104,10 @@ public sealed class Configuration public bool Offline { get; } /// - /// Whether the SDK should save flag values for each user in persistent storage, so they will be - /// immediately available the next time the SDK is started for the same user. + /// A factory object that creates an implementation of , for + /// saving flag values in persistent storage. /// - /// - /// The default is . - /// - public bool PersistFlagValues { get; } + public IPersistentDataStoreFactory PersistentDataStoreFactory { get; } /// /// Creates a configuration with all parameters set to the default. @@ -173,14 +168,12 @@ internal Configuration(ConfigurationBuilder builder) LoggingConfigurationBuilder = builder._loggingConfigurationBuilder; MobileKey = builder._mobileKey; Offline = builder._offline; - PersistFlagValues = builder._persistFlagValues; + PersistentDataStoreFactory = builder._persistentDataStoreFactory; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; DeviceInfo = builder._deviceInfo; - FlagCacheManager = builder._flagCacheManager; FlagChangedEventManager = builder._flagChangedEventManager; - PersistentStorage = builder._persistentStorage; } } } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 2f57fbc7..86f578eb 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -42,15 +42,13 @@ public sealed class ConfigurationBuilder internal LoggingConfigurationBuilder _loggingConfigurationBuilder = null; internal string _mobileKey; internal bool _offline = false; - internal bool _persistFlagValues = true; + internal IPersistentDataStoreFactory _persistentDataStoreFactory = null; // Internal properties only settable for testing internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; internal IDeviceInfo _deviceInfo; - internal IFlagCacheManager _flagCacheManager; internal IFlagChangedEventManager _flagChangedEventManager; - internal IPersistentStorage _persistentStorage; internal ConfigurationBuilder(string mobileKey) { @@ -68,7 +66,7 @@ internal ConfigurationBuilder(Configuration copyFrom) _loggingConfigurationBuilder = copyFrom.LoggingConfigurationBuilder; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; - _persistFlagValues = copyFrom.PersistFlagValues; + _persistentDataStoreFactory = copyFrom.PersistentDataStoreFactory; } /// @@ -252,7 +250,7 @@ public ConfigurationBuilder Logging(LoggingConfigurationBuilder loggingConfigura /// /// This should be the "mobile key" field for the environment on your LaunchDarkly dashboard. /// - /// + /// the mobile key /// the same builder public ConfigurationBuilder MobileKey(string mobileKey) { @@ -272,17 +270,26 @@ public ConfigurationBuilder Offline(bool offline) } /// - /// Sets whether the SDK should save flag values for each user in persistent storage, so they will be - /// immediately available the next time the SDK is started for the same user. + /// Sets the implementation of the component that saves flag values for each user in persistent storage. /// /// - /// The default is . + /// + /// The persistent storage mechanism allows the SDK to immediately access the last known flag data + /// for the user, if any, if it is offline or has not yet received data from LaunchDarkly. + /// + /// + /// By default, the SDK uses a persistence mechanism that is specific to each platform: on Android and + /// iOS it is the native preferences store, and in the .NET Standard implementation for desktop apps + /// it is the System.IO.IsolatedStorage API. You may substitute a custom implementation, or + /// pass to disable persistent storage and use only in-memory + /// storage. + /// /// - /// to save flag values + /// the factory object; null to use the default implementation /// the same builder - public ConfigurationBuilder PersistFlagValues(bool persistFlagValues) + public ConfigurationBuilder Persistence(IPersistentDataStoreFactory persistentDataStoreFactory) { - _persistFlagValues = persistFlagValues; + _persistentDataStoreFactory = persistentDataStoreFactory; return this; } @@ -306,22 +313,10 @@ internal ConfigurationBuilder DeviceInfo(IDeviceInfo deviceInfo) return this; } - internal ConfigurationBuilder FlagCacheManager(IFlagCacheManager flagCacheManager) - { - _flagCacheManager = flagCacheManager; - return this; - } - internal ConfigurationBuilder FlagChangedEventManager(IFlagChangedEventManager flagChangedEventManager) { _flagChangedEventManager = flagChangedEventManager; return this; } - - internal ConfigurationBuilder PersistentStorage(IPersistentStorage persistentStorage) - { - _persistentStorage = persistentStorage; - return this; - } } } diff --git a/src/LaunchDarkly.ClientSdk/DataModel.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs index 75cb6eb3..819ce5ee 100644 --- a/src/LaunchDarkly.ClientSdk/DataModel.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -2,6 +2,8 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client { /// @@ -52,6 +54,10 @@ internal FeatureFlag( this.debugEventsUntilDate = debugEventsUntilDate; } + /// + public override bool Equals(object obj) => + Equals(obj as FeatureFlag); + /// public bool Equals(FeatureFlag otherFlag) => value.Equals(otherFlag.value) @@ -61,6 +67,18 @@ public bool Equals(FeatureFlag otherFlag) => && flagVersion == otherFlag.flagVersion && trackEvents == otherFlag.trackEvents && debugEventsUntilDate == otherFlag.debugEventsUntilDate; + + /// + public override int GetHashCode() => + value.GetHashCode(); + + /// + public override string ToString() => + string.Format("({0},{1},{2},{3},{4},{5},{6},{7})", + value, variation, reason, version, flagVersion, trackEvents, trackReason, debugEventsUntilDate); + + internal ItemDescriptor ToItemDescriptor() => + new ItemDescriptor(version, this); } internal sealed class FeatureFlagJsonConverter : IJsonStreamConverter diff --git a/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs b/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs index c9f287a5..ad02e573 100644 --- a/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs +++ b/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs @@ -68,8 +68,7 @@ internal FlagChangedEventArgs(string key, LdValue newValue, LdValue oldValue, bo internal interface IFlagChangedEventManager { event EventHandler FlagChanged; - void FlagWasDeleted(string flagKey, LdValue oldValue); - void FlagWasUpdated(string flagKey, LdValue newValue, LdValue oldValue); + void FireEvent(FlagChangedEventArgs e); } internal sealed class FlagChangedEventManager : IFlagChangedEventManager @@ -88,17 +87,7 @@ public bool IsHandlerRegistered(EventHandler handler) return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); } - public void FlagWasDeleted(string flagKey, LdValue oldValue) - { - FireEvent(new FlagChangedEventArgs(flagKey, LdValue.Null, oldValue, true)); - } - - public void FlagWasUpdated(string flagKey, LdValue newValue, LdValue oldValue) - { - FireEvent(new FlagChangedEventArgs(flagKey, newValue, oldValue, false)); - } - - private void FireEvent(FlagChangedEventArgs eventArgs) + public void FireEvent(FlagChangedEventArgs eventArgs) { var copyOfHandlers = FlagChanged; var sender = this; diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs index de836058..bb24a15d 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs @@ -1,4 +1,7 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using static LaunchDarkly.Sdk.Client.DataModel; @@ -9,24 +12,83 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// public static class DataStoreTypes { + /// + /// A versioned item (or placeholder) storeable in a data store. + /// + public struct ItemDescriptor : IEquatable + { + /// + /// The version number of this data, provided by the SDK. + /// + public int Version { get; } + + /// + /// The data item, or null if this is a deleted item placeholder. + /// + public FeatureFlag Item { get; } + + /// + /// Constructs an instance. + /// + /// the version number + /// the data item, or null if this is a deleted item placeholder + public ItemDescriptor(int version, FeatureFlag item) + { + Version = version; + Item = item; + } + + /// + public override bool Equals(object obj) => + obj is ItemDescriptor o && Equals(o); + + + /// + public bool Equals(ItemDescriptor other) => + other.Version == this.Version && + object.Equals(other.Item, this.Item); + + /// + public override int GetHashCode() => + Version + 31 * (Item?.GetHashCode() ?? 0); + + /// + public override string ToString() => "ItemDescriptor(" + Version + "," + Item + ")"; + } + /// /// Represents a full set of feature flag data received from LaunchDarkly. /// - public struct FullDataSet + public struct FullDataSet : IEquatable { /// - /// The feature flags, indexed by key. + /// The feature flag data. /// - public IImmutableDictionary Flags { get; } + public IImmutableList> Items { get; } /// /// Creates a new instance. /// - /// the feature flags, indexed by key - public FullDataSet(IImmutableDictionary flags) + /// the feature flags, indexed by key + public FullDataSet(IEnumerable> items) { - Flags = flags ?? ImmutableDictionary.Create(); + Items = items is null ? ImmutableList.Create>() : + ImmutableList.CreateRange(items); } + + /// + public override bool Equals(object obj) => + obj is FullDataSet o && Equals(o); + + + /// + public bool Equals(FullDataSet other) => + Enumerable.SequenceEqual(other.Items.OrderBy(ItemKey), this.Items.OrderBy(ItemKey)); + + /// + public override int GetHashCode() => Items.OrderBy(ItemKey).GetHashCode(); + + private static string ItemKey(KeyValuePair kv) => kv.Key; } } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs index 1dd48a2a..f152a490 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Immutable; - -using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -20,24 +17,15 @@ public interface IDataSourceUpdateSink /// the data set /// the current user /// true if the update succeeded, false if it failed - void Init(DataStoreTypes.FullDataSet data, User user); + void Init(FullDataSet data, User user); /// /// Updates or inserts an item. For updates, the object will only be updated if the existing /// version is less than the new version. /// /// the feature flag key - /// the data version - /// the flag data - /// the current user - void Upsert(string key, int version, FeatureFlag data, User user); - - /// - /// Deletes an item, if the version is greater than any existing version. - /// - /// the feature flag key - /// the deletion version + /// the item data /// the current user - void Delete(string key, int version, User user); + void Upsert(string key, ItemDescriptor data, User user); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs new file mode 100644 index 00000000..2ad9981e --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs @@ -0,0 +1,64 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for a data store that holds feature flag data in a serialized form. + /// + /// + /// + /// This interface should be used for platform-specific integrations that store data somewhere + /// other than in memory. The SDK will take care of converting between its own internal data model + /// and a serialized string form; the data store interacts only with the serialized form. The data + /// store should not make any assumptions about the format of the serialized data. + /// + /// + /// Unlike server-side SDKs, the persistent data store in this SDK reads or writes the data for + /// all flags at once for a given user, instead of one flag at a time. This is for two reasons: + /// + /// + /// The SDK assumes that the persistent store cannot be written to by any other process, + /// so it does not need to implement read-through behavior when getting individual flags, and can + /// read flags only from the in-memory cache. It only needs to read the persistent store at + /// startup time or when changing users, to get any last known data for all flags at once. + /// On many platforms, reading or writing multiple separate keys may be inefficient or may + /// not be possible to do atomically. + /// + /// + /// The SDK will also provide its own caching layer on top of the persistent data store; the data + /// store implementation should not provide caching, but simply do every query or update that the + /// SDK tells it to do. + /// + /// + /// Implementations must be thread-safe. + /// + /// + /// Error handling is defined as follows: if any data store operation encounters an I/O + /// error, or is otherwise unable to complete its task, it should throw an exception to make + /// the SDK aware of this. + /// + /// + /// + public interface IPersistentDataStore : IDisposable + { + /// + /// Overwrites the store's contents for a specific user with a serialized data set. + /// + /// + /// + /// All previous data for the user should be discarded. This should not affect stored data for + /// any other user. For efficiency, the store can assume that only the user key is significant. + /// + /// + /// the current user + /// a serialized data set represented as an opaque string + void Init(User user, string allData); + + /// + /// Retrieves the serialized data for a specific user, if available. + /// + /// the current user + /// the serialized data for that user, or null if there is none + string GetAll(User user); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs new file mode 100644 index 00000000..cd335342 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs @@ -0,0 +1,18 @@ + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Interface for a factory that creates some implementation of . + /// + /// + public interface IPersistentDataStoreFactory + { + /// + /// Called internally by the SDK to create an implementation instance. Applications do not need + /// to call this method. + /// + /// configuration of the current client instance + /// an IPersistentDataStore instance + IPersistentDataStore CreatePersistentDataStore(LdClientContext context); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index 5f5ac3a3..b5d6006c 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -51,5 +51,24 @@ public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) { } public void SetOffline(bool offline) { } } + + internal sealed class NullPersistentDataStoreFactory : IPersistentDataStoreFactory + { + internal static NullPersistentDataStoreFactory Instance = new NullPersistentDataStoreFactory(); + + public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => + NullPersistentDataStore.Instance; + } + + internal sealed class NullPersistentDataStore : IPersistentDataStore + { + internal static NullPersistentDataStore Instance = new NullPersistentDataStore(); + + public string GetAll(User user) => null; + + public void Init(User user, string allData) { } + + public void Dispose() { } + } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs new file mode 100644 index 00000000..d31ad1cc --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using LaunchDarkly.JsonStream; +using LaunchDarkly.Sdk.Json; + +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + // Methods for converting data to or from a serialized form. + // + // The JSON representation of a User is defined along with User in LaunchDarkly.CommonSdk. + // + // The serialized representation of a single FeatureFlag is simply a JSON object containing + // its properties, as defined in FeatureFlag. + // + // For a whole set of FeatureFlags, the format used to store serialized data is the same as + // the format used by the LaunchDarkly polling and streaming endpoints. It is a JSON object + // where each key is a flag key and each value is the FeatureFlag representation. There is + // no way to represent a deleted item placeholder in this format. The version for each flag + // is simply the "version" property of the flag's JSON representation. + // + // All deserialization methods throw InvalidDataException for malformed data. + + internal static class DataModelSerialization + { + private const string ParseErrorMessage = "Data was not in a recognized format"; + + internal static string SerializeUser(User user) => + LdJsonSerialization.SerializeObject(user); + + internal static string SerializeFlag(FeatureFlag flag) => + LdJsonSerialization.SerializeObject(flag); + + internal static string SerializeAll(FullDataSet allData) + { + var w = JWriter.New(); + using (var ow = w.Object()) + { + foreach (var item in allData.Items) + { + if (item.Value.Item != null) + { + FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, ow.Name(item.Key)); + } + } + } + return w.GetString(); + } + + internal static FeatureFlag DeserializeFlag(string json) + { + try + { + return LdJsonSerialization.DeserializeObject(json); + } + catch (Exception e) + { + throw new InvalidDataException(ParseErrorMessage, e); + } + } + + internal static FullDataSet DeserializeAll(string serializedData) + { + try + { + return DeserializeV1Schema(serializedData); + } + catch (InvalidDataException) + { + throw; + } + catch (Exception e) + { + throw new InvalidDataException(ParseErrorMessage, e); + } + throw new InvalidDataException(ParseErrorMessage); + } + + // Currently there is only one serialization schema, but it is possible that future + // SDK versions will require a richer model. In that case we will need to design the + // serialized format to be distinguishable from previous formats and allow reading + // of older formats, while only writing the new format. + + internal static FullDataSet DeserializeV1Schema(string serializedData) + { + var builder = ImmutableList.CreateBuilder>(); + var r = JReader.FromString(serializedData); + try + { + for (var or = r.Object(); or.Next(ref r);) + { + var flag = FeatureFlagJsonConverter.ReadJsonValue(ref r); + builder.Add(new KeyValuePair(or.Name.ToString(), flag.ToItemDescriptor())); + } + } + catch (Exception e) + { + throw new InvalidDataException(ParseErrorMessage, r.TranslateException(e)); + } + return new FullDataSet(builder.ToImmutable()); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index 73aafa39..492e5ab4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System.Collections.Generic; +using System.Collections.Immutable; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Interfaces; @@ -9,50 +10,115 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink { - private readonly IFlagCacheManager _dataStore; + private readonly IDataStore _dataStore; + private readonly IFlagChangedEventManager _flagChangedEventManager; + private readonly object _lastValuesLock = new object(); + private volatile ImmutableDictionary> _lastValues = + ImmutableDictionary.Create>(); - public DataSourceUpdateSinkImpl(IFlagCacheManager flagCacheManager) + public DataSourceUpdateSinkImpl( + IDataStore dataStore, + IFlagChangedEventManager flagChangedEventManager + ) { - _dataStore = flagCacheManager; + _dataStore = dataStore; + _flagChangedEventManager = flagChangedEventManager; } public void Init(FullDataSet data, User user) { - var builder = ImmutableDictionary.CreateBuilder(); - foreach (var entry in data.Flags) + _dataStore.Init(user, data); + + ImmutableDictionary oldValues, newValues; + lock (_lastValuesLock) { - if (entry.Value != null) + _lastValues.TryGetValue(user.Key, out oldValues); + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var newEntry in data.Items) { - builder.Add(entry.Key, entry.Value); + var newFlag = newEntry.Value.Item; + if (newFlag != null) + { + builder.Add(newEntry.Key, newFlag); + } } + newValues = builder.ToImmutable(); + _lastValues = _lastValues.SetItem(user.Key, newValues); } - _dataStore.CacheFlagsFromService(builder.ToImmutableDictionary(), user); - } - public void Upsert(string key, int version, FeatureFlag data, User user) - { - var oldItem = _dataStore.FlagForUser(key, user); - if (oldItem != null && oldItem.version >= version) + if (oldValues != null) { - return; + List events = new List(); + + foreach (var newEntry in newValues) + { + var newFlag = newEntry.Value; + if (oldValues.TryGetValue(newEntry.Key, out var oldFlag)) + { + if (newFlag.variation != oldFlag.variation) + { + events.Add(new FlagChangedEventArgs(newEntry.Key, + newFlag.value, oldFlag.value, false)); + } + } + else + { + events.Add(new FlagChangedEventArgs(newEntry.Key, + newFlag.value, LdValue.Null, false)); + } + } + foreach (var oldEntry in oldValues) + { + if (!newValues.ContainsKey(oldEntry.Key)) + { + events.Add(new FlagChangedEventArgs(oldEntry.Key, + LdValue.Null, oldEntry.Value.value, true)); + } + } + foreach (var e in events) + { + _flagChangedEventManager.FireEvent(e); + } } - _dataStore.UpdateFlagForUser(key, data, user); - // Eventually we should make this class responsible for sending flag change events, - // to decouple that behavior from the storage mechanism, but currently that is - // implemented within FlagCacheManager. } - public void Delete(string key, int version, User user) + public void Upsert(string key, ItemDescriptor data, User user) { - var oldItem = _dataStore.FlagForUser(key, user); - if (oldItem == null || oldItem.version >= version) + var updated = _dataStore.Upsert(user, key, data); + if (!updated) { return; } - // Currently we are not storing a "tombstone" for deletions, so it is possible for - // an out-of-order update after a delete to recreate the flag. We need to extend the - // data store implementation to allow tombstones. - _dataStore.RemoveFlagForUser(key, user); + + FeatureFlag oldFlag = null; + lock (_lastValuesLock) + { + _lastValues.TryGetValue(user.Key, out var oldValues); + if (oldValues is null) + { + // didn't have any flags for this user + var initValues = ImmutableDictionary.Create(); + if (data.Item != null) + { + initValues = initValues.SetItem(key, data.Item); + } + _lastValues = _lastValues.SetItem(user.Key, initValues); + return; // don't bother with change events if we had no previous data + } + oldValues.TryGetValue(key, out oldFlag); + var newValues = data.Item is null ? + oldValues.Remove(key) : oldValues.SetItem(key, data.Item); + _lastValues = _lastValues.SetItem(user.Key, newValues); + } + if (oldFlag?.variation != data.Item?.variation) + { + var eventArgs = new FlagChangedEventArgs(key, + data.Item?.value ?? LdValue.Null, + oldFlag?.value ?? LdValue.Null, + data.Item is null + ); + _flagChangedEventManager.FireEvent(eventArgs); + } } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index 62c8f059..a0e8ff86 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -69,14 +69,14 @@ public async Task FeatureFlagsAsync() private HttpRequestMessage GetRequestMessage() { - var path = Constants.FLAG_REQUEST_PATH_GET + Base64.UrlSafeEncode(JsonUtil.EncodeJson(_currentUser)); + var path = Constants.FLAG_REQUEST_PATH_GET + Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_currentUser)); return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } private HttpRequestMessage ReportRequestMessage() { var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(Constants.FLAG_REQUEST_PATH_REPORT)); - request.Content = new StringContent(JsonUtil.EncodeJson(_currentUser), Encoding.UTF8, Constants.APPLICATION_JSON); + request.Content = new StringContent(DataModelSerialization.SerializeUser(_currentUser), Encoding.UTF8, Constants.APPLICATION_JSON); return request; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index edd1da99..e57f5135 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -5,8 +5,6 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; - namespace LaunchDarkly.Sdk.Client.Internal.DataSources { internal sealed class PollingDataSource : IDataSource @@ -81,8 +79,8 @@ private async Task UpdateTaskAsync() if (response.statusCode == 200) { var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); - _updateSink.Init(new FullDataSet(flagsDictionary), _user); + var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); + _updateSink.Init(allData, _user); if (_initialized.GetAndSet(true) == false) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index b2c5848e..a671a910 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -81,7 +81,7 @@ public Task Start() _httpProperties, ReportMethod, MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), - JsonUtil.EncodeJson(_user) + DataModelSerialization.SerializeUser(_user) ); } else @@ -90,7 +90,7 @@ public Task Start() _httpProperties, HttpMethod.Get, MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + - Base64.UrlSafeEncode(JsonUtil.EncodeJson(_user))), + Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_user))), null ); } @@ -167,11 +167,13 @@ private void OnError(object sender, EventSource.ExceptionEventArgs e) void HandleMessage(string messageType, string messageData) { + _log.Debug("Event '{0}': {1}", messageType, messageData); switch (messageType) { case Constants.PUT: { - _updateSink.Init(new FullDataSet(JsonUtil.DeserializeFlags(messageData)), _user); + var allData = DataModelSerialization.DeserializeV1Schema(messageData); + _updateSink.Init(allData, _user); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -184,12 +186,13 @@ void HandleMessage(string messageType, string messageData) { var parsed = LdValue.Parse(messageData); var flagkey = parsed.Get(Constants.KEY).AsString; - var featureFlag = JsonUtil.DecodeJson(messageData); - _updateSink.Upsert(flagkey, featureFlag.version, featureFlag, _user); + var featureFlag = DataModelSerialization.DeserializeFlag(messageData); + _updateSink.Upsert(flagkey, featureFlag.ToItemDescriptor(), _user); } catch (Exception ex) { - _log.Error("Error parsing PATCH message {0}: {1}", messageData, LogValues.ExceptionSummary(ex)); + LogHelpers.LogException(_log, "Error parsing PATCH message", ex); + _log.Debug("Message data follows: {0}", messageData); } break; } @@ -200,34 +203,36 @@ void HandleMessage(string messageType, string messageData) var parsed = LdValue.Parse(messageData); int version = parsed.Get(Constants.VERSION).AsInt; string flagKey = parsed.Get(Constants.KEY).AsString; - _updateSink.Delete(flagKey, version, _user); + var deletedItem = new ItemDescriptor(version, null); + _updateSink.Upsert(flagKey, deletedItem, _user); } catch (Exception ex) { - _log.Error("Error parsing DELETE message {0}: {1}", messageData, LogValues.ExceptionSummary(ex)); + LogHelpers.LogException(_log, "Error parsing DELETE message", ex); + _log.Debug("Message data follows: {0}", messageData); } break; } case Constants.PING: { - try + Task.Run(async () => { - Task.Run(async () => + try { var response = await _requestor.FeatureFlagsAsync(); var flagsAsJsonString = response.jsonResponse; - var flagsDictionary = JsonUtil.DeserializeFlags(flagsAsJsonString); - _updateSink.Init(new FullDataSet(flagsDictionary), _user); + var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); + _updateSink.Init(allData, _user); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); } - }); - } - catch (Exception ex) - { - _log.Error("Error in handling PING message: {0}", LogValues.ExceptionSummary(ex)); - } + } + catch (Exception ex) + { + LogHelpers.LogException(_log, "Error in handling PING message", ex); + } + }); break; } default: diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs new file mode 100644 index 00000000..09a19e3d --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs @@ -0,0 +1,29 @@ +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + internal sealed class DefaultPersistentDataStore : IPersistentDataStore + { + private readonly Logger _log; + + internal DefaultPersistentDataStore(Logger log) + { + _log = log; + } + + public void Init(User user, string allData) => + PlatformSpecific.Preferences.Set(Constants.FLAGS_KEY_PREFIX + user.Key, allData, _log); + + string IPersistentDataStore.GetAll(User user) => + PlatformSpecific.Preferences.Get(Constants.FLAGS_KEY_PREFIX + user.Key, null, _log); + + public void Dispose() { } + } + + internal sealed class DefaultPersistentDataStoreFactory : IPersistentDataStoreFactory + { + public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => + new DefaultPersistentDataStore(context.BaseLogger.SubLogger(LogNames.DataStoreSubLog)); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs deleted file mode 100644 index 92472f5f..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentStorage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using LaunchDarkly.Sdk.Client.PlatformSpecific; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class DefaultPersistentStorage : IPersistentStorage - { - private readonly Logger _log; - - internal DefaultPersistentStorage(Logger log) - { - _log = log; - } - - public void Save(string key, string value) => - Preferences.Set(key, value, _log); - - public string GetValue(string key) => - Preferences.Get(key, null, _log); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs deleted file mode 100644 index d33fca13..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagCacheManager.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class FlagCacheManager : IFlagCacheManager - { - private readonly IUserFlagCache inMemoryCache; - private readonly IUserFlagCache deviceCache; - private readonly IFlagChangedEventManager flagChangedEventManager; - - private ReaderWriterLockSlim readWriteLock = new ReaderWriterLockSlim(); - - public FlagCacheManager(IUserFlagCache inMemoryCache, - IUserFlagCache deviceCache, - IFlagChangedEventManager flagChangedEventManager, - User user) - { - this.inMemoryCache = inMemoryCache; - this.deviceCache = deviceCache; - this.flagChangedEventManager = flagChangedEventManager; - - var flagsFromDevice = deviceCache.RetrieveFlags(user); - if (flagsFromDevice != null) - { - inMemoryCache.CacheFlagsForUser(flagsFromDevice, user); - } - } - - public IImmutableDictionary FlagsForUser(User user) - { - readWriteLock.EnterReadLock(); - try - { - return inMemoryCache.RetrieveFlags(user); - } - finally - { - readWriteLock.ExitReadLock(); - } - } - - public void CacheFlagsFromService(IImmutableDictionary flags, User user) - { - List> changes = null; - readWriteLock.EnterWriteLock(); - try - { - var previousFlags = inMemoryCache.RetrieveFlags(user); - deviceCache.CacheFlagsForUser(flags, user); - inMemoryCache.CacheFlagsForUser(flags, user); - - foreach (var flag in flags) - { - if (previousFlags.TryGetValue(flag.Key, out var originalFlag)) - { - if (!originalFlag.value.Equals(flag.Value.value)) - { - if (changes == null) - { - changes = new List>(); - } - changes.Add(Tuple.Create(flag.Key, flag.Value.value, originalFlag.value)); - } - } - } - } - finally - { - readWriteLock.ExitWriteLock(); - } - if (changes != null) - { - foreach (var c in changes) - { - flagChangedEventManager.FlagWasUpdated(c.Item1, c.Item2, c.Item3); - } - } - } - - public FeatureFlag FlagForUser(string flagKey, User user) - { - var flags = FlagsForUser(user); - if (flags.TryGetValue(flagKey, out var featureFlag)) - { - return featureFlag; - } - return null; - } - - public void RemoveFlagForUser(string flagKey, User user) - { - LdValue oldValue = LdValue.Null; - bool existed = false; - readWriteLock.EnterWriteLock(); - try - { - var flagsForUser = inMemoryCache.RetrieveFlags(user); - if (flagsForUser.TryGetValue(flagKey, out var flag)) - { - existed = true; - oldValue = flag.value; - var updatedFlags = flagsForUser.Remove(flagKey); // IImmutableDictionary.Remove() returns a new dictionary - deviceCache.CacheFlagsForUser(updatedFlags, user); - inMemoryCache.CacheFlagsForUser(updatedFlags, user); - } - } - finally - { - readWriteLock.ExitWriteLock(); - } - if (existed) - { - flagChangedEventManager.FlagWasDeleted(flagKey, oldValue); - } - } - - public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) - { - bool changed = false; - LdValue oldValue = LdValue.Null; - readWriteLock.EnterWriteLock(); - try - { - var flagsForUser = inMemoryCache.RetrieveFlags(user); - if (flagsForUser.TryGetValue(flagKey, out var oldFlag)) - { - if (!oldFlag.value.Equals(featureFlag.value)) - { - oldValue = oldFlag.value; - changed = true; - } - } - var updatedFlags = flagsForUser.SetItem(flagKey, featureFlag); // IImmutableDictionary.SetItem() returns a new dictionary - deviceCache.CacheFlagsForUser(updatedFlags, user); - inMemoryCache.CacheFlagsForUser(updatedFlags, user); - } - finally - { - readWriteLock.ExitWriteLock(); - } - if (changed) - { - flagChangedEventManager.FlagWasUpdated(flagKey, featureFlag.value, oldValue); - } - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs new file mode 100644 index 00000000..6ceb48c7 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs @@ -0,0 +1,60 @@ +using System.Collections.Immutable; +using LaunchDarkly.Sdk.Client.Internal.Interfaces; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + internal sealed class InMemoryDataStore : IDataStore + { + private readonly object _writerLock = new object(); + private volatile ImmutableDictionary> _data = + ImmutableDictionary>.Empty; + + public void Preload(User user) { } + + public void Init(User user, FullDataSet allData) + { + var newFlags = allData.Items.ToImmutableDictionary(); + lock (_writerLock) + { + _data = _data.SetItem(user.Key, newFlags); + } + } + + public ItemDescriptor? Get(User user, string key) + { + if (_data.TryGetValue(user.Key, out var flags)) + { + return flags.TryGetValue(key, out var item) ? item : (ItemDescriptor?)null; + } + return null; + } + + public FullDataSet? GetAll(User user) => + _data.TryGetValue(user.Key, out var flags) ? new FullDataSet(flags) : (FullDataSet?)null; + + public bool Upsert(User user, string key, ItemDescriptor item) + { + string userKey = user.Key; + lock (_writerLock) + { + if (_data.TryGetValue(userKey, out var flags)) + { + if (!flags.TryGetValue(key, out var oldItem) || oldItem.Version < item.Version) + { + var newFlags = flags.SetItem(key, item); + _data = _data.SetItem(userKey, newFlags); + return true; + } + return false; + } + var allFlags = ImmutableDictionary.Create().SetItem(key, item); + _data = _data.SetItem(userKey, allFlags); + return true; + } + } + + public void Dispose() { } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs new file mode 100644 index 00000000..2882db2b --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -0,0 +1,116 @@ +using System; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Internal; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + // Internal implementation of combining an in-memory store with a persistent store. + // + // For details on the format of the serialized data we store, see DataModelSerialization. + + internal sealed class PersistentDataStoreWrapper : IDataStore + { + private readonly InMemoryDataStore _inMemoryStore; + private readonly IPersistentDataStore _persistentStore; + private readonly object _writerLock = new object(); + private readonly Logger _log; + + public PersistentDataStoreWrapper( + InMemoryDataStore inMemoryStore, + IPersistentDataStore persistentStore, + Logger log + ) + { + _inMemoryStore = inMemoryStore; + _persistentStore = persistentStore; + _log = log; + } + + public void Preload(User user) + { + lock (_writerLock) + { + if (_inMemoryStore.GetAll(user) is null) + { + string serializedData = null; + try + { + serializedData = _persistentStore.GetAll(user); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to read from persistent store", e); + } + if (serializedData is null) + { + return; + } + try + { + _inMemoryStore.Init(user, DataModelSerialization.DeserializeAll(serializedData)); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to deserialize data from persistent store", e); + } + } + } + } + + public void Init(User user, FullDataSet allData) + { + lock (_writerLock) + { + _inMemoryStore.Init(user, allData); + try + { + _persistentStore.Init(user, DataModelSerialization.SerializeAll(allData)); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to write to persistent store", e); + } + } + } + + public ItemDescriptor? Get(User user, string key) => + _inMemoryStore.Get(user, key); + + public FullDataSet? GetAll(User user) => + _inMemoryStore.GetAll(user); + + public bool Upsert(User user, string key, ItemDescriptor data) + { + lock (_writerLock) + { + if (_inMemoryStore.Upsert(user, key, data)) + { + var allData = _inMemoryStore.GetAll(user); + if (allData != null) + { + try + { + _persistentStore.Init(user, DataModelSerialization.SerializeAll(allData.Value)); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to write to persistent store", e); + } + } + return true; + } + return false; + } + } + + public void Dispose() + { + _inMemoryStore.Dispose(); + _persistentStore.Dispose(); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs deleted file mode 100644 index 52e3bad5..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagDeviceCache.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Immutable; -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class UserFlagDeviceCache : IUserFlagCache - { - private readonly IPersistentStorage persister; - private readonly Logger _log; - - public UserFlagDeviceCache(IPersistentStorage persister, Logger log) - { - this.persister = persister; - _log = log; - } - - void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) - { - var jsonString = JsonUtil.SerializeFlags(flags); - try - { - persister.Save(Constants.FLAGS_KEY_PREFIX + user.Key, jsonString); - } - catch (System.Exception ex) - { - _log.Error("Couldn't set preferences on mobile device: {0}", - LogValues.ExceptionSummary(ex)); - } - } - - IImmutableDictionary IUserFlagCache.RetrieveFlags(User user) - { - try - { - var flagsAsJson = persister.GetValue(Constants.FLAGS_KEY_PREFIX + user.Key); - if (flagsAsJson != null) - { - return JsonUtil.DeserializeFlags(flagsAsJson); - } - } - catch (Exception ex) - { - _log.Error("Couldn't get preferences on mobile device: {0}", - LogValues.ExceptionSummary(ex)); - } - - return ImmutableDictionary.Create(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs deleted file mode 100644 index 8a1b00c5..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserFlagInMemoryCache.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Immutable; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class UserFlagInMemoryCache : IUserFlagCache - { - // For each known user key, store a map of their flags. This is a write-through cache - updates will always - // go to UserFlagDeviceCache as well. The inner dictionaries are immutable; updates are done by updating - // the whole thing (that is safe because updates are only ever done from the streaming/polling thread, and - // since updates should be relatively infrequent, it's not very expensive). - - private readonly ConcurrentDictionary> _allData = - new ConcurrentDictionary>(); - - void IUserFlagCache.CacheFlagsForUser(IImmutableDictionary flags, User user) - { - _allData[user.Key] = flags; - } - - IImmutableDictionary IUserFlagCache.RetrieveFlags(User user) - { - if (_allData.TryGetValue(user.Key, out var flags)) - { - return flags; - } - return ImmutableDictionary.Create(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index d585aa84..d6aa0d22 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -1,40 +1,16 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal.DataSources; -using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal { internal static class Factory { - internal static IFlagCacheManager CreateFlagCacheManager(Configuration configuration, - IPersistentStorage persister, - IFlagChangedEventManager flagChangedEventManager, - User user, - Logger log) - { - if (configuration.FlagCacheManager != null) - { - return configuration.FlagCacheManager; - } - else - { - var inMemoryCache = new UserFlagInMemoryCache(); - var deviceCache = configuration.PersistFlagValues ? new UserFlagDeviceCache(persister, log) as IUserFlagCache : new NullUserFlagCache(); - return new FlagCacheManager(inMemoryCache, deviceCache, flagChangedEventManager, user); - } - } - internal static IConnectivityStateManager CreateConnectivityStateManager(Configuration configuration) { return configuration.ConnectivityStateManager ?? new DefaultConnectivityStateManager(); } - internal static IPersistentStorage CreatePersistentStorage(Configuration configuration, Logger log) - { - return configuration.PersistentStorage ?? new DefaultPersistentStorage(log); - } - internal static IDeviceInfo CreateDeviceInfo(Configuration configuration, Logger log) { return configuration.DeviceInfo ?? new DefaultDeviceInfo(log); diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs new file mode 100644 index 00000000..532db724 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs @@ -0,0 +1,65 @@ +using System; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.DataStores; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.Interfaces +{ + /// + /// Internal interface for the top-level component that reads and writes flag data. + /// + /// + /// This is non-public because applications should not need to customize storage at + /// this level; they can customize the piece. + /// The standard implementations of IDataStore are + /// and . + /// + internal interface IDataStore : IDisposable + { + /// + /// Tells the data store that we are changing the user properties and it should + /// load any previously persisted flag data for the new user into memory. The + /// store does not maintain a "current user" state, since each method has a user + /// parameter, but Preload allows us to load a whole data set at once + /// rather than doing so on individual cache misses. + /// + /// the new user properties + void Preload(User user); + + /// + /// Overwrites the store's contents for a specific user with a serialized data set. + /// + /// the current user + /// + void Init(User user, FullDataSet allData); + + /// + /// Retrieves an individual flag item for a specific user, if available. + /// + /// the current user + /// the flag key + /// an containing either the flag data or a + /// deleted item tombstone, or if the flag key is unknown + ItemDescriptor? Get(User user, string key); + + /// + /// Retrieves all items for a specific user, if available. + /// + /// the current user + /// a containing all flags for the user, or + /// if the user key is unknown + FullDataSet? GetAll(User user); + + /// + /// Updates or inserts an item. For updates, the object will only be updated if the + /// existing version is less than the new version. + /// + /// the current user + /// the flag key + /// the new flag data or deleted item tombstone + /// true if the item was updated; false if it was not updated because the + /// store contains an equal or greater version + bool Upsert(User user, string key, ItemDescriptor data); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs deleted file mode 100644 index ee1319a5..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IFlagCacheManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Immutable; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - internal interface IFlagCacheManager - { - void CacheFlagsFromService(IImmutableDictionary flags, User user); - FeatureFlag FlagForUser(string flagKey, User user); - void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user); - void RemoveFlagForUser(string flagKey, User user); - IImmutableDictionary FlagsForUser(User user); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs deleted file mode 100644 index ffaff499..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IPersistentStorage.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - internal interface IPersistentStorage - { - string GetValue(string key); - void Save(string key, string value); - } - - internal class NullPersistentStorage : IPersistentStorage - { - public string GetValue(string key) => null; - public void Save(string key, string value) { } - } -} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs deleted file mode 100644 index d068605a..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IUserFlagCache.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Immutable; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - internal interface IUserFlagCache - { - void CacheFlagsForUser(IImmutableDictionary flags, User user); - IImmutableDictionary RetrieveFlags(User user); - } - - internal sealed class NullUserFlagCache : IUserFlagCache - { - public void CacheFlagsForUser(IImmutableDictionary flags, User user) { } - public IImmutableDictionary RetrieveFlags(User user) => ImmutableDictionary.Create(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs b/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs deleted file mode 100644 index fc5536d5..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/JsonUtil.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using LaunchDarkly.JsonStream; -using LaunchDarkly.Sdk.Json; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client.Internal -{ - internal class JsonUtil - { - internal static T DecodeJson(string json) where T : IJsonSerializable => - LdJsonSerialization.DeserializeObject(json); - - internal static string EncodeJson(T o) where T : IJsonSerializable => - LdJsonSerialization.SerializeObject(o); - - public static ImmutableDictionary DeserializeFlags(string json) - { - var r = JReader.FromString(json); - try - { - var builder = ImmutableDictionary.CreateBuilder(); - for (var or = r.Object(); or.Next(ref r);) - { - builder.Add(or.Name.ToString(), FeatureFlagJsonConverter.ReadJsonValue(ref r)); - } - return builder.ToImmutable(); - } - catch (Exception e) - { - throw r.TranslateException(e); - } - } - - public static string SerializeFlags(IReadOnlyDictionary flags) - { - var w = JWriter.New(); - using (var ow = w.Object()) - { - foreach (var kv in flags) - { - FeatureFlagJsonConverter.WriteJsonValue(kv.Value, ow.Name(kv.Key)); - } - } - return w.GetString(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 3a72ebbe..0e8d5fa1 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -7,6 +8,7 @@ using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; @@ -31,14 +33,13 @@ public sealed class LdClient : ILdClient readonly LdClientContext _context; readonly IDataSourceFactory _dataSourceFactory; readonly IDataSourceUpdateSink _dataSourceUpdateSink; + readonly IDataStore _dataStore; readonly ConnectionManager _connectionManager; readonly IBackgroundModeManager _backgroundModeManager; readonly IDeviceInfo deviceInfo; readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor _eventProcessor; - readonly IFlagCacheManager flagCacheManager; internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing - readonly IPersistentStorage persister; private readonly Logger _log; // Mutable client state (some state is also in the ConnectionManager) @@ -137,14 +138,21 @@ public event EventHandler FlagChanged _log.Info("Starting LaunchDarkly Client {0}", Version); - persister = Factory.CreatePersistentStorage(configuration, _log); deviceInfo = Factory.CreateDeviceInfo(configuration, _log); flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration, _log); _user = DecorateUser(user); - flagCacheManager = Factory.CreateFlagCacheManager(configuration, persister, flagChangedEventManager, User, _log); - _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(flagCacheManager); + var persistentStore = configuration.PersistentDataStoreFactory is null ? + new DefaultPersistentDataStore(_log.SubLogger(LogNames.DataStoreSubLog)) : + configuration.PersistentDataStoreFactory.CreatePersistentDataStore(_context); + _dataStore = new PersistentDataStoreWrapper( + new InMemoryDataStore(), + persistentStore, + _log.SubLogger(LogNames.DataStoreSubLog) + ); + _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(_dataStore, flagChangedEventManager); + _dataStore.Preload(_user); _dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); @@ -417,7 +425,7 @@ EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(kind)); - var flag = flagCacheManager.FlagForUser(featureKey, User); + var flag = _dataStore.Get(User, featureKey)?.Item; if (flag == null) { if (!Initialized) @@ -477,8 +485,13 @@ private void SendEvaluationEventIfOnline(EventProcessorTypes.EvaluationEvent e) /// public IDictionary AllFlags() { - return flagCacheManager.FlagsForUser(User) - .ToDictionary(p => p.Key, p => p.Value.value); + var data = _dataStore.GetAll(User); + if (data is null) + { + return ImmutableDictionary.Create(); + } + return data.Value.Items.Where(entry => entry.Value.Item != null) + .ToDictionary(p => p.Key, p => p.Value.Item.value); } /// @@ -546,6 +559,8 @@ public async Task IdentifyAsync(User user) _user = newUser; }); + _dataStore.Preload(newUser); + EventProcessorIfEnabled().RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { Timestamp = UnixMillisecondTime.Now, @@ -643,6 +658,7 @@ void Dispose(bool disposing) _backgroundModeManager.BackgroundModeChanged -= OnBackgroundModeChanged; _connectionManager.Dispose(); + _dataStore.Dispose(); _eventProcessor.Dispose(); // Reset the static Instance to null *if* it was referring to this instance diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs index 62ea1b23..03829e8b 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs @@ -1,13 +1,10 @@ -#if NETSTANDARD1_6 -#else -using System; +using System; using System.IO; using System.IO.IsolatedStorage; using System.Linq; using System.Text; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Internal; -#endif namespace LaunchDarkly.Sdk.Client.PlatformSpecific { @@ -20,8 +17,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific // // This is based on the Plugin.Settings plugin (which is what Xamarin Essentials uses for preferences), but greatly // simplified since we only need one data type. See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs - // - // In .NET Standard 1.6, there is no data store. internal static partial class Preferences { @@ -103,17 +98,7 @@ private static T WithStore(Func callback, Logger log) var store = IsolatedStorageFile.GetUserStoreForDomain(); return callback(store); } - catch (Exception e) - { - HandleStoreException(e, log); - return default; - } - } - - private static void HandleStoreException(Exception e, Logger log) - { - if (e is IsolatedStorageException || - e is InvalidOperationException) + catch (Exception e) when (e is IsolatedStorageException || e is InvalidOperationException) { // These exceptions are ones that IsolatedStorageFile methods may throw under conditions that are // unrelated to our code, e.g. filesystem permissions don't allow the store to be used. Since such a @@ -128,12 +113,7 @@ private static void HandleStoreException(Exception e, Logger log) { log.Warn("Persistent storage is unavailable and has been disabled ({0}: {1})", e.GetType(), e.Message); } - } - else - { - // All other errors probably indicate an error in our own code. We don't want to throw these up - // into the SDK; the Preferences API is expected to either work or silently fail. - LogHelpers.LogException(log, "Error in accessing persistent storage", e); + return default(T); } } diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index 41bb02ba..f6f22836 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -14,7 +14,7 @@ public void SdkReturnsAndroidPlatformType() public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").Build(); + var config = TestUtil.TestConfig("mobileKey").Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; @@ -29,7 +29,7 @@ public void UserHasOSAndDeviceAttributesForPlatform() public void CanGetUniqueUserKey() { var anonUser = User.Builder((string)null).Anonymous(true).Build(); - var config = TestUtil.ConfigWithFlagsJson(anonUser, "mobileKey", "{}") + var config = TestUtil.TestConfig("mobileKey") .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index e221b3d9..1fc62718 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -67,7 +67,7 @@ - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 5342941a..5dd9222b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -1,5 +1,6 @@ using System; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Internal; using Xunit; using Xunit.Abstractions; @@ -9,21 +10,21 @@ namespace LaunchDarkly.Sdk.Client [Collection("serialize all tests")] public class BaseTest : IDisposable { - protected readonly ILogAdapter testLogging; + protected readonly LoggingConfigurationBuilder testLogging; protected readonly Logger testLogger; - protected readonly LogCapture logCapture; + protected readonly LogCapture logCapture = Logs.Capture(); - public BaseTest() - { - logCapture = Logs.Capture(); - testLogging = logCapture; - testLogger = logCapture.Logger(""); - } + public BaseTest() : this(capture => capture) { } + + public BaseTest(ITestOutputHelper testOutput) : + this(capture => Logs.ToMultiple(TestLogging.TestOutputAdapter(testOutput), capture)) + { } - public BaseTest(ITestOutputHelper testOutput) : this() + protected BaseTest(Func adapterFn) { - testLogging = Logs.ToMultiple(TestLogging.TestOutputAdapter(testOutput), logCapture); - testLogger = testLogging.Logger(""); + var adapter = adapterFn(logCapture); + testLogger = adapter.Level(LogLevel.Debug).Logger(""); + testLogging = Components.Logging(adapter).Level(LogLevel.Debug); } protected void ClearCachedFlags(User user) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs b/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs deleted file mode 100644 index 18f0b48f..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/BuilderBehavior.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; -using Xunit; - -// THIS CODE WILL BE MOVED into the dotnet-test-helpers project where it can be shared - -namespace LaunchDarkly.TestHelpers -{ - /// - /// Factories for helper classes that provide useful test patterns for builder types. - /// - /// - /// - /// These helpers make it easier to provide thorough test coverage of builder types. - /// It is easy when implementing builders to make basic mistakes like not setting the - /// right property in a setter, not enforcing desired constraints, or not copying all - /// the properties in a copy constructor. - /// - /// - /// The general pattern consists of creating a generic helper for the builder type and - /// the type that it builds, then creating a property helper for each settable property - /// and performing standard assertions with it. Example: - /// - ///

-    ///     // This assumes there is a type MyBuilder whose Build method creates an
-    ///     // instance of MyType, with properties Height and Weight.
-    ///     
-    ///     var tester = BuilderBehavior.For(() => new MyBuilder(), b => b.Build());
-    ///
-    ///     var height = tester.Property(x => x.Height, b => (b, value) => b.Height(value));
-    ///     height.AssertDefault(DefaultHeight);
-    ///     height.AssertCanSet(72);
-    ///     height.AssertSetIsChangedTo(-1, 0); // setter should enforce minimum height of 0
-    ///
-    ///     var weight = tester.Property(x => x.Weight, b => (b, value) => b.Weight(value));
-    ///     weight.AssertDefault(DefaultWeight);
-    ///     weight.AssertCanSet(200);
-    ///     weight.AssertSetIsChangedTo(-1, 0); // setter should enforce minimum weight of 0
-    /// 
- /// - /// It uses Xunit assertion methods. - /// - ///
- public static class BuilderBehavior - { - /// - /// Provides a generic for testing - /// methods of a builder against properties of the type it builds. - /// - /// the builder type - /// the type that it builds - /// function that constructs a - /// function that creates a - /// from a - /// a instance - public static BuildTester For( - Func constructor, Func buildMethod) => - new BuildTester(constructor, buildMethod, null); - - /// - /// Provides a generic for testing - /// methods of a builder against the builder's own internal state. - /// - /// - /// This can be used in cases where it is not feasible for the test code to actually - /// call the builder's build method, for instance if it has unwanted side effects. - /// - /// the builder type - /// function that constructs a - /// an instance - // Use this when we want to test the builder's internal state directly, without - // calling Build - i.e. if the object is difficult to inspect after it's built. - public static InternalStateTester For(Func constructor) => - new InternalStateTester(constructor); - - /// - /// Helper class that provides useful test patterns for a builder type and the - /// type that it builds. - /// - /// - /// Create instances of this class with - /// . - /// - /// the builder type - /// the type that it builds - public sealed class BuildTester - { - private readonly Func _constructor; - internal readonly Func _buildMethod; - internal readonly Func _copyConstructor; - - internal BuildTester(Func constructor, - Func buildMethod, - Func copyConstructor - ) - { - _constructor = constructor; - _buildMethod = buildMethod; - _copyConstructor = copyConstructor; - } - - /// - /// Creates a helper for testing a specific property of the builder. - /// - /// type of the property - /// function that gets that property from the built object - /// function that sets the property in the builder - /// - public IPropertyAssertions Property( - Func getter, - Action builderSetter - ) => - new BuildTesterProperty( - this, getter, builderSetter); - - /// - /// Creates an instance of the builder. - /// - /// a new instance - public TBuilder New() => _constructor(); - - /// - /// Adds the ability to test the builder's copy constructor. - /// - /// - /// The effect of this is that all - /// assertions created from the resulting helper will also verify that copying - /// the builder also copies the value of this property. - /// - /// function that should create a new builder with an - /// identical state to the existing one - /// a copy of the BuilderTestHelper with this additional behavior - public BuildTester WithCopyConstructor( - Func copyConstructor - ) => - new BuildTester(_constructor, _buildMethod, copyConstructor); - } - - /// - /// Similar to , but instead of testing the values of - /// properties in the built object, it inspects the builder directly. - /// - /// - /// Create instances of this class with . - /// - /// the builder type - public class InternalStateTester - { - private readonly Func _constructor; - - internal InternalStateTester(Func constructor) - { - _constructor = constructor; - } - - /// - /// Creates a helper for testing a specific property of the builder. - /// - /// type of the property - /// function that gets that property from the builder's internal state - /// function that sets the property in the builder - /// - public IPropertyAssertions Property( - Func builderGetter, - Action builderSetter - ) => - new InternalStateTesterProperty(this, - builderGetter, builderSetter); - - /// - /// Creates an instance of the builder. - /// - /// a new instance - public TBuilder New() => _constructor(); - } - - /// - /// Assertions provided by the property-specific helpers. - /// - /// type of the property - public interface IPropertyAssertions - { - /// - /// Asserts that the property has the expected value when it has not been set. - /// - /// the expected value - void AssertDefault(TValue defaultValue); - - /// - /// Asserts that calling the setter for a specific value causes the property - /// to have that value. - /// - /// the expected value - void AssertCanSet(TValue newValue); - - /// - /// Asserts that calling the setter for a specific value causes the property - /// to have another specific value for the corresponding property. - /// - /// the value to pass to the setter - /// the expected result value - void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue); - } - - internal class BuildTesterProperty : IPropertyAssertions - { - private readonly BuildTester _owner; - private readonly Func _getter; - private readonly Action _builderSetter; - - internal BuildTesterProperty(BuildTester owner, - Func getter, - Action builderSetter) - { - _owner = owner; - _getter = getter; - _builderSetter = builderSetter; - } - - public void AssertDefault(TValue defaultValue) - { - var b = _owner.New(); - AssertValue(b, defaultValue); - } - - public void AssertCanSet(TValue newValue) - { - AssertSetIsChangedTo(newValue, newValue); - } - - public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) - { - var b = _owner.New(); - _builderSetter(b, attemptedValue); - AssertValue(b, resultingValue); - } - - private void AssertValue(TBuilder b, TValue v) - { - var o = _owner._buildMethod(b); - Assert.Equal(v, _getter(o)); - if (_owner._copyConstructor != null) - { - var b1 = _owner._copyConstructor(o); - var o1 = _owner._buildMethod(b1); - Assert.Equal(v, _getter(o1)); - } - } - } - - internal class InternalStateTesterProperty : IPropertyAssertions - { - private readonly InternalStateTester _owner; - private readonly Func _builderGetter; - private readonly Action _builderSetter; - - internal InternalStateTesterProperty(InternalStateTester owner, - Func builderGetter, - Action builderSetter) - { - _owner = owner; - _builderGetter = builderGetter; - _builderSetter = builderSetter; - } - - public void AssertDefault(TValue defaultValue) - { - Assert.Equal(defaultValue, _builderGetter(_owner.New())); - } - - public void AssertCanSet(TValue newValue) - { - AssertSetIsChangedTo(newValue, newValue); - } - - public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) - { - var b = _owner.New(); - _builderSetter(b, attemptedValue); - Assert.Equal(resultingValue, _builderGetter(b)); - } - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs deleted file mode 100644 index 27d2ce10..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/BuilderTestUtil.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using Xunit; - -namespace LaunchDarkly.Sdk.Client -{ - public static class BuilderTestUtil - { - // Use this when we want to test the effect of builder changes on the object that - // is eventually built. - public static BuilderTestUtil For( - Func constructor, Func buildMethod) => - new BuilderTestUtil(constructor, buildMethod, null); - - // Use this when we want to test the builder's internal state directly, without - // calling Build - i.e. if the object is difficult to inspect after it's built. - public static BuilderInternalTestUtil For(Func constructor) => - new BuilderInternalTestUtil(constructor); - } - - public class BuilderTestUtil - { - private readonly Func _constructor; - internal readonly Func _buildMethod; - internal readonly Func _copyConstructor; - - public BuilderTestUtil(Func constructor, - Func buildMethod, - Func copyConstructor - ) - { - _constructor = constructor; - _buildMethod = buildMethod; - _copyConstructor = copyConstructor; - } - - public BuilderPropertyTestUtil Property( - Func getter, - Action builderSetter - ) => - new BuilderPropertyTestUtil( - this, getter, builderSetter); - - public TBuilder New() => _constructor(); - - public BuilderTestUtil WithCopyConstructor( - Func copyConstructor - ) => - new BuilderTestUtil(_constructor, _buildMethod, copyConstructor); - } - - public class BuilderPropertyTestUtil - { - private readonly BuilderTestUtil _owner; - private readonly Func _getter; - private readonly Action _builderSetter; - - public BuilderPropertyTestUtil(BuilderTestUtil owner, - Func getter, - Action builderSetter) - { - _owner = owner; - _getter = getter; - _builderSetter = builderSetter; - } - - public void AssertDefault(TValue defaultValue) - { - var b = _owner.New(); - AssertValue(b, defaultValue); - } - - public void AssertCanSet(TValue newValue) - { - AssertSetIsChangedTo(newValue, newValue); - } - - public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) - { - var b = _owner.New(); - _builderSetter(b, attemptedValue); - AssertValue(b, resultingValue); - } - - private void AssertValue(TBuilder b, TValue v) - { - var o = _owner._buildMethod(b); - Assert.Equal(v, _getter(o)); - if (_owner._copyConstructor != null) - { - var b1 = _owner._copyConstructor(o); - var o1 = _owner._buildMethod(b); - Assert.Equal(v, _getter(o)); - } - } - } - - public class BuilderInternalTestUtil - { - private readonly Func _constructor; - - public BuilderInternalTestUtil(Func constructor) - { - _constructor = constructor; - } - - public BuilderInternalPropertyTestUtil Property( - Func builderGetter, - Action builderSetter - ) => - new BuilderInternalPropertyTestUtil(this, - builderGetter, builderSetter); - - public TBuilder New() => _constructor(); - } - - public class BuilderInternalPropertyTestUtil - { - private readonly BuilderInternalTestUtil _owner; - private readonly Func _builderGetter; - private readonly Action _builderSetter; - - public BuilderInternalPropertyTestUtil(BuilderInternalTestUtil owner, - Func builderGetter, - Action builderSetter) - { - _owner = owner; - _builderGetter = builderGetter; - _builderSetter = builderSetter; - } - - public void AssertDefault(TValue defaultValue) - { - Assert.Equal(defaultValue, _builderGetter(_owner.New())); - } - - public void AssertCanSet(TValue newValue) - { - AssertSetIsChangedTo(newValue, newValue); - } - - public void AssertSetIsChangedTo(TValue attemptedValue, TValue resultingValue) - { - var b = _owner.New(); - _builderSetter(b, attemptedValue); - Assert.Equal(resultingValue, _builderGetter(b)); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 18a90e3c..6afaa813 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -112,11 +112,11 @@ public void Offline() } [Fact] - public void PersistFlagValues() + public void Persistence() { - var prop = _tester.Property(c => c.PersistFlagValues, (b, v) => b.PersistFlagValues(v)); - prop.AssertDefault(true); - prop.AssertCanSet(false); + var prop = _tester.Property(c => c.PersistentDataStoreFactory, (b, v) => b.Persistence(v)); + prop.AssertDefault(null); + prop.AssertCanSet(Components.NoPersistence); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs b/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs deleted file mode 100644 index 86644585..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/FeatureFlagBuilder.cs +++ /dev/null @@ -1,62 +0,0 @@ -using LaunchDarkly.Sdk.Client.Internal; - -using static LaunchDarkly.Sdk.Client.DataModel; - -namespace LaunchDarkly.Sdk.Client -{ - internal class FeatureFlagBuilder - { - private LdValue _value = LdValue.Null; - private int _version; - private int? _variation; - private int? _flagVersion; -#pragma warning disable 0649 - // Currently trackEvents, trackReason, and debugEventsUntilDate are never set in the tests. That's because those properties - // are only used by DefaultEventProcessor (in LaunchDarkly.CommonSdk), which has its own tests against an abstraction of the - // same properties. - private bool _trackEvents; - private bool _trackReason; - private UnixMillisecondTime? _debugEventsUntilDate; -#pragma warning disable 0649 - private EvaluationReason? _reason; - - public FeatureFlagBuilder() - { - } - - public FeatureFlag Build() - { - return new FeatureFlag(_value, _variation, _reason, _version, _flagVersion, _trackEvents, _trackReason, _debugEventsUntilDate); - } - - public FeatureFlagBuilder Value(LdValue value) - { - _value = value; - return this; - } - - public FeatureFlagBuilder FlagVersion(int? flagVersion) - { - _flagVersion = flagVersion; - return this; - } - - public FeatureFlagBuilder Version(int version) - { - _version = version; - return this; - } - - public FeatureFlagBuilder Variation(int? variation) - { - _variation = variation; - return this; - } - - public FeatureFlagBuilder Reason(EvaluationReason? reason) - { - _reason = reason; - return this; - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs index 9ccd6148..8d3786fd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs @@ -1,8 +1,10 @@ -using System.Collections.Concurrent; -using System.Threading; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.TestUtil; + namespace LaunchDarkly.Sdk.Client { public class FlagChangedEventTests : BaseTest @@ -21,18 +23,18 @@ FlagChangedEventManager Manager() public void CanRegisterListeners() { var manager = Manager(); - var listener1 = new FlagChangedEventSink(); - var listener2 = new FlagChangedEventSink(); - manager.FlagChanged += listener1.Handler; - manager.FlagChanged += listener2.Handler; + var listener1 = new EventSink(); + var listener2 = new EventSink(); + manager.FlagChanged += listener1.Add; + manager.FlagChanged += listener2.Add; - manager.FlagWasUpdated(INT_FLAG, LdValue.Of(7), LdValue.Of(6)); - manager.FlagWasUpdated(DOUBLE_FLAG, LdValue.Of(10.5f), LdValue.Of(9.5f)); + manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(7), LdValue.Of(6), false)); + var event1a = listener1.ExpectValue(); + var event2a = listener2.ExpectValue(); - var event1a = listener1.Await(); - var event1b = listener1.Await(); - var event2a = listener2.Await(); - var event2b = listener2.Await(); + manager.FireEvent(new FlagChangedEventArgs(DOUBLE_FLAG, LdValue.Of(10.5f), LdValue.Of(9.5f), false)); + var event1b = listener1.ExpectValue(); + var event2b = listener2.ExpectValue(); Assert.Equal(INT_FLAG, event1a.Key); Assert.Equal(INT_FLAG, event2a.Key); @@ -57,39 +59,21 @@ public void CanRegisterListeners() public void CanUnregisterListeners() { var manager = Manager(); - var listener1 = new FlagChangedEventSink(); - var listener2 = new FlagChangedEventSink(); - manager.FlagChanged += listener1.Handler; - manager.FlagChanged += listener2.Handler; + var listener1 = new EventSink(); + var listener2 = new EventSink(); + manager.FlagChanged += listener1.Add; + manager.FlagChanged += listener2.Add; - manager.FlagChanged -= listener1.Handler; + manager.FlagChanged -= listener1.Add; - manager.FlagWasUpdated(INT_FLAG, LdValue.Of(7), LdValue.Of(6)); + manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(7), LdValue.Of(6), false)); - var e = listener2.Await(); + var e = listener2.ExpectValue(); Assert.Equal(INT_FLAG, e.Key); Assert.Equal(7, e.NewValue.AsInt); Assert.Equal(6, e.OldValue.AsInt); - // This is pretty hacky, but since we're testing for the *lack* of a call, there's no signal we can wait on. - Thread.Sleep(100); - - Assert.True(listener1.IsEmpty); - } - - [Fact] - public void ListenerGetsUpdatedWhenManagerFlagDeleted() - { - var manager = Manager(); - var listener = new FlagChangedEventSink(); - manager.FlagChanged += listener.Handler; - - manager.FlagWasDeleted(INT_FLAG, LdValue.Of(1)); - - var e = listener.Await(); - Assert.Equal(INT_FLAG, e.Key); - Assert.Equal(1, e.OldValue.AsInt); - Assert.True(e.FlagWasDeleted); + listener1.ExpectNoValue(); } [Fact] @@ -104,38 +88,21 @@ public void ListenerCallIsDeferred() // ensures it won't set Called until after we have checked it. Pass. var manager = Manager(); var locker = new object(); - var called = false; + var called = new AtomicBoolean(false); manager.FlagChanged += (sender, args) => { lock (locker) { - Volatile.Write(ref called, true); + called.GetAndSet(true); } }; lock (locker) { - manager.FlagWasUpdated(INT_FLAG, LdValue.Of(2), LdValue.Of(1)); - Assert.False(Volatile.Read(ref called)); + manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(2), LdValue.Of(1), false)); + Assert.False(called.Get()); } } } - - public class FlagChangedEventSink - { - private BlockingCollection _events = new BlockingCollection(); - - public void Handler(object sender, FlagChangedEventArgs args) - { - _events.Add(args); - } - - public FlagChangedEventArgs Await() - { - return _events.Take(); - } - - public bool IsEmpty => _events.Count == 0; - } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs new file mode 100644 index 00000000..ce69369c --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs @@ -0,0 +1,97 @@ +using LaunchDarkly.Sdk.Json; +using Xunit; + +using static LaunchDarkly.Sdk.Client.TestUtil; +using static LaunchDarkly.TestHelpers.JsonAssertions; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + public class DataModelSerializationTest + { + [Fact] + public void SerializeUser() + { + var user = User.Builder("user-key") + .FirstName("Lucy").LastName("Cat").Build(); + AssertJsonEqual(LdJsonSerialization.SerializeObject(user), + DataModelSerialization.SerializeUser(user)); + } + + [Fact] + public void SerializeFlagWithMinimalProperties() + { + var flag = new FeatureFlagBuilder() + .Version(1) + .Value(LdValue.Of(false)) + .Build(); + AssertJsonEqual(@"{""version"":1,""value"":false}", + DataModelSerialization.SerializeFlag(flag)); + } + + [Fact] + public void SerializeFlagWithAllProperties() + { + var flag1 = new FeatureFlagBuilder() + .Version(1) + .Value(LdValue.Of(false)) + .Variation(2) + .FlagVersion(3) + .Reason(EvaluationReason.OffReason) + .TrackEvents(true) + .DebugEventsUntilDate(UnixMillisecondTime.OfMillis(1234)) + .Build(); + AssertJsonEqual(@"{""version"":1,""value"":false,""variation"":2,""flagVersion"":3," + + @"""reason"":{""kind"":""OFF""},""trackEvents"":true,""debugEventsUntilDate"":1234}", + DataModelSerialization.SerializeFlag(flag1)); + + // make sure we're treating trackReason separately from trackEvents + var flag2 = new FeatureFlagBuilder() + .Version(1) + .Value(LdValue.Of(false)) + .Reason(EvaluationReason.OffReason) + .Variation(2) + .FlagVersion(3) + .TrackReason(true) + .Build(); + AssertJsonEqual(@"{""version"":1,""value"":false,""variation"":2,""flagVersion"":3," + + @"""reason"":{""kind"":""OFF""},""trackReason"":true}", + DataModelSerialization.SerializeFlag(flag2)); + } + + [Fact] + public void SerializeAll() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Build(); + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); + var flag1Json = DataModelSerialization.SerializeFlag(flag1); + var flag2Json = DataModelSerialization.SerializeFlag(flag2); + var deletedVersion = 300; + var allData = new DataSetBuilder() + .Add("key1", flag1) + .Add("key2", flag2) + .AddDeleted("deletedKey", deletedVersion) + .Build(); + var actual = DataModelSerialization.SerializeAll(allData); + var expected = MakeJsonData(allData); + AssertJsonEqual(expected, actual); + } + + [Fact] + public void DeserializeAll() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Build(); + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); + var expectedData = new DataSetBuilder() + .Add("key1", flag1) + .Add("key2", flag2) + .Build(); + var serialized = MakeJsonData(expectedData); + + var actualData1 = DataModelSerialization.DeserializeV1Schema(serialized); + Assert.Equal(expectedData, actualData1); + + var actualData2 = DataModelSerialization.DeserializeAll(serialized); + Assert.Equal(expectedData, actualData2); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs new file mode 100644 index 00000000..529e5a2e --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs @@ -0,0 +1,260 @@ +using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataSources +{ + public class DataSourceUpdateSinkImplTest : BaseTest + { + private readonly InMemoryDataStore _store; + private readonly FlagChangedEventManager _flagChangedEventManager; + private readonly DataSourceUpdateSinkImpl _updateSink; + private readonly User _basicUser = User.WithKey("user-key"); + private readonly User _otherUser = User.WithKey("other-key"); + + public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) + { + _store = new InMemoryDataStore(); + _flagChangedEventManager = new FlagChangedEventManager(testLogger); + _updateSink = new DataSourceUpdateSinkImpl(_store, _flagChangedEventManager); + } + + [Fact] + public void InitPassesDataToStore() + { + var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); + _updateSink.Init(initData, _basicUser); + + Assert.Equal(initData.Items, _store.GetAll(_basicUser).Value.Items); + } + + [Fact] + public void UpsertPassesDataToStore() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Build(); + var initData = new DataSetBuilder().Add("key1", flag1a).Build(); + _updateSink.Init(initData, _basicUser); + + var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(true)).Build(); + + _updateSink.Upsert("key1", flag1b.ToItemDescriptor(), _basicUser); + + Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + + [Fact] + public void NoEventsAreSentForFirstInit() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); + _updateSink.Init(initData, _basicUser); + + events.ExpectNoValue(); + } + + [Fact] + public void NoEventsAreSentForUpsertIfNeverInited() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + _updateSink.Upsert("key1", new FeatureFlagBuilder().Build().ToItemDescriptor(), _basicUser); + + events.ExpectNoValue(); + } + + [Fact] + public void EventIsSentForChangedFlagOnInit() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData1 = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData1, _basicUser); + + var initData2 = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(false)).Variation(1).Build()) + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData2, _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Of(true), e.OldValue); + Assert.Equal(LdValue.Of(false), e.NewValue); + Assert.False(e.FlagWasDeleted); + } + + [Fact] + public void EventIsSentForAddedFlagOnInit() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData1 = new DataSetBuilder() + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData1, _basicUser); + + var initData2 = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(false)).Variation(1).Build()) + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData2, _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Null, e.OldValue); + Assert.Equal(LdValue.Of(false), e.NewValue); + Assert.False(e.FlagWasDeleted); + } + + [Fact] + public void EventIsSentForDeletedFlagOnInit() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData1 = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData1, _basicUser); + + var initData2 = new DataSetBuilder() + .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData2, _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Of(true), e.OldValue); + Assert.Equal(LdValue.Null, e.NewValue); + Assert.True(e.FlagWasDeleted); + } + + [Fact] + public void EventIsSentForChangedFlagOnUpsert() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData, _basicUser); + + _updateSink.Upsert("key1", + new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), + _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Of(true), e.OldValue); + Assert.Equal(LdValue.Of(false), e.NewValue); + Assert.False(e.FlagWasDeleted); + } + + [Fact] + public void EventIsSentForAddedFlagOnUpsert() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData = new DataSetBuilder() + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData, _basicUser); + + _updateSink.Upsert("key1", + new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), + _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Null, e.OldValue); + Assert.Equal(LdValue.Of(false), e.NewValue); + Assert.False(e.FlagWasDeleted); + } + + [Fact] + public void EventIsSentForDeletedFlagOnUpsert() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData, _basicUser); + + _updateSink.Upsert("key1", new ItemDescriptor(101, null), _basicUser); + + var e = events.ExpectValue(); + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Of(true), e.OldValue); + Assert.Equal(LdValue.Null, e.NewValue); + Assert.True(e.FlagWasDeleted); + } + + [Fact] + public void EventIsNotSentIfUpsertFailsDueToLowerVersion() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initData = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) + .Build(); + _updateSink.Init(initData, _basicUser); + + _updateSink.Upsert("key1", + new FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), + _basicUser); + + events.ExpectNoValue(); + } + + [Fact] + public void ValueChangesAreTrackedSeparatelyForEachUser() + { + var events = new EventSink(); + _flagChangedEventManager.FlagChanged += events.Add; + + var initDataForBasicUser = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("a")).Variation(1).Build()) + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("b")).Variation(2).Build()) + .Build(); + _updateSink.Init(initDataForBasicUser, _basicUser); + + var initDataForOtherUser = new DataSetBuilder() + .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("c")).Variation(3).Build()) + .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("d")).Variation(4).Build()) + .Build(); + _updateSink.Init(initDataForOtherUser, _otherUser); + + events.ExpectNoValue(); + + _updateSink.Upsert("key1", + new FeatureFlagBuilder().Version(101).Value(LdValue.Of("c")).Variation(3).Build().ToItemDescriptor(), + _basicUser); + + var e = events.ExpectValue(); + + Assert.Equal("key1", e.Key); + Assert.Equal(LdValue.Of("a"), e.OldValue); + Assert.Equal(LdValue.Of("c"), e.NewValue); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 5cc99820..af116fe7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -6,6 +6,8 @@ using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.TestHelpers.JsonAssertions; + namespace LaunchDarkly.Sdk.Client.Internal.DataSources { // End-to-end tests of this component against an embedded HTTP server. @@ -105,7 +107,7 @@ string expectedQuery Assert.Equal(expectedPath, req.Path); Assert.Equal(expectedQuery, req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); - TestUtil.AssertJsonEquals(LdValue.Parse(LdJsonSerialization.SerializeObject(_user)), + AssertJsonEqual(LdJsonSerialization.SerializeObject(_user), TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 18377fa3..83a83c72 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -1,7 +1,6 @@ using System; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; using Xunit; using Xunit.Abstractions; @@ -15,7 +14,7 @@ public class PollingDataSourceTest : BaseTest "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + "}"; - IFlagCacheManager mockFlagCacheManager; + private InMemoryDataStore _store = new InMemoryDataStore(); User user; public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } @@ -23,11 +22,9 @@ public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { IDataSource MakeDataSource() { var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); - var stubbedFlagCache = new UserFlagInMemoryCache(); - mockFlagCacheManager = new MockFlagCacheManager(stubbedFlagCache); user = User.WithKey("user1Key"); return new PollingDataSource( - new DataSourceUpdateSinkImpl(mockFlagCacheManager), + new DataSourceUpdateSinkImpl(_store, new FlagChangedEventManager(testLogger)), user, mockFeatureFlagRequestor, TimeSpan.FromSeconds(30), @@ -48,8 +45,8 @@ public void StartWaitsUntilFlagCacheFilled() var dataSource = MakeDataSource(); var initTask = dataSource.Start(); var unused = initTask.Wait(TimeSpan.FromSeconds(1)); - var flags = mockFlagCacheManager.FlagsForUser(user); - Assert.Equal(3, flags.Count); + var flags = _store.GetAll(user); + Assert.Equal(3, flags.Value.Items.Count); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 65c928c8..bc8c62ed 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -1,10 +1,9 @@ using System; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using LaunchDarkly.EventSource; using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Internal.Http; using Xunit; using Xunit.Abstractions; @@ -24,7 +23,7 @@ public class StreamingDataSourceTest : BaseTest private EventSourceMock mockEventSource; private TestEventSourceFactory eventSourceFactory; - private IFlagCacheManager mockFlagCacheMgr; + private MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); private IFeatureFlagRequestor mockRequestor; private Uri baseUri; private TimeSpan initialReconnectDelay = StreamingDataSourceBuilder.DefaultInitialReconnectDelay; @@ -34,7 +33,6 @@ public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { mockEventSource = new EventSourceMock(); eventSourceFactory = new TestEventSourceFactory(mockEventSource); - mockFlagCacheMgr = new MockFlagCacheManager(new UserFlagInMemoryCache()); mockRequestor = new MockFeatureFlagRequestor(initialFlagsJson); baseUri = new Uri("http://example"); } @@ -42,7 +40,7 @@ public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) private StreamingDataSource MakeStartedStreamingDataSource() { var dataSource = new StreamingDataSource( - new DataSourceUpdateSinkImpl(mockFlagCacheMgr), + _updateSink, user, baseUri, withReasons, @@ -116,13 +114,13 @@ public void PutStoresFeatureFlags() { MakeStartedStreamingDataSource(); // should be empty before PUT message arrives - var flagsInCache = mockFlagCacheMgr.FlagsForUser(user); - Assert.Empty(flagsInCache); + _updateSink.Actions.ExpectNoValue(); PUTMessageSentToProcessor(); - flagsInCache = mockFlagCacheMgr.FlagsForUser(user); - Assert.NotEmpty(flagsInCache); - int intFlagValue = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; + + var gotData = _updateSink.ExpectInit(user); + var gotItem = gotData.Items.First(item => item.Key == "int-flag"); + int intFlagValue = gotItem.Value.Item.value.AsInt; Assert.Equal(15, intFlagValue); } @@ -132,34 +130,15 @@ public void PatchUpdatesFeatureFlag() // before PATCH, fill in flags MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(15, intFlagFromPUT); + _updateSink.ExpectInit(user); //PATCH to update 1 flag MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("patch", UpdatedFlag(), null)); mockEventSource.RaiseMessageRcvd(eventArgs); //verify flag has changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(99, flagFromPatch); - } - - [Fact] - public void PatchDoesnotUpdateFlagIfVersionIsLower() - { - // before PATCH, fill in flags - MakeStartedStreamingDataSource(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(15, intFlagFromPUT); - - //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("patch", UpdatedFlagWithLowerVersion(), null)); - mockEventSource.RaiseMessageRcvd(eventArgs); - - //verify flag has not changed - int flagFromPatch = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(15, flagFromPatch); + var gotItem = _updateSink.ExpectUpsert(user, "int-flag"); + Assert.Equal(99, gotItem.Item.value.AsInt); } [Fact] @@ -168,50 +147,27 @@ public void DeleteRemovesFeatureFlag() // before DELETE, fill in flags, test it's there MakeStartedStreamingDataSource(); PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(15, intFlagFromPUT); + _updateSink.ExpectInit(user); // DELETE int-flag MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("delete", DeleteFlag(), null)); - mockEventSource.RaiseMessageRcvd(eventArgs); - - // verify flag was deleted - Assert.Null(mockFlagCacheMgr.FlagForUser("int-flag", user)); + mockEventSource.RaiseMessageRcvd(eventArgs); + + // verify flag was deleted + var gotItem = _updateSink.ExpectUpsert(user, "int-flag"); + Assert.Null(gotItem.Item); } [Fact] - public void DeleteDoesnotRemoveFeatureFlagIfVersionIsLower() + public void PingCausesPoll() { - // before DELETE, fill in flags, test it's there MakeStartedStreamingDataSource(); - PUTMessageSentToProcessor(); - var intFlagFromPUT = mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt; - Assert.Equal(15, intFlagFromPUT); - - // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("delete", DeleteFlagWithLowerVersion(), null)); - mockEventSource.RaiseMessageRcvd(eventArgs); - - // verify flag was not deleted - Assert.NotNull(mockFlagCacheMgr.FlagForUser("int-flag", user)); - } - - [Fact] - public async void PingCausesPoll() - { - MakeStartedStreamingDataSource(); - mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("ping", null, null))); - var deadline = DateTime.Now.Add(TimeSpan.FromSeconds(5)); - while (DateTime.Now < deadline) - { - await Task.Delay(TimeSpan.FromMilliseconds(10)); - if (mockFlagCacheMgr.FlagsForUser(user).Count > 0) - { - Assert.Equal(15, mockFlagCacheMgr.FlagForUser("int-flag", user).value.AsInt); - return; - } - } - Assert.True(false, "timed out waiting for polled flags"); + mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("ping", null, null))); + + var gotData = _updateSink.ExpectInit(user); + var gotItem = gotData.Items.First(item => item.Key == "int-flag"); + int intFlagValue = gotItem.Value.Item.value.AsInt; + Assert.Equal(15, intFlagValue); } string UpdatedFlag() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs deleted file mode 100644 index 6c3afda2..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagCacheManagerTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using Xunit; -using Xunit.Abstractions; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - public class FlagCacheManagerTests : BaseTest - { - private const string initialFlagsJson = "{" + - "\"int-flag\":{\"value\":15}," + - "\"float-flag\":{\"value\":13.5}," + - "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + - "}"; - - IUserFlagCache deviceCache = new UserFlagInMemoryCache(); - IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); - FlagChangedEventManager listenerManager; - - User user = User.WithKey("someKey"); - - public FlagCacheManagerTests(ITestOutputHelper testOutput) : base(testOutput) - { - listenerManager = new FlagChangedEventManager(testLogger); - } - - IFlagCacheManager ManagerWithCachedFlags() - { - var flagCacheManager = new FlagCacheManager(deviceCache, inMemoryCache, listenerManager, user); - var flags = TestUtil.DecodeFlagsJson(initialFlagsJson); - flagCacheManager.CacheFlagsFromService(flags, user); - return flagCacheManager; - } - - [Fact] - public void CacheFlagsShouldStoreFlagsInDeviceCache() - { - var flagCacheManager = ManagerWithCachedFlags(); - var cachedDeviceFlags = deviceCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.AsInt); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.AsString); - Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.AsFloat); - } - - [Fact] - public void CacheFlagsShouldAlsoStoreFlagsInMemoryCache() - { - var flagCacheManager = ManagerWithCachedFlags(); - var cachedDeviceFlags = inMemoryCache.RetrieveFlags(user); - Assert.Equal(15, cachedDeviceFlags["int-flag"].value.AsInt); - Assert.Equal("markw@magenic.com", cachedDeviceFlags["string-flag"].value.AsString); - Assert.Equal(13.5, cachedDeviceFlags["float-flag"].value.AsFloat); - } - - [Fact] - public void CanRemoveFlagForUser() - { - var manager = ManagerWithCachedFlags(); - manager.RemoveFlagForUser("int-key", user); - Assert.Null(manager.FlagForUser("int-key", user)); - } - - [Fact] - public void CanUpdateFlagForUser() - { - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(5)).Version(12).Build(); - flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); - var updatedFlagFromCache = flagCacheManager.FlagForUser("int-flag", user); - Assert.Equal(5, updatedFlagFromCache.value.AsInt); - Assert.Equal(12, updatedFeatureFlag.version); - } - - [Fact] - public void UpdateFlagSendsFlagChangeEvent() - { - var listener = new FlagChangedEventSink(); - listenerManager.FlagChanged += listener.Handler; - - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(7)).Build(); - - flagCacheManager.UpdateFlagForUser("int-flag", updatedFeatureFlag, user); - - var e = listener.Await(); - Assert.Equal("int-flag", e.Key); - Assert.Equal(7, e.NewValue.AsInt); - Assert.False(e.FlagWasDeleted); - } - - [Fact] - public void RemoveFlagSendsFlagChangeEvent() - { - var listener = new FlagChangedEventSink(); - listenerManager.FlagChanged += listener.Handler; - - var flagCacheManager = ManagerWithCachedFlags(); - var updatedFeatureFlag = new FeatureFlagBuilder().Value(LdValue.Of(7)).Build(); - flagCacheManager.RemoveFlagForUser("int-flag", user); - - var e = listener.Await(); - Assert.Equal("int-flag", e.Key); - Assert.True(e.FlagWasDeleted); - } - - [Fact] - public void CacheFlagsFromServiceUpdatesListenersIfFlagValueChanged() - { - var listener = new FlagChangedEventSink(); - listenerManager.FlagChanged += listener.Handler; - - var flagCacheManager = ManagerWithCachedFlags(); - var newFlagsJson = "{\"int-flag\":{\"value\":5}}"; - flagCacheManager.CacheFlagsFromService(TestUtil.DecodeFlagsJson(newFlagsJson), user); - - var e = listener.Await(); - Assert.Equal("int-flag", e.Key); - Assert.Equal(5, e.NewValue.AsInt); - Assert.False(e.FlagWasDeleted); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs new file mode 100644 index 00000000..fdeda517 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class InMemoryDataStoreTest + { + private readonly InMemoryDataStore _store = new InMemoryDataStore(); + private readonly User _basicUser = User.WithKey("user-key"); + private readonly User _otherUser = User.WithKey("other-key"); + + [Fact] + public void GetForUnknownUser() + { + Assert.Null(_store.Get(_basicUser, "key1")); + } + + [Fact] + public void GetUnknownFlagForKnownUser() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("key1", flag1) + .Build(); + _store.Init(_basicUser, initData); + + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(false)).Build(); + var otherData = new DataSetBuilder().Add("key2", flag2).Build(); + _store.Init(_otherUser, otherData); + + Assert.Null(_store.Get(_basicUser, "key2")); + } + + [Fact] + public void GetDeletedFlagForKnownUser() + { + var initData = new DataSetBuilder() + .AddDeleted("key2", 200) + .Build(); + _store.Init(_basicUser, initData); + + Assert.Equal(new ItemDescriptor(200, null), _store.Get(_basicUser, "key2")); + } + + [Fact] + public void GetAllForUnknownUser() + { + Assert.Null(_store.GetAll(_basicUser)); + } + + [Fact] + public void GetAllForKnownUser() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("key1", flag1) + .Add("key2", flag2) + .AddDeleted("deletedKey", 300) + .Build(); + + _store.Init(_basicUser, initData); + + var otherData = new DataSetBuilder().AddDeleted("key1", 100).Build(); + _store.Init(_otherUser, otherData); + + var result = _store.GetAll(_basicUser); + Assert.NotNull(result); + Assert.Equal(initData, result.Value); + } + + [Fact] + public void UpsertAddsFlagForUnknownUser() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + + var updated = _store.Upsert(_basicUser, "key1", flag1.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag1.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + + [Fact] + public void UpsertAddsFlagForKnownUser() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("key1", flag1) + .Build(); + + _store.Init(_basicUser, initData); + + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); + + var updated = _store.Upsert(_basicUser, "key2", flag2.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag2.ToItemDescriptor(), _store.Get(_basicUser, "key2")); + } + + [Fact] + public void UpsertUpdatesFlagForKnownUser() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var expected = new DataSetBuilder() + .Add("key1", flag1a) + .Build(); + + _store.Init(_basicUser, expected); + + var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Build(); + + var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + + [Fact] + public void UpsertDeletesFlagForKnownUser() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var expected = new DataSetBuilder() + .Add("key1", flag1a) + .Build(); + + _store.Init(_basicUser, expected); + + var flag1Deleted = new ItemDescriptor(101, null); + + var updated = _store.Upsert(_basicUser, "key1", flag1Deleted); + Assert.True(updated); + + Assert.Equal(flag1Deleted, _store.Get(_basicUser, "key1")); + } + + [Fact] + public void UpsertUndeletesFlagForKnownUser() + { + var expected = new DataSetBuilder() + .AddDeleted("key1", 100) + .Build(); + + _store.Init(_basicUser, expected); + + var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Build(); + + var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + + [Fact] + public void UpsertDoesNotUpdateFlagForKnownUserWithLowerVersion() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var expected = new DataSetBuilder() + .Add("key1", flag1a) + .Build(); + + _store.Init(_basicUser, expected); + + var flag1b = new FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Build(); + + var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); + Assert.False(updated); + + Assert.Equal(flag1a.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + + [Fact] + public void UpsertDoesNotDeleteFlagForKnownUserWithLowerVersion() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var expected = new DataSetBuilder() + .Add("key1", flag1a) + .Build(); + + _store.Init(_basicUser, expected); + + var deletedDesc = new ItemDescriptor(99, null); + + var updated = _store.Upsert(_basicUser, "key1", deletedDesc); + Assert.False(updated); + + Assert.Equal(flag1a.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs new file mode 100644 index 00000000..8b099717 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -0,0 +1,135 @@ +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class PersistentDataStoreWrapperTest : BaseTest + { + private static readonly User _basicUser = User.WithKey("user-key"); + private static readonly User _otherUser = User.WithKey("user-key"); + + private readonly InMemoryDataStore _inMemoryStore; + private readonly MockPersistentDataStore _persistentStore; + private readonly PersistentDataStoreWrapper _wrapper; + + public PersistentDataStoreWrapperTest(ITestOutputHelper testOutput) : base(testOutput) + { + _inMemoryStore = new InMemoryDataStore(); + _persistentStore = new MockPersistentDataStore(); + _wrapper = new PersistentDataStoreWrapper(_inMemoryStore, _persistentStore, testLogger); + } + + [Fact] + public void PreloadDoesNotInitializeStoreForUserWhoIsNotInPersistentStore() + { + _wrapper.Preload(_basicUser); + + Assert.Null(_inMemoryStore.GetAll(_basicUser)); + } + + [Fact] + public void PreloadDoesNotOverwriteStoreIfDataIsAlreadyCached() + { + var inMemoryData = new DataSetBuilder() + .Add("flag1", 2, LdValue.Of(true), 0) + .Build(); + _inMemoryStore.Init(_basicUser, inMemoryData); + + var persistedData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(false), 1) + .Build(); + _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + + _wrapper.Preload(_basicUser); + + Assert.Equal(inMemoryData, _inMemoryStore.GetAll(_basicUser)); + } + + [Fact] + public void PreloadGetsDataFromPersistentStoreIfNotAlreadyCached() + { + var persistedData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(false), 1) + .Build(); + _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + + _wrapper.Preload(_basicUser); + + Assert.Equal(persistedData, _inMemoryStore.GetAll(_basicUser)); + } + + [Fact] + public void InitWritesToBothMemoryAndPersistentStore() + { + var data = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Build(); + + _wrapper.Init(_basicUser, data); + + Assert.Equal(data, _inMemoryStore.GetAll(_basicUser)); + + Assert.Equal(data, DataModelSerialization.DeserializeAll(_persistentStore.GetAll(_basicUser))); + } + + [Fact] + public void GetReadsOnlyInMemoryStore() + { + var flag1a = new FeatureFlagBuilder().Version(2).Value(true).Variation(0).Build(); + var inMemoryData = new DataSetBuilder() + .Add("flag1", flag1a) + .Build(); + _inMemoryStore.Init(_basicUser, inMemoryData); + + var flag1b = new FeatureFlagBuilder().Version(1).Value(false).Variation(1).Build(); + var persistedData = new DataSetBuilder() + .Add("flag1", flag1b) + .Build(); + _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + + Assert.Equal(flag1a.ToItemDescriptor(), _wrapper.Get(_basicUser, "flag1")); + } + + [Fact] + public void GetAllReadsOnlyInMemoryStore() + { + var inMemoryData = new DataSetBuilder() + .Add("flag1", 2, LdValue.Of(true), 0) + .Build(); + _inMemoryStore.Init(_basicUser, inMemoryData); + + var persistedData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(false), 1) + .Build(); + _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + + Assert.Equal(inMemoryData, _wrapper.GetAll(_basicUser)); + } + + [Fact] + public void UpsertPersistsAllDataAfterUpdatingMemory() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(true).Variation(0).Build(); + var flag1b = new FeatureFlagBuilder().Version(101).Value(false).Variation(1).Build(); + var flag2 = new FeatureFlagBuilder().Version(200).Value(true).Variation(0).Build(); + var initialData = new DataSetBuilder() + .Add("flag1", flag1a) + .Add("flag2", flag2) + .Build(); + + _wrapper.Init(_basicUser, initialData); + + _wrapper.Upsert(_basicUser, "flag1", flag1b.ToItemDescriptor()); + + Assert.Equal(flag1b.ToItemDescriptor(), _inMemoryStore.Get(_basicUser, "flag1")); + Assert.Equal(flag2.ToItemDescriptor(), _inMemoryStore.Get(_basicUser, "flag2")); + + var updatedData = new DataSetBuilder() + .Add("flag1", flag1b) + .Add("flag2", flag2) + .Build(); + + Assert.Equal(updatedData, DataModelSerialization.DeserializeAll(_persistentStore.GetAll(_basicUser))); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs deleted file mode 100644 index 024e03f6..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserFlagCacheTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using Xunit; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - public class UserFlagCacheTests : BaseTest - { - IUserFlagCache inMemoryCache = new UserFlagInMemoryCache(); - User user1 = User.WithKey("user1Key"); - User user2 = User.WithKey("user2Key"); - - [Fact] - public void CanCacheFlagsInMemory() - { - var jsonFlags = @"{""flag1"":{""value"":1},""flag2"":{""value"":2}}"; - var flags = TestUtil.DecodeFlagsJson(jsonFlags); - inMemoryCache.CacheFlagsForUser(flags, user1); - var flagsRetrieved = inMemoryCache.RetrieveFlags(user1); - Assert.Equal(2, flagsRetrieved.Count); - Assert.Equal(flags["flag1"], flagsRetrieved["flag1"]); - Assert.Equal(flags["flag2"], flagsRetrieved["flag2"]); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index e7d86ac4..62a49d35 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -9,6 +9,8 @@ using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + namespace LaunchDarkly.Sdk.Client { // Tests of an LDClient instance doing actual HTTP against an embedded server. These aren't intended to cover @@ -23,15 +25,13 @@ public class LdClientEndToEndTests : BaseTest private static readonly User _user = User.WithKey("foo"); private static readonly User _otherUser = User.WithKey("bar"); - private static readonly IDictionary _flagData1 = new Dictionary - { - { "flag1", "value1" } - }; + private static readonly FullDataSet _flagData1 = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of("value1"), 0) + .Build(); - private static readonly IDictionary _flagData2 = new Dictionary - { - { "flag1", "value2" } - }; + private static readonly FullDataSet _flagData2 = new DataSetBuilder() + .Add("flag1", 2, LdValue.Of("value2"), 1) + .Build(); public static readonly IEnumerable PollingAndStreaming = new List { @@ -102,7 +102,7 @@ public void InitCanTimeOutSync() using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized); - Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + Assert.Null(client.StringVariation(_flagData1.Items.First().Key, null)); Assert.True(logCapture.HasMessageWithText(Logging.LogLevel.Warn, "Client did not successfully initialize within 200 milliseconds.")); } @@ -248,7 +248,7 @@ public void IdentifyCanTimeOutSync(UpdateMode mode) var success = client.Identify(_otherUser, TimeSpan.FromMilliseconds(100)); Assert.False(success); Assert.False(client.Initialized); - Assert.Null(client.StringVariation(_flagData1.First().Key, null)); + Assert.Null(client.StringVariation(_flagData1.Items.First().Key, null)); } } } @@ -267,7 +267,7 @@ string expectedPath var config = Configuration.Builder(_mobileKey) .DataSource(MockPollingProcessor.Factory("{}")) .Events(Components.SendEvents().BaseUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath))) - .PersistFlagValues(false) + .Persistence(Components.NoPersistence) .Build(); using (var client = TestUtil.CreateClient(config, _user)) @@ -291,7 +291,8 @@ public void OfflineClientUsesCachedFlagsSync() ClearCachedFlags(_user); try { - var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); + // resetting Persistence to the default enables persistent storage + var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.Persistence(null)); using (var client = TestUtil.CreateClient(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -321,7 +322,8 @@ public async Task OfflineClientUsesCachedFlagsAsync() ClearCachedFlags(_user); try { - var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.PersistFlagValues(true)); + // resetting Persistence to the default enables persistent storage + var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.Persistence(null)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyFlagValues(client, _flagData1); @@ -356,7 +358,7 @@ public async Task BackgroundModeForcesPollingAsync() var config = BaseConfig(builder => builder .BackgroundModeManager(mockBackgroundModeManager) .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollingIntervalWithoutMinimum(backgroundInterval)) - .PersistFlagValues(false)); + ); using (var client = await TestUtil.CreateClientAsync(config, _user)) { @@ -374,14 +376,14 @@ public async Task BackgroundModeForcesPollingAsync() mockBackgroundModeManager.UpdateBackgroundMode(true); - await receivedChangeSignal.WaitAsync(); + Assert.True(await receivedChangeSignal.WaitAsync(TimeSpan.FromSeconds(5))); VerifyFlagValues(client, _flagData2); // Now switch back to streaming switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); - await receivedChangeSignal.WaitAsync(); + Assert.True(await receivedChangeSignal.WaitAsync(TimeSpan.FromSeconds(5))); VerifyFlagValues(client, _flagData1); } } @@ -404,7 +406,7 @@ public async Task BackgroundModePollingCanBeDisabledAsync() .BackgroundModeManager(mockBackgroundModeManager) .EnableBackgroundUpdating(false) .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollInterval(backgroundInterval)) - .PersistFlagValues(false)); + ); using (var client = await TestUtil.CreateClientAsync(config, _user)) { @@ -428,7 +430,7 @@ public async Task BackgroundModePollingCanBeDisabledAsync() switchable.Target = SetupResponse(_flagData2, UpdateMode.Streaming); mockBackgroundModeManager.UpdateBackgroundMode(false); - await receivedChangeSignal.WaitAsync(); + Assert.True(await receivedChangeSignal.WaitAsync(TimeSpan.FromSeconds(5))); VerifyFlagValues(client, _flagData2); } } @@ -441,7 +443,7 @@ public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) using (var server = HttpServer.Start(SetupResponse(_flagData1, mode))) { ClearCachedFlags(_user); - var config = BaseConfig(server.Uri, mode, builder => builder.Offline(true).PersistFlagValues(false)); + var config = BaseConfig(server.Uri, mode, builder => builder.Offline(true)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { VerifyNoFlagValues(client, _flagData1); @@ -462,11 +464,10 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) // definitely don't want that. Verify that we're disabling that behavior when we parse flags. const string dateLikeString1 = "1970-01-01T00:00:01.001Z"; const string dateLikeString2 = "1970-01-01T00:00:01Z"; - var flagData = new Dictionary - { - { "flag1", dateLikeString1 }, - { "flag2", dateLikeString2 } - }; + var flagData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(dateLikeString1), 0) + .Add("flag2", 1, LdValue.Of(dateLikeString2), 0) + .Build(); using (var server = HttpServer.Start(SetupResponse(flagData, mode))) { var config = BaseConfig(server.Uri, mode); @@ -483,7 +484,7 @@ private Configuration BaseConfig(Func data, UpdateMode mode) => + private Handler SetupResponse(FullDataSet data, UpdateMode mode) => mode.IsStreaming ? Handlers.SSE.Start() .Then(Handlers.SSE.Event("put", PollingData(data))) @@ -529,42 +530,26 @@ private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) return req; } - private void VerifyFlagValues(ILdClient client, IDictionary flags) + private void VerifyFlagValues(ILdClient client, FullDataSet flags) { Assert.True(client.Initialized); - foreach (var e in flags) + foreach (var e in flags.Items) { - Assert.Equal(e.Value, client.StringVariation(e.Key, null)); + Assert.Equal(e.Value.Item.value, client.JsonVariation(e.Key, LdValue.Null)); } } - private void VerifyNoFlagValues(ILdClient client, IDictionary flags) + private void VerifyNoFlagValues(ILdClient client, FullDataSet flags) { Assert.True(client.Initialized); - foreach (var e in flags) + foreach (var e in flags.Items) { - Assert.Null(client.StringVariation(e.Key, null)); + Assert.Equal(LdValue.Null, client.JsonVariation(e.Key, LdValue.Null)); } } - private static LdValue FlagJson(string key, string value) - { - return LdValue.ObjectFrom(new Dictionary - { - { "key", LdValue.Of(key) }, - { "value", LdValue.Of(value) } - }); - } - - private static string PollingData(IDictionary flags) - { - var d = new Dictionary(); - foreach (var e in flags) - { - d.Add(e.Key, FlagJson(e.Key, e.Value)); - } - return LdValue.ObjectFrom(d).ToJsonString(); - } + private static string PollingData(FullDataSet flags) => + TestUtil.MakeJsonData(flags); } public class UpdateMode diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index 2a2ffcc4..e2fc43af 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -5,7 +5,7 @@ LaunchDarkly.Sdk.Client - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 3f41bb7d..08fd3347 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -1,8 +1,10 @@ using System; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client @@ -15,8 +17,14 @@ public class LdClientEventTests : BaseTest public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { _factory = new SingleEventProcessorFactory(eventProcessor); - } - + } + + public LdClient MakeClient(User user) => + MakeClient(user, new DataSetBuilder().Build()); + + public LdClient MakeClient(User user, FullDataSet initialData) => + MakeClient(user, DataModelSerialization.SerializeAll(initialData)); + public LdClient MakeClient(User user, string flagsJson) { var config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); @@ -27,7 +35,7 @@ public LdClient MakeClient(User user, string flagsJson) [Fact] public void IdentifySendsIdentifyEvent() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { User user1 = User.WithKey("userkey1"); client.Identify(user1, TimeSpan.FromSeconds(1)); @@ -40,7 +48,7 @@ public void IdentifySendsIdentifyEvent() [Fact] public void TrackSendsCustomEvent() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { client.Track("eventkey"); Assert.Collection(eventProcessor.Events, @@ -58,7 +66,7 @@ public void TrackSendsCustomEvent() [Fact] public void TrackWithDataSendsCustomEvent() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { LdValue data = LdValue.Of("hi"); client.Track("eventkey", data); @@ -77,7 +85,7 @@ public void TrackWithDataSendsCustomEvent() [Fact] public void TrackWithMetricValueSendsCustomEvent() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { LdValue data = LdValue.Of("hi"); double metricValue = 1.5; @@ -100,7 +108,7 @@ public void AliasSendsAliasEvent() User oldUser = User.Builder("anon-key").Anonymous(true).Build(); User newUser = User.WithKey("real-key"); - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { client.Alias(user, oldUser); Assert.Collection(eventProcessor.Events, @@ -119,7 +127,7 @@ public void IdentifySendsAliasEventFromAnonUserToNonAnonUserIfNotOptedOut() User oldUser = User.Builder("anon-key").Anonymous(true).Build(); User newUser = User.WithKey("real-key"); - using (LdClient client = MakeClient(oldUser, "{}")) + using (LdClient client = MakeClient(oldUser)) { User actualOldUser = client.User; // so we can get any automatic properties that the client added client.Identify(newUser, TimeSpan.FromSeconds(1)); @@ -166,7 +174,7 @@ public void IdentifyDoesNotSendAliasEventIfNewUserIsAnonymousOrOldUserIsNot( User oldUser = User.Builder("old-key").Anonymous(oldAnon).Build(); User newUser = User.Builder("new-key").Anonymous(newAnon).Build(); - using (LdClient client = MakeClient(oldUser, "{}")) + using (LdClient client = MakeClient(oldUser)) { client.Identify(newUser, TimeSpan.FromSeconds(1)); @@ -179,10 +187,11 @@ public void IdentifyDoesNotSendAliasEventIfNewUserIsAnonymousOrOldUserIsNot( [Fact] public void VariationSendsFeatureEventForValidFlag() { - string flagsJson = @"{""flag"":{ - ""value"":""a"",""variation"":1,""version"":1000, - ""trackEvents"":true, ""debugEventsUntilDate"":2000 }}"; - using (LdClient client = MakeClient(user, flagsJson)) + var data = new DataSetBuilder() + .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)).Build()) + .Build(); + using (LdClient client = MakeClient(user, data)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -204,11 +213,12 @@ public void VariationSendsFeatureEventForValidFlag() [Fact] public void FeatureEventUsesFlagVersionIfProvided() - { - string flagsJson = @"{""flag"":{ - ""value"":""a"",""variation"":1,""version"":1000, - ""flagVersion"":1500 }}"; - using (LdClient client = MakeClient(user, flagsJson)) + { + var data = new DataSetBuilder() + .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .FlagVersion(1500).Build()) + .Build(); + using (LdClient client = MakeClient(user, data)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -228,9 +238,10 @@ public void FeatureEventUsesFlagVersionIfProvided() [Fact] public void VariationSendsFeatureEventForDefaultValue() { - string flagsJson = @"{""flag"":{ - ""value"":null,""variation"":null,""version"":1000 }}"; - using (LdClient client = MakeClient(user, flagsJson)) + var data = new DataSetBuilder() + .Add("flag", new FeatureFlagBuilder().Version(1000).Build()) + .Build(); + using (LdClient client = MakeClient(user, data)) { string result = client.StringVariation("flag", "b"); Assert.Equal("b", result); @@ -251,7 +262,7 @@ public void VariationSendsFeatureEventForDefaultValue() [Fact] public void VariationSendsFeatureEventForUnknownFlag() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { string result = client.StringVariation("flag", "b"); Assert.Equal("b", result); @@ -299,11 +310,11 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() [Fact] public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() { - string flagsJson = @"{""flag"":{ - ""value"":""a"",""variation"":1,""version"":1000, - ""trackReason"":true, ""reason"":{""kind"":""OFF""} - }}"; - using (LdClient client = MakeClient(user, flagsJson)) + var data = new DataSetBuilder() + .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackReason(true).Reason(EvaluationReason.OffReason).Build()) + .Build(); + using (LdClient client = MakeClient(user, data)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -326,12 +337,12 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() [Fact] public void VariationDetailSendsFeatureEventWithReasonForValidFlag() { - string flagsJson = @"{""flag"":{ - ""value"":""a"",""variation"":1,""version"":1000, - ""trackEvents"":true, ""debugEventsUntilDate"":2000, - ""reason"":{""kind"":""OFF""} - }}"; - using (LdClient client = MakeClient(user, flagsJson)) + var data = new DataSetBuilder() + .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)) + .Reason(EvaluationReason.OffReason).Build()) + .Build(); + using (LdClient client = MakeClient(user, data)) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); Assert.Equal("a", result.Value); @@ -355,7 +366,7 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() [Fact] public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() { - using (LdClient client = MakeClient(user, "{}")) + using (LdClient client = MakeClient(user)) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); var expectedReason = EvaluationReason.ErrorReason(EvaluationErrorKind.FlagNotFound); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 62bff46f..bc1acad0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -5,8 +5,6 @@ using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; - namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest @@ -17,7 +15,7 @@ public class LdClientTests : BaseTest public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } ConfigurationBuilder BaseConfig() => - TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Logging(testLogging); + TestUtil.TestConfig(appKey).Logging(testLogging); LdClient Client() { @@ -117,8 +115,10 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func Actions = new EventSink(); + + public void Init(FullDataSet data, User user) => + Actions.Add(null, new ReceivedInit { Data = data, User = user }); + + public void Upsert(string key, ItemDescriptor data, User user) => + Actions.Add(null, new ReceivedUpsert { Key = key, Data = data, User = user }); + + public FullDataSet ExpectInit(User user) + { + var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); + Assert.Equal(user, action.User); + return action.Data; + } + + public ItemDescriptor ExpectUpsert(User user, string key) + { + var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); + Assert.Equal(user, action.User); + Assert.Equal(key, action.Key); + return action.Data; + } + } + internal class MockDeviceInfo : IDeviceInfo { internal const string GeneratedId = "fake-generated-id"; @@ -133,70 +171,32 @@ public Task FeatureFlagsAsync() } } - internal class MockFlagCacheManager : IFlagCacheManager + internal class MockPersistentDataStore : IPersistentDataStore { - private readonly IUserFlagCache _flagCache; - - public MockFlagCacheManager(IUserFlagCache flagCache) - { - _flagCache = flagCache; - } - - public void CacheFlagsFromService(IImmutableDictionary flags, User user) - { - _flagCache.CacheFlagsForUser(flags, user); - } - - public FeatureFlag FlagForUser(string flagKey, User user) - { - var flags = FlagsForUser(user); - FeatureFlag featureFlag; - if (flags != null && flags.TryGetValue(flagKey, out featureFlag)) - { - return featureFlag; - } - - return null; - } - - public IImmutableDictionary FlagsForUser(User user) - { - return _flagCache.RetrieveFlags(user); - } + private IDictionary map = new Dictionary(); - public void RemoveFlagForUser(string flagKey, User user) - { - var flagsForUser = FlagsForUser(user); - var updatedDict = flagsForUser.Remove(flagKey); + public void Dispose() { } - CacheFlagsFromService(updatedDict, user); - } + public string GetAll(User user) => + map.TryGetValue(user.Key, out var value) ? value : null; - public void UpdateFlagForUser(string flagKey, FeatureFlag featureFlag, User user) + public void Init(User user, string allData) { - var flagsForUser = FlagsForUser(user); - var updatedDict = flagsForUser.SetItem(flagKey, featureFlag); - - CacheFlagsFromService(updatedDict, user); + map[user.Key] = allData; } } - internal class MockPersistentStorage : IPersistentStorage + internal class SinglePersistentDataStoreFactory : IPersistentDataStoreFactory { - private IDictionary map = new Dictionary(); + private readonly IPersistentDataStore _instance; - public string GetValue(string key) + public SinglePersistentDataStoreFactory(IPersistentDataStore instance) { - if (!map.ContainsKey(key)) - return null; - - return map[key]; + _instance = instance; } - public void Save(string key, string value) - { - map[key] = value; - } + public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => + _instance; } internal class MockDataSourceFactoryFromLambda : IDataSourceFactory @@ -264,7 +264,7 @@ public Task Start() IsRunning = true; if (_updateSink != null && _flagsJson != null) { - _updateSink.Init(new FullDataSet(JsonUtil.DeserializeFlags(_flagsJson)), _user); + _updateSink.Init(DataModelSerialization.DeserializeV1Schema(_flagsJson), _user); } return Task.FromResult(true); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs new file mode 100644 index 00000000..4522cb44 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; + +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client +{ + internal class FeatureFlagBuilder + { + private LdValue _value = LdValue.Null; + private int _version; + private int? _variation; + private int? _flagVersion; + private bool _trackEvents; + private bool _trackReason; + private UnixMillisecondTime? _debugEventsUntilDate; + private EvaluationReason? _reason; + + public FeatureFlagBuilder() + { + } + + public FeatureFlag Build() + { + return new FeatureFlag(_value, _variation, _reason, _version, _flagVersion, _trackEvents, _trackReason, _debugEventsUntilDate); + } + + public FeatureFlagBuilder Value(LdValue value) + { + _value = value; + return this; + } + + public FeatureFlagBuilder Value(bool value) => Value(LdValue.Of(value)); + + public FeatureFlagBuilder Value(string value) => Value(LdValue.Of(value)); + + public FeatureFlagBuilder FlagVersion(int? flagVersion) + { + _flagVersion = flagVersion; + return this; + } + + public FeatureFlagBuilder Version(int version) + { + _version = version; + return this; + } + + public FeatureFlagBuilder Variation(int? variation) + { + _variation = variation; + return this; + } + + public FeatureFlagBuilder Reason(EvaluationReason? reason) + { + _reason = reason; + return this; + } + + public FeatureFlagBuilder TrackEvents(bool trackEvents) + { + _trackEvents = trackEvents; + return this; + } + + public FeatureFlagBuilder TrackReason(bool trackReason) + { + _trackReason = trackReason; + return this; + } + + public FeatureFlagBuilder DebugEventsUntilDate(UnixMillisecondTime? debugEventsUntilDate) + { + _debugEventsUntilDate = debugEventsUntilDate; + return this; + } + } + + internal class DataSetBuilder + { + private List> _items = new List>(); + + public DataSetBuilder Add(string key, FeatureFlag flag) + { + _items.Add(new KeyValuePair(key, flag.ToItemDescriptor())); + return this; + } + + public DataSetBuilder Add(string key, int version, LdValue value, int variation) => + Add(key, new FeatureFlagBuilder().Version(version).Value(value).Variation(variation).Build()); + + public DataSetBuilder AddDeleted(string key, int version) + { + _items.Add(new KeyValuePair(key, new ItemDescriptor(version, null))); + return this; + } + + public FullDataSet Build() => new FullDataSet(_items); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs index 4554d4e9..b4963cc7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs @@ -1,5 +1,4 @@ -using System; -using LaunchDarkly.Logging; +using LaunchDarkly.Logging; using Xunit.Abstractions; namespace LaunchDarkly.Sdk.Client @@ -29,20 +28,5 @@ public class TestLogging /// a log adapter public static ILogAdapter TestOutputAdapter(ITestOutputHelper testOutputHelper) => Logs.ToMethod(line => testOutputHelper.WriteLine("LOG OUTPUT >> " + line)); - - /// - /// Creates a that sends logging to the Xunit output buffer. Use this when testing - /// lower-level components that want a specific logger instance instead of an . - /// - /// the that Xunit passed to the test - /// class constructor - /// a logger - public static Logger TestLogger(ITestOutputHelper testOutputHelper) => - TestOutputAdapter(testOutputHelper).Logger(""); - - /// - /// Convenience property for getting a millisecond timestamp string. - /// - public static string TimestampString => DateTime.Now.ToString("O"); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 36bfa929..6e1c5a0b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -1,14 +1,11 @@ using System; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; -using Xunit; using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { @@ -119,46 +116,46 @@ public static void ClearClient() }); } - internal static IImmutableDictionary MakeSingleFlagData(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) + internal static string MakeJsonData(FullDataSet data) { - var flag = new FeatureFlagBuilder().Value(value).Variation(variation).Reason(reason).Build(); - return ImmutableDictionary.Create().SetItem(flagKey, flag); - } - - internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) - { - return JsonUtil.SerializeFlags(MakeSingleFlagData(flagKey, value, variation, reason)); - } - - internal static IImmutableDictionary DecodeFlagsJson(string flagsJson) - { - return JsonUtil.DeserializeFlags(flagsJson); + var w = JWriter.New(); + using (var ow = w.Object()) + { + foreach (var item in data.Items) + { + if (item.Value.Item != null) + { + FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, ow.Name(item.Key)); + } + } + } + return w.GetString(); } + internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) => + MakeJsonData(new DataSetBuilder() + .Add(flagKey, new FeatureFlagBuilder().Value(value).Variation(variation).Reason(reason).Build()) + .Build()); + + internal static ConfigurationBuilder TestConfig(string appKey) => + Configuration.Builder(appKey) + .ConnectivityStateManager(new MockConnectivityStateManager(true)) + .Events(new SingleEventProcessorFactory(new MockEventProcessor())) + .DataSource(MockPollingProcessor.Factory(null)) + .Persistence(Components.NoPersistence) + .DeviceInfo(new MockDeviceInfo()); + internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) { - var flags = DecodeFlagsJson(flagsJson); - IUserFlagCache stubbedFlagCache = new UserFlagInMemoryCache(); + var mockStore = new MockPersistentDataStore(); if (user != null && user.Key != null) { - stubbedFlagCache.CacheFlagsForUser(flags, user); + mockStore.Init(user, flagsJson); } - - return Configuration.Builder(appKey) - .FlagCacheManager(new MockFlagCacheManager(stubbedFlagCache)) - .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .Events(new SingleEventProcessorFactory(new MockEventProcessor())) - .DataSource(MockPollingProcessor.Factory(null)) - .PersistentStorage(new MockPersistentStorage()) - .DeviceInfo(new MockDeviceInfo()); - } - - public static void AssertJsonEquals(LdValue expected, LdValue actual) - { - Assert.Equal(expected, actual); + return TestConfig(appKey).Persistence(new SinglePersistentDataStoreFactory(mockStore)); } - public static LdValue NormalizeJsonUser(LdValue json) + public static string NormalizeJsonUser(LdValue json) { // It's undefined whether a user with no custom attributes will have "custom":{} or not if (json.Type == LdValueType.Object && json.Get("custom").Type == LdValueType.Object) @@ -173,10 +170,10 @@ public static LdValue NormalizeJsonUser(LdValue json) o1.Add(kv.Key, kv.Value); } } - return o1.Build(); + return o1.Build().ToJsonString(); } } - return json; + return json.ToJsonString(); } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index 68392550..02df7ff1 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -14,7 +14,7 @@ public void SdkReturnsIOsPlatformType() public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.ConfigWithFlagsJson(baseUser, "mobileKey", "{}").Build(); + var config = TestUtil.TestConfig("mobileKey").Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; @@ -29,7 +29,7 @@ public void UserHasOSAndDeviceAttributesForPlatform() public void CanGetUniqueUserKey() { var anonUser = User.Builder((string)null).Anonymous(true).Build(); - var config = TestUtil.ConfigWithFlagsJson(anonUser, "mobileKey", "{}") + var config = TestUtil.TestConfig("mobileKey") .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj index ec32addf..d22e6493 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj @@ -75,7 +75,7 @@ - + From cc5bd8655bd1458f1ca5f4a28deef64ef4270311 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 22 Sep 2021 16:02:19 -0700 Subject: [PATCH 359/499] (#2) misc tedious code cleanup of property/method naming (#128) --- src/LaunchDarkly.ClientSdk/DataModel.cs | 72 +++++++++---------- .../Interfaces/IDataSourceUpdateSink.cs | 8 +-- .../DataSources/DataSourceUpdateSinkImpl.cs | 18 ++--- .../Internal/DataSources/PollingDataSource.cs | 2 +- .../DataSources/StreamingDataSource.cs | 9 ++- .../Internal/Events/EventFactory.cs | 14 ++-- src/LaunchDarkly.ClientSdk/LdClient.cs | 14 ++-- .../DataSources/DataSourceUpdateSinkTest.cs | 56 +++++++-------- .../DataSources/StreamingDataSourceTest.cs | 6 +- .../LDClientEndToEndTests.cs | 2 +- .../LdClientTests.cs | 6 +- .../MockComponents.cs | 6 +- 12 files changed, 104 insertions(+), 109 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/DataModel.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs index 819ce5ee..46404add 100644 --- a/src/LaunchDarkly.ClientSdk/DataModel.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -24,14 +24,14 @@ public static class DataModel [JsonStreamConverter(typeof(FeatureFlagJsonConverter))] public sealed class FeatureFlag : IEquatable, IJsonSerializable { - internal LdValue value { get; } - internal int? variation { get; } - internal EvaluationReason? reason { get; } - internal int version { get; } - internal int? flagVersion { get; } - internal bool trackEvents { get; } - internal bool trackReason { get; } - internal UnixMillisecondTime? debugEventsUntilDate { get; } + internal LdValue Value { get; } + internal int? Variation { get; } + internal EvaluationReason? Reason { get; } + internal int Version { get; } + internal int? FlagVersion { get; } + internal bool TrackEvents { get; } + internal bool TrackReason { get; } + internal UnixMillisecondTime? DebugEventsUntilDate { get; } internal FeatureFlag( LdValue value, @@ -44,14 +44,14 @@ internal FeatureFlag( UnixMillisecondTime? debugEventsUntilDate ) { - this.value = value; - this.variation = variation; - this.reason = reason; - this.version = version; - this.flagVersion = flagVersion; - this.trackEvents = trackEvents; - this.trackReason = trackReason; - this.debugEventsUntilDate = debugEventsUntilDate; + Value = value; + Variation = variation; + Reason = reason; + Version = version; + FlagVersion = flagVersion; + TrackEvents = trackEvents; + TrackReason = trackReason; + DebugEventsUntilDate = debugEventsUntilDate; } /// @@ -60,25 +60,25 @@ public override bool Equals(object obj) => /// public bool Equals(FeatureFlag otherFlag) => - value.Equals(otherFlag.value) - && variation == otherFlag.variation - && reason.Equals(otherFlag.reason) - && version == otherFlag.version - && flagVersion == otherFlag.flagVersion - && trackEvents == otherFlag.trackEvents - && debugEventsUntilDate == otherFlag.debugEventsUntilDate; + Value.Equals(otherFlag.Value) + && Variation == otherFlag.Variation + && Reason.Equals(otherFlag.Reason) + && Version == otherFlag.Version + && FlagVersion == otherFlag.FlagVersion + && TrackEvents == otherFlag.TrackEvents + && DebugEventsUntilDate == otherFlag.DebugEventsUntilDate; /// public override int GetHashCode() => - value.GetHashCode(); + Value.GetHashCode(); /// public override string ToString() => string.Format("({0},{1},{2},{3},{4},{5},{6},{7})", - value, variation, reason, version, flagVersion, trackEvents, trackReason, debugEventsUntilDate); + Value, Variation, Reason, Version, FlagVersion, TrackEvents, TrackReason, DebugEventsUntilDate); internal ItemDescriptor ToItemDescriptor() => - new ItemDescriptor(version, this); + new ItemDescriptor(Version, this); } internal sealed class FeatureFlagJsonConverter : IJsonStreamConverter @@ -168,18 +168,18 @@ public static void WriteJsonValue(FeatureFlag value, IValueWriter writer) { using (var ow = writer.Object()) { - LdJsonConverters.LdValueConverter.WriteJsonValue(value.value, ow.Name("value")); - ow.Name("version").Int(value.version); - ow.MaybeName("flagVersion", value.flagVersion.HasValue).Int(value.flagVersion.GetValueOrDefault()); - ow.MaybeName("variation", value.variation.HasValue).Int(value.variation.GetValueOrDefault()); - if (value.reason.HasValue) + LdJsonConverters.LdValueConverter.WriteJsonValue(value.Value, ow.Name("value")); + ow.Name("version").Int(value.Version); + ow.MaybeName("flagVersion", value.FlagVersion.HasValue).Int(value.FlagVersion.GetValueOrDefault()); + ow.MaybeName("variation", value.Variation.HasValue).Int(value.Variation.GetValueOrDefault()); + if (value.Reason.HasValue) { - LdJsonConverters.EvaluationReasonConverter.WriteJsonValue(value.reason.Value, ow.Name("reason")); + LdJsonConverters.EvaluationReasonConverter.WriteJsonValue(value.Reason.Value, ow.Name("reason")); } - ow.MaybeName("trackEvents", value.trackEvents).Bool(value.trackEvents); - ow.MaybeName("trackReason", value.trackReason).Bool(value.trackReason); - ow.MaybeName("debugEventsUntilDate", value.debugEventsUntilDate.HasValue) - .Long(value.debugEventsUntilDate.GetValueOrDefault().Value); + ow.MaybeName("trackEvents", value.TrackEvents).Bool(value.TrackEvents); + ow.MaybeName("trackReason", value.TrackReason).Bool(value.TrackReason); + ow.MaybeName("debugEventsUntilDate", value.DebugEventsUntilDate.HasValue) + .Long(value.DebugEventsUntilDate.GetValueOrDefault().Value); } } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs index f152a490..d01183e7 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -14,18 +14,18 @@ public interface IDataSourceUpdateSink /// /// Completely overwrites the current contents of the data store with a set of items for each collection. /// - /// the data set /// the current user + /// the data set /// true if the update succeeded, false if it failed - void Init(FullDataSet data, User user); + void Init(User user, FullDataSet data); /// /// Updates or inserts an item. For updates, the object will only be updated if the existing /// version is less than the new version. /// + /// the current user /// the feature flag key /// the item data - /// the current user - void Upsert(string key, ItemDescriptor data, User user); + void Upsert(User user, string key, ItemDescriptor data); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index 492e5ab4..352d14b9 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -25,7 +25,7 @@ IFlagChangedEventManager flagChangedEventManager _flagChangedEventManager = flagChangedEventManager; } - public void Init(FullDataSet data, User user) + public void Init(User user, FullDataSet data) { _dataStore.Init(user, data); @@ -55,16 +55,16 @@ public void Init(FullDataSet data, User user) var newFlag = newEntry.Value; if (oldValues.TryGetValue(newEntry.Key, out var oldFlag)) { - if (newFlag.variation != oldFlag.variation) + if (newFlag.Variation != oldFlag.Variation) { events.Add(new FlagChangedEventArgs(newEntry.Key, - newFlag.value, oldFlag.value, false)); + newFlag.Value, oldFlag.Value, false)); } } else { events.Add(new FlagChangedEventArgs(newEntry.Key, - newFlag.value, LdValue.Null, false)); + newFlag.Value, LdValue.Null, false)); } } foreach (var oldEntry in oldValues) @@ -72,7 +72,7 @@ public void Init(FullDataSet data, User user) if (!newValues.ContainsKey(oldEntry.Key)) { events.Add(new FlagChangedEventArgs(oldEntry.Key, - LdValue.Null, oldEntry.Value.value, true)); + LdValue.Null, oldEntry.Value.Value, true)); } } foreach (var e in events) @@ -82,7 +82,7 @@ public void Init(FullDataSet data, User user) } } - public void Upsert(string key, ItemDescriptor data, User user) + public void Upsert(User user, string key, ItemDescriptor data) { var updated = _dataStore.Upsert(user, key, data); if (!updated) @@ -110,11 +110,11 @@ public void Upsert(string key, ItemDescriptor data, User user) oldValues.Remove(key) : oldValues.SetItem(key, data.Item); _lastValues = _lastValues.SetItem(user.Key, newValues); } - if (oldFlag?.variation != data.Item?.variation) + if (oldFlag?.Variation != data.Item?.Variation) { var eventArgs = new FlagChangedEventArgs(key, - data.Item?.value ?? LdValue.Null, - oldFlag?.value ?? LdValue.Null, + data.Item?.Value ?? LdValue.Null, + oldFlag?.Value ?? LdValue.Null, data.Item is null ); _flagChangedEventManager.FireEvent(eventArgs); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index e57f5135..5b0c3493 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -80,7 +80,7 @@ private async Task UpdateTaskAsync() { var flagsAsJsonString = response.jsonResponse; var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); - _updateSink.Init(allData, _user); + _updateSink.Init(_user, allData); if (_initialized.GetAndSet(true) == false) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index a671a910..9ba1f0bb 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -9,7 +9,6 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; -using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataSources @@ -173,7 +172,7 @@ void HandleMessage(string messageType, string messageData) case Constants.PUT: { var allData = DataModelSerialization.DeserializeV1Schema(messageData); - _updateSink.Init(allData, _user); + _updateSink.Init(_user, allData); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -187,7 +186,7 @@ void HandleMessage(string messageType, string messageData) var parsed = LdValue.Parse(messageData); var flagkey = parsed.Get(Constants.KEY).AsString; var featureFlag = DataModelSerialization.DeserializeFlag(messageData); - _updateSink.Upsert(flagkey, featureFlag.ToItemDescriptor(), _user); + _updateSink.Upsert(_user, flagkey, featureFlag.ToItemDescriptor()); } catch (Exception ex) { @@ -204,7 +203,7 @@ void HandleMessage(string messageType, string messageData) int version = parsed.Get(Constants.VERSION).AsInt; string flagKey = parsed.Get(Constants.KEY).AsString; var deletedItem = new ItemDescriptor(version, null); - _updateSink.Upsert(flagKey, deletedItem, _user); + _updateSink.Upsert(_user, flagKey, deletedItem); } catch (Exception ex) { @@ -222,7 +221,7 @@ void HandleMessage(string messageType, string messageData) var response = await _requestor.FeatureFlagsAsync(); var flagsAsJsonString = response.jsonResponse; var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); - _updateSink.Init(allData, _user); + _updateSink.Init(_user, allData); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs index e66a8004..73a96f42 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs @@ -27,20 +27,20 @@ LdValue defaultValue // EventFactory passes the reason parameter to this method because the server-side SDK needs to // look at the reason; but in this client-side SDK, we don't look at that parameter, because // LD has already done the relevant calculation for us and sent us the result in trackReason. - var isExperiment = flag.trackReason; + var isExperiment = flag.TrackReason; return new EvaluationEvent { Timestamp = UnixMillisecondTime.Now, User = user, FlagKey = flagKey, - FlagVersion = flag.flagVersion ?? flag.version, + FlagVersion = flag.FlagVersion ?? flag.Version, Variation = result.VariationIndex, Value = result.Value, Default = defaultValue, Reason = (_withReasons || isExperiment) ? result.Reason : (EvaluationReason?)null, - TrackEvents = flag.trackEvents || isExperiment, - DebugEventsUntilDate = flag.debugEventsUntilDate + TrackEvents = flag.TrackEvents || isExperiment, + DebugEventsUntilDate = flag.DebugEventsUntilDate }; } @@ -57,12 +57,12 @@ EvaluationErrorKind errorKind Timestamp = UnixMillisecondTime.Now, User = user, FlagKey = flagKey, - FlagVersion = flag.flagVersion ?? flag.version, + FlagVersion = flag.FlagVersion ?? flag.Version, Value = defaultValue, Default = defaultValue, Reason = _withReasons ? EvaluationReason.ErrorReason(errorKind) : (EvaluationReason?)null, - TrackEvents = flag.trackEvents, - DebugEventsUntilDate = flag.debugEventsUntilDate + TrackEvents = flag.TrackEvents, + DebugEventsUntilDate = flag.DebugEventsUntilDate }; } diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 0e8d5fa1..3385899b 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -453,26 +453,26 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => EvaluationDetail result; LdValue valueJson; - if (flag.value.IsNull) + if (flag.Value.IsNull) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, flag.variation, flag.reason ?? EvaluationReason.OffReason); + result = new EvaluationDetail(defaultValue, flag.Variation, flag.Reason ?? EvaluationReason.OffReason); } else { - if (checkType && !defaultJson.IsNull && flag.value.Type != defaultJson.Type) + if (checkType && !defaultJson.IsNull && flag.Value.Type != defaultJson.Type) { valueJson = defaultJson; result = new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); } else { - valueJson = flag.value; - result = new EvaluationDetail(converter.ToType(flag.value), flag.variation, flag.reason ?? EvaluationReason.OffReason); + valueJson = flag.Value; + result = new EvaluationDetail(converter.ToType(flag.Value), flag.Variation, flag.Reason ?? EvaluationReason.OffReason); } } var featureEvent = eventFactory.NewEvaluationEvent(featureKey, flag, User, - new EvaluationDetail(valueJson, flag.variation, flag.reason ?? EvaluationReason.OffReason), defaultJson); + new EvaluationDetail(valueJson, flag.Variation, flag.Reason ?? EvaluationReason.OffReason), defaultJson); SendEvaluationEventIfOnline(featureEvent); return result; } @@ -491,7 +491,7 @@ public IDictionary AllFlags() return ImmutableDictionary.Create(); } return data.Value.Items.Where(entry => entry.Value.Item != null) - .ToDictionary(p => p.Key, p => p.Value.Item.value); + .ToDictionary(p => p.Key, p => p.Value.Item.Value); } /// diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs index 529e5a2e..0e2317a8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs @@ -26,7 +26,7 @@ public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOut public void InitPassesDataToStore() { var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); Assert.Equal(initData.Items, _store.GetAll(_basicUser).Value.Items); } @@ -36,11 +36,11 @@ public void UpsertPassesDataToStore() { var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Build(); var initData = new DataSetBuilder().Add("key1", flag1a).Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(true)).Build(); - _updateSink.Upsert("key1", flag1b.ToItemDescriptor(), _basicUser); + _updateSink.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); } @@ -52,7 +52,7 @@ public void NoEventsAreSentForFirstInit() _flagChangedEventManager.FlagChanged += events.Add; var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); events.ExpectNoValue(); } @@ -63,7 +63,7 @@ public void NoEventsAreSentForUpsertIfNeverInited() var events = new EventSink(); _flagChangedEventManager.FlagChanged += events.Add; - _updateSink.Upsert("key1", new FeatureFlagBuilder().Build().ToItemDescriptor(), _basicUser); + _updateSink.Upsert(_basicUser, "key1", new FeatureFlagBuilder().Build().ToItemDescriptor()); events.ExpectNoValue(); } @@ -78,13 +78,13 @@ public void EventIsSentForChangedFlagOnInit() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData1, _basicUser); + _updateSink.Init(_basicUser, initData1); var initData2 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(false)).Variation(1).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData2, _basicUser); + _updateSink.Init(_basicUser, initData2); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -102,13 +102,13 @@ public void EventIsSentForAddedFlagOnInit() var initData1 = new DataSetBuilder() .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData1, _basicUser); + _updateSink.Init(_basicUser, initData1); var initData2 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(false)).Variation(1).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData2, _basicUser); + _updateSink.Init(_basicUser, initData2); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -127,12 +127,12 @@ public void EventIsSentForDeletedFlagOnInit() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData1, _basicUser); + _updateSink.Init(_basicUser, initData1); var initData2 = new DataSetBuilder() .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData2, _basicUser); + _updateSink.Init(_basicUser, initData2); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -151,11 +151,10 @@ public void EventIsSentForChangedFlagOnUpsert() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); - _updateSink.Upsert("key1", - new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), - _basicUser); + _updateSink.Upsert(_basicUser, "key1", + new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor()); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -173,11 +172,10 @@ public void EventIsSentForAddedFlagOnUpsert() var initData = new DataSetBuilder() .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); - _updateSink.Upsert("key1", - new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), - _basicUser); + _updateSink.Upsert(_basicUser, "key1", + new FeatureFlagBuilder().Version(100).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor()); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -196,9 +194,9 @@ public void EventIsSentForDeletedFlagOnUpsert() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); - _updateSink.Upsert("key1", new ItemDescriptor(101, null), _basicUser); + _updateSink.Upsert(_basicUser, "key1", new ItemDescriptor(101, null)); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); @@ -217,11 +215,10 @@ public void EventIsNotSentIfUpsertFailsDueToLowerVersion() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) .Build(); - _updateSink.Init(initData, _basicUser); + _updateSink.Init(_basicUser, initData); - _updateSink.Upsert("key1", - new FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor(), - _basicUser); + _updateSink.Upsert(_basicUser, "key1", + new FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor()); events.ExpectNoValue(); } @@ -236,19 +233,18 @@ public void ValueChangesAreTrackedSeparatelyForEachUser() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("a")).Variation(1).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("b")).Variation(2).Build()) .Build(); - _updateSink.Init(initDataForBasicUser, _basicUser); + _updateSink.Init(_basicUser, initDataForBasicUser); var initDataForOtherUser = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("c")).Variation(3).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("d")).Variation(4).Build()) .Build(); - _updateSink.Init(initDataForOtherUser, _otherUser); + _updateSink.Init(_otherUser, initDataForOtherUser); events.ExpectNoValue(); - _updateSink.Upsert("key1", - new FeatureFlagBuilder().Version(101).Value(LdValue.Of("c")).Variation(3).Build().ToItemDescriptor(), - _basicUser); + _updateSink.Upsert(_basicUser, "key1", + new FeatureFlagBuilder().Version(101).Value(LdValue.Of("c")).Variation(3).Build().ToItemDescriptor()); var e = events.ExpectValue(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index bc8c62ed..a03e8eb8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -120,7 +120,7 @@ public void PutStoresFeatureFlags() var gotData = _updateSink.ExpectInit(user); var gotItem = gotData.Items.First(item => item.Key == "int-flag"); - int intFlagValue = gotItem.Value.Item.value.AsInt; + int intFlagValue = gotItem.Value.Item.Value.AsInt; Assert.Equal(15, intFlagValue); } @@ -138,7 +138,7 @@ public void PatchUpdatesFeatureFlag() //verify flag has changed var gotItem = _updateSink.ExpectUpsert(user, "int-flag"); - Assert.Equal(99, gotItem.Item.value.AsInt); + Assert.Equal(99, gotItem.Item.Value.AsInt); } [Fact] @@ -166,7 +166,7 @@ public void PingCausesPoll() var gotData = _updateSink.ExpectInit(user); var gotItem = gotData.Items.First(item => item.Key == "int-flag"); - int intFlagValue = gotItem.Value.Item.value.AsInt; + int intFlagValue = gotItem.Value.Item.Value.AsInt; Assert.Equal(15, intFlagValue); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 62a49d35..101cd28a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -535,7 +535,7 @@ private void VerifyFlagValues(ILdClient client, FullDataSet flags) Assert.True(client.Initialized); foreach (var e in flags.Items) { - Assert.Equal(e.Value.Item.value, client.JsonVariation(e.Key, LdValue.Null)); + Assert.Equal(e.Value.Item.Value, client.JsonVariation(e.Key, LdValue.Null)); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index bc1acad0..c06e78b3 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -130,13 +130,13 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func Actions = new EventSink(); - public void Init(FullDataSet data, User user) => + public void Init(User user, FullDataSet data) => Actions.Add(null, new ReceivedInit { Data = data, User = user }); - public void Upsert(string key, ItemDescriptor data, User user) => + public void Upsert(User user, string key, ItemDescriptor data) => Actions.Add(null, new ReceivedUpsert { Key = key, Data = data, User = user }); public FullDataSet ExpectInit(User user) @@ -264,7 +264,7 @@ public Task Start() IsRunning = true; if (_updateSink != null && _flagsJson != null) { - _updateSink.Init(DataModelSerialization.DeserializeV1Schema(_flagsJson), _user); + _updateSink.Init(_user, DataModelSerialization.DeserializeV1Schema(_flagsJson)); } return Task.FromResult(true); } From d3db5548bc79e893690863cbf2cb7209a4cad417 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 22 Sep 2021 16:05:05 -0700 Subject: [PATCH 360/499] (#3) revise flag notification mechanism to look more like the server-side SDK (#129) --- src/LaunchDarkly.ClientSdk/Configuration.cs | 2 - .../ConfigurationBuilder.cs | 7 -- .../FlagChangedEvent.cs | 114 ----------------- .../Interfaces/IFlagTracker.cs | 116 ++++++++++++++++++ .../Interfaces/ILdClient.cs | 41 ++----- .../DataSources/DataSourceUpdateSinkImpl.cs | 28 ++--- .../Internal/Factory.cs | 5 - .../Internal/FlagTrackerImpl.cs | 45 +++++++ src/LaunchDarkly.ClientSdk/LdClient.cs | 24 ++-- .../FlagChangedEventTests.cs | 108 ---------------- .../ILdClientExtensionsTest.cs | 4 +- ...est.cs => DataSourceUpdateSinkImplTest.cs} | 61 ++++----- .../DataSources/PollingDataSourceTest.cs | 2 +- .../LDClientEndToEndTests.cs | 4 +- .../LdClientListenersTest.cs | 67 ++++++++++ .../LdClientTests.cs | 16 --- 16 files changed, 294 insertions(+), 350 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs rename tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/{DataSourceUpdateSinkTest.cs => DataSourceUpdateSinkImplTest.cs} (83%) create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 27134958..8199533e 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -31,7 +31,6 @@ public sealed class Configuration internal IBackgroundModeManager BackgroundModeManager { get; } internal IConnectivityStateManager ConnectivityStateManager { get; } internal IDeviceInfo DeviceInfo { get; } - internal IFlagChangedEventManager FlagChangedEventManager { get; } /// /// Whether to disable the automatic sending of an alias event when the current user is changed @@ -173,7 +172,6 @@ internal Configuration(ConfigurationBuilder builder) BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; DeviceInfo = builder._deviceInfo; - FlagChangedEventManager = builder._flagChangedEventManager; } } } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 86f578eb..1f7c042e 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -48,7 +48,6 @@ public sealed class ConfigurationBuilder internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; internal IDeviceInfo _deviceInfo; - internal IFlagChangedEventManager _flagChangedEventManager; internal ConfigurationBuilder(string mobileKey) { @@ -312,11 +311,5 @@ internal ConfigurationBuilder DeviceInfo(IDeviceInfo deviceInfo) _deviceInfo = deviceInfo; return this; } - - internal ConfigurationBuilder FlagChangedEventManager(IFlagChangedEventManager flagChangedEventManager) - { - _flagChangedEventManager = flagChangedEventManager; - return this; - } } } diff --git a/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs b/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs deleted file mode 100644 index ad02e573..00000000 --- a/src/LaunchDarkly.ClientSdk/FlagChangedEvent.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Linq; -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Internal; - -namespace LaunchDarkly.Sdk.Client -{ - /// - /// An event object that is sent to handlers for the event. - /// - public sealed class FlagChangedEventArgs - { - /// - /// The unique key of the feature flag whose value has changed. - /// - public string Key { get; private set; } - - /// - /// The updated value of the flag for the current user. - /// - /// - /// - /// Since flag values can be of any JSON type, this property is an . You - /// can use properties and methods of such as - /// to convert it to other types. - /// - /// - /// Flag evaluations always produce non-null values, but this property could still be - /// if the flag was completely deleted or if it could not be evaluated due to an error of some kind. - /// - /// - /// Note that in those cases, the Variation methods may return a different result from this property, - /// because of their "default value" behavior. For instance, if the flag "feature1" has been deleted, the - /// following expression will return the string "xyz", because that is the default value that you specified - /// in the method call: - /// - /// - /// client.StringVariation("feature1", "xyz"); - /// - /// - /// But when an event is sent for the deletion of the flag, it has no way to know that you would have - /// specified "xyz" as a default value when evaluating the flag, so will simply - /// contain a . - /// - /// - public LdValue NewValue { get; private set; } - - /// - /// The last known value of the flag for the current user prior to the update. - /// - public LdValue OldValue { get; private set; } - - /// - /// True if the flag was completely removed from the environment. - /// - public bool FlagWasDeleted { get; private set; } - - internal FlagChangedEventArgs(string key, LdValue newValue, LdValue oldValue, bool flagWasDeleted) - { - Key = key; - NewValue = newValue; - OldValue = oldValue; - FlagWasDeleted = flagWasDeleted; - } - } - - internal interface IFlagChangedEventManager - { - event EventHandler FlagChanged; - void FireEvent(FlagChangedEventArgs e); - } - - internal sealed class FlagChangedEventManager : IFlagChangedEventManager - { - private readonly Logger _log; - - public event EventHandler FlagChanged; - - internal FlagChangedEventManager(Logger log) - { - _log = log; - } - - public bool IsHandlerRegistered(EventHandler handler) - { - return FlagChanged != null && FlagChanged.GetInvocationList().Contains(handler); - } - - public void FireEvent(FlagChangedEventArgs eventArgs) - { - var copyOfHandlers = FlagChanged; - var sender = this; - if (copyOfHandlers != null) - { - foreach (var h in copyOfHandlers.GetInvocationList()) - { - // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. - PlatformSpecific.AsyncScheduler.ScheduleAction(() => - { - try - { - h.DynamicInvoke(sender, eventArgs); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Unexpected exception from FlagChanged event handler", e); - } - }); - } - } - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs new file mode 100644 index 00000000..89e3261e --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs @@ -0,0 +1,116 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// An interface for tracking changes in feature flag configurations. + /// + /// + /// An implementation of this interface is returned by . + /// Application code never needs to implement this interface. + /// + public interface IFlagTracker + { + /// + /// An event for receiving notifications of feature flag changes. + /// + /// + /// + /// This event is raised whenever the SDK receives a variation for any feature flag that is + /// not equal to its previous variation. This could mean that the flag configuration was + /// changed in LaunchDarkly, or that you have changed the current user and the flag values + /// are different for this user than for the previous user. The event is not raised if the + /// SDK has just received its very first set of flag values. + /// + /// + /// Notifications will be dispatched on a background task. It is the listener's + /// responsibility to return as soon as possible so as not to block subsequent + /// notifications. + /// + /// + /// + /// client.FlagTracker.FlagChanged += (sender, eventArgs) => + /// { + /// System.Console.WriteLine("flag '" + eventArgs.Key + /// + "' changed from " + eventArgs.OldValue + /// + " to " + eventArgs.NewValue); + /// }; + /// + event EventHandler FlagValueChanged; + } + + /// + /// A parameter class used with . + /// + /// + /// This is not an analytics event to be sent to LaunchDarkly; it is a notification to the + /// application. + /// + public struct FlagValueChangeEvent + { + /// + /// The key of the feature flag whose configuration has changed. + /// + /// + /// The specified flag may have been modified directly, or this may be an indirect + /// change due to a change in some other flag that is a prerequisite for this flag, or + /// a user segment that is referenced in the flag's rules. + /// + public string Key { get; } + + /// + /// The last known value of the flag for the specified user prior to the update. + /// + /// + /// + /// Since flag values can be of any JSON data type, this is represented as + /// . That class has properties for converting to other .NET types, + /// such as . + /// + /// + /// If the flag was deleted or could not be evaluated, this will be . + /// there is no application default value parameter as there is for the Variation + /// methods; it is up to your code to substitute whatever fallback value is appropriate. + /// + /// + public LdValue OldValue { get; } + + /// + /// The new value of the flag for the specified user. + /// + /// + /// + /// Since flag values can be of any JSON data type, this is represented as + /// . That class has properties for converting to other .NET types, + /// such as . + /// + /// + /// If the flag was deleted or could not be evaluated, this will be . + /// there is no application default value parameter as there is for the Variation + /// methods; it is up to your code to substitute whatever fallback value is appropriate. + /// + /// + public LdValue NewValue { get; } + + /// + /// True if the flag was completely removed from the environment. + /// + public bool Deleted { get; } + + /// + /// Constructs a new instance. + /// + /// the key of the feature flag whose configuration has changed + /// the last known value of the flag for the specified user prior to + /// the update + /// the new value of the flag for the specified user + /// true if the flag was deleted + public FlagValueChangeEvent(string key, LdValue oldValue, LdValue newValue, bool deleted) + { + Key = key; + OldValue = oldValue; + NewValue = newValue; + Deleted = deleted; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index 70e3625b..bf1546ad 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -14,6 +14,15 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// public interface ILdClient : IDisposable { + /// + /// A mechanism for tracking changes in feature flag configurations. + /// + /// + /// The contains methods for requesting notifications about feature flag + /// changes using an event listener model. + /// + IFlagTracker FlagTracker { get; } + /// /// Returns a boolean value indicating LaunchDarkly connection and flag state within the client. /// @@ -266,38 +275,6 @@ public interface ILdClient : IDisposable /// a map from feature flag keys to values for the current user IDictionary AllFlags(); - /// - /// This event is triggered when the client has received an updated value for a feature flag. - /// - /// - /// - /// This could mean that the flag configuration was changed in LaunchDarkly, or that you have changed the current - /// user and the flag values are different for this user than for the previous user. The event is only triggered - /// if the newly received flag value is actually different from the previous one. - /// - /// - /// The properties will indicate the key of the feature flag, the new value, - /// and the previous value. - /// - /// - /// On platforms that have a main UI thread (such as iOS and Android), handlers for this event are guaranteed to - /// be called on that thread; on other platforms, the SDK uses a thread pool. Either way, the handler is called - /// called asynchronously after whichever SDK action triggered the flag change has already completed. This is to - /// avoid deadlocks, in case the action was also on the main thread, or on a thread that was holding a lock on - /// some application resource that the handler also uses. - /// - /// - /// - /// - /// client.FlagChanged += (sender, eventArgs) => { - /// if (eventArgs.Key == "key-for-flag-i-am-watching") { - /// DoSomethingWithNewFlagValue(eventArgs.NewBoolValue); - /// } - /// }; - /// - /// - event EventHandler FlagChanged; - /// /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates /// an analytics event to tell LaunchDarkly about the user. diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index 352d14b9..25cab958 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -11,18 +11,18 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink { private readonly IDataStore _dataStore; - private readonly IFlagChangedEventManager _flagChangedEventManager; + private readonly FlagTrackerImpl _flagTracker; private readonly object _lastValuesLock = new object(); private volatile ImmutableDictionary> _lastValues = ImmutableDictionary.Create>(); public DataSourceUpdateSinkImpl( IDataStore dataStore, - IFlagChangedEventManager flagChangedEventManager + FlagTrackerImpl flagTracker ) { _dataStore = dataStore; - _flagChangedEventManager = flagChangedEventManager; + _flagTracker = flagTracker; } public void Init(User user, FullDataSet data) @@ -48,7 +48,7 @@ public void Init(User user, FullDataSet data) if (oldValues != null) { - List events = new List(); + List events = new List(); foreach (var newEntry in newValues) { @@ -57,27 +57,27 @@ public void Init(User user, FullDataSet data) { if (newFlag.Variation != oldFlag.Variation) { - events.Add(new FlagChangedEventArgs(newEntry.Key, - newFlag.Value, oldFlag.Value, false)); + events.Add(new FlagValueChangeEvent(newEntry.Key, + oldFlag.Value, newFlag.Value, false)); } } else { - events.Add(new FlagChangedEventArgs(newEntry.Key, - newFlag.Value, LdValue.Null, false)); + events.Add(new FlagValueChangeEvent(newEntry.Key, + LdValue.Null, newFlag.Value, false)); } } foreach (var oldEntry in oldValues) { if (!newValues.ContainsKey(oldEntry.Key)) { - events.Add(new FlagChangedEventArgs(oldEntry.Key, - LdValue.Null, oldEntry.Value.Value, true)); + events.Add(new FlagValueChangeEvent(oldEntry.Key, + oldEntry.Value.Value, LdValue.Null, true)); } } foreach (var e in events) { - _flagChangedEventManager.FireEvent(e); + _flagTracker.FireEvent(e); } } } @@ -112,12 +112,12 @@ public void Upsert(User user, string key, ItemDescriptor data) } if (oldFlag?.Variation != data.Item?.Variation) { - var eventArgs = new FlagChangedEventArgs(key, - data.Item?.Value ?? LdValue.Null, + var eventArgs = new FlagValueChangeEvent(key, oldFlag?.Value ?? LdValue.Null, + data.Item?.Value ?? LdValue.Null, data.Item is null ); - _flagChangedEventManager.FireEvent(eventArgs); + _flagTracker.FireEvent(eventArgs); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index d6aa0d22..e90f160e 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -15,10 +15,5 @@ internal static IDeviceInfo CreateDeviceInfo(Configuration configuration, Logger { return configuration.DeviceInfo ?? new DefaultDeviceInfo(log); } - - internal static IFlagChangedEventManager CreateFlagChangedEventManager(Configuration configuration, Logger log) - { - return configuration.FlagChangedEventManager ?? new FlagChangedEventManager(log); - } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs new file mode 100644 index 00000000..4f17a721 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs @@ -0,0 +1,45 @@ +using System; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal sealed class FlagTrackerImpl : IFlagTracker + { + public event EventHandler FlagValueChanged; + + private readonly Logger _log; + + internal FlagTrackerImpl( + Logger log + ) + { + _log = log; + } + + internal void FireEvent(FlagValueChangeEvent ev) + { + var copyOfHandlers = FlagValueChanged; + var sender = this; + if (copyOfHandlers != null) + { + foreach (var h in copyOfHandlers.GetInvocationList()) + { + // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. + PlatformSpecific.AsyncScheduler.ScheduleAction(() => + { + try + { + h.DynamicInvoke(sender, ev); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Unexpected exception from FlagValueChanged event handler", e); + } + }); + } + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 3385899b..c73117fa 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -39,7 +39,7 @@ public sealed class LdClient : ILdClient readonly IDeviceInfo deviceInfo; readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor _eventProcessor; - internal readonly IFlagChangedEventManager flagChangedEventManager; // exposed for testing + readonly IFlagTracker _flagTracker; private readonly Logger _log; // Mutable client state (some state is also in the ConnectionManager) @@ -83,6 +83,9 @@ public sealed class LdClient : ILdClient /// public bool Initialized => _connectionManager.Initialized; + /// + public IFlagTracker FlagTracker => _flagTracker; + /// /// Indicates which platform the SDK is built for. /// @@ -107,19 +110,6 @@ public sealed class LdClient : ILdClient /// public static PlatformType PlatformType => UserMetadata.PlatformType; - /// - public event EventHandler FlagChanged - { - add - { - flagChangedEventManager.FlagChanged += value; - } - remove - { - flagChangedEventManager.FlagChanged -= value; - } - } - // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) LdClient() { } @@ -139,10 +129,12 @@ public event EventHandler FlagChanged _log.Info("Starting LaunchDarkly Client {0}", Version); deviceInfo = Factory.CreateDeviceInfo(configuration, _log); - flagChangedEventManager = Factory.CreateFlagChangedEventManager(configuration, _log); _user = DecorateUser(user); + var flagTracker = new FlagTrackerImpl(_log); + _flagTracker = flagTracker; + var persistentStore = configuration.PersistentDataStoreFactory is null ? new DefaultPersistentDataStore(_log.SubLogger(LogNames.DataStoreSubLog)) : configuration.PersistentDataStoreFactory.CreatePersistentDataStore(_context); @@ -151,7 +143,7 @@ public event EventHandler FlagChanged persistentStore, _log.SubLogger(LogNames.DataStoreSubLog) ); - _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(_dataStore, flagChangedEventManager); + _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(_dataStore, flagTracker); _dataStore.Preload(_user); _dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs deleted file mode 100644 index 8d3786fd..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/FlagChangedEventTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using LaunchDarkly.Sdk.Internal; -using LaunchDarkly.TestHelpers; -using Xunit; -using Xunit.Abstractions; - -using static LaunchDarkly.Sdk.Client.TestUtil; - -namespace LaunchDarkly.Sdk.Client -{ - public class FlagChangedEventTests : BaseTest - { - private const string INT_FLAG = "int-flag"; - private const string DOUBLE_FLAG = "double-flag"; - - public FlagChangedEventTests(ITestOutputHelper testOutput) : base(testOutput) { } - - FlagChangedEventManager Manager() - { - return new FlagChangedEventManager(testLogger); - } - - [Fact] - public void CanRegisterListeners() - { - var manager = Manager(); - var listener1 = new EventSink(); - var listener2 = new EventSink(); - manager.FlagChanged += listener1.Add; - manager.FlagChanged += listener2.Add; - - manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(7), LdValue.Of(6), false)); - var event1a = listener1.ExpectValue(); - var event2a = listener2.ExpectValue(); - - manager.FireEvent(new FlagChangedEventArgs(DOUBLE_FLAG, LdValue.Of(10.5f), LdValue.Of(9.5f), false)); - var event1b = listener1.ExpectValue(); - var event2b = listener2.ExpectValue(); - - Assert.Equal(INT_FLAG, event1a.Key); - Assert.Equal(INT_FLAG, event2a.Key); - Assert.Equal(7, event1a.NewValue.AsInt); - Assert.Equal(7, event2a.NewValue.AsInt); - Assert.Equal(6, event1a.OldValue.AsInt); - Assert.Equal(6, event2a.OldValue.AsInt); - Assert.False(event1a.FlagWasDeleted); - Assert.False(event2a.FlagWasDeleted); - - Assert.Equal(DOUBLE_FLAG, event1b.Key); - Assert.Equal(DOUBLE_FLAG, event2b.Key); - Assert.Equal(10.5, event1b.NewValue.AsFloat); - Assert.Equal(10.5, event2b.NewValue.AsFloat); - Assert.Equal(9.5, event1b.OldValue.AsFloat); - Assert.Equal(9.5, event2b.OldValue.AsFloat); - Assert.False(event1b.FlagWasDeleted); - Assert.False(event2b.FlagWasDeleted); - } - - [Fact] - public void CanUnregisterListeners() - { - var manager = Manager(); - var listener1 = new EventSink(); - var listener2 = new EventSink(); - manager.FlagChanged += listener1.Add; - manager.FlagChanged += listener2.Add; - - manager.FlagChanged -= listener1.Add; - - manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(7), LdValue.Of(6), false)); - - var e = listener2.ExpectValue(); - Assert.Equal(INT_FLAG, e.Key); - Assert.Equal(7, e.NewValue.AsInt); - Assert.Equal(6, e.OldValue.AsInt); - - listener1.ExpectNoValue(); - } - - [Fact] - public void ListenerCallIsDeferred() - { - // This verifies that we are not making synchronous calls to listeners, so they cannot deadlock by trying to - // acquire some resource that is being held by the caller. There are three possible things that can happen: - // 1. We call the listener synchronously; listener.Called gets set to true before FlagWasUpdated returns. Fail. - // 2. The listener is queued somewhere for later execution, so it doesn't even start to run before the end of - // the test. Pass. - // 3. The listener starts executing immediately on another thread; that's OK too, because the lock(locker) block - // ensures it won't set Called until after we have checked it. Pass. - var manager = Manager(); - var locker = new object(); - var called = new AtomicBoolean(false); - - manager.FlagChanged += (sender, args) => - { - lock (locker) - { - called.GetAndSet(true); - } - }; - - lock (locker) - { - manager.FireEvent(new FlagChangedEventArgs(INT_FLAG, LdValue.Of(2), LdValue.Of(1), false)); - Assert.False(called.Get()); - } - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index 5f676f66..c28f4339 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -118,9 +118,7 @@ public EvaluationDetail StringVariationDetail(string key, string default public bool Initialized => true; public bool Offline => false; -#pragma warning disable CS0067 // FlagChanged isn't used in tests - public event System.EventHandler FlagChanged; -#pragma warning restore CS0067 + public IFlagTracker FlagTracker => null; public IDictionary AllFlags() => throw new System.NotImplementedException(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs similarity index 83% rename from tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs rename to tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 0e2317a8..755fe840 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -1,4 +1,5 @@ -using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -10,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources public class DataSourceUpdateSinkImplTest : BaseTest { private readonly InMemoryDataStore _store; - private readonly FlagChangedEventManager _flagChangedEventManager; + private readonly FlagTrackerImpl _flagTracker; private readonly DataSourceUpdateSinkImpl _updateSink; private readonly User _basicUser = User.WithKey("user-key"); private readonly User _otherUser = User.WithKey("other-key"); @@ -18,8 +19,8 @@ public class DataSourceUpdateSinkImplTest : BaseTest public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) { _store = new InMemoryDataStore(); - _flagChangedEventManager = new FlagChangedEventManager(testLogger); - _updateSink = new DataSourceUpdateSinkImpl(_store, _flagChangedEventManager); + _flagTracker = new FlagTrackerImpl(testLogger); + _updateSink = new DataSourceUpdateSinkImpl(_store, _flagTracker); } [Fact] @@ -48,8 +49,8 @@ public void UpsertPassesDataToStore() [Fact] public void NoEventsAreSentForFirstInit() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); _updateSink.Init(_basicUser, initData); @@ -60,8 +61,8 @@ public void NoEventsAreSentForFirstInit() [Fact] public void NoEventsAreSentForUpsertIfNeverInited() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; _updateSink.Upsert(_basicUser, "key1", new FeatureFlagBuilder().Build().ToItemDescriptor()); @@ -71,8 +72,8 @@ public void NoEventsAreSentForUpsertIfNeverInited() [Fact] public void EventIsSentForChangedFlagOnInit() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData1 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) @@ -90,14 +91,14 @@ public void EventIsSentForChangedFlagOnInit() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Of(false), e.NewValue); - Assert.False(e.FlagWasDeleted); + Assert.False(e.Deleted); } [Fact] public void EventIsSentForAddedFlagOnInit() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData1 = new DataSetBuilder() .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) @@ -114,14 +115,14 @@ public void EventIsSentForAddedFlagOnInit() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Null, e.OldValue); Assert.Equal(LdValue.Of(false), e.NewValue); - Assert.False(e.FlagWasDeleted); + Assert.False(e.Deleted); } [Fact] public void EventIsSentForDeletedFlagOnInit() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData1 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) @@ -138,14 +139,14 @@ public void EventIsSentForDeletedFlagOnInit() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Null, e.NewValue); - Assert.True(e.FlagWasDeleted); + Assert.True(e.Deleted); } [Fact] public void EventIsSentForChangedFlagOnUpsert() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) @@ -160,14 +161,14 @@ public void EventIsSentForChangedFlagOnUpsert() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Of(false), e.NewValue); - Assert.False(e.FlagWasDeleted); + Assert.False(e.Deleted); } [Fact] public void EventIsSentForAddedFlagOnUpsert() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder() .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) @@ -181,14 +182,14 @@ public void EventIsSentForAddedFlagOnUpsert() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Null, e.OldValue); Assert.Equal(LdValue.Of(false), e.NewValue); - Assert.False(e.FlagWasDeleted); + Assert.False(e.Deleted); } [Fact] public void EventIsSentForDeletedFlagOnUpsert() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) @@ -202,14 +203,14 @@ public void EventIsSentForDeletedFlagOnUpsert() Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Null, e.NewValue); - Assert.True(e.FlagWasDeleted); + Assert.True(e.Deleted); } [Fact] public void EventIsNotSentIfUpsertFailsDueToLowerVersion() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) @@ -226,8 +227,8 @@ public void EventIsNotSentIfUpsertFailsDueToLowerVersion() [Fact] public void ValueChangesAreTrackedSeparatelyForEachUser() { - var events = new EventSink(); - _flagChangedEventManager.FlagChanged += events.Add; + var events = new EventSink(); + _flagTracker.FlagValueChanged += events.Add; var initDataForBasicUser = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("a")).Variation(1).Build()) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 83a83c72..cdd8391e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -24,7 +24,7 @@ IDataSource MakeDataSource() var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); user = User.WithKey("user1Key"); return new PollingDataSource( - new DataSourceUpdateSinkImpl(_store, new FlagChangedEventManager(testLogger)), + new DataSourceUpdateSinkImpl(_store, new FlagTrackerImpl(testLogger)), user, mockFeatureFlagRequestor, TimeSpan.FromSeconds(30), diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 101cd28a..1f669558 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -369,7 +369,7 @@ public async Task BackgroundModeForcesPollingAsync() // configure the polling endpoint, so if the client makes a streaming request here it'll fail. switchable.Target = SetupResponse(_flagData2, UpdateMode.Polling); var receivedChangeSignal = new SemaphoreSlim(0, 1); - client.FlagChanged += (sender, args) => + client.FlagTracker.FlagValueChanged += (sender, args) => { receivedChangeSignal.Release(); }; @@ -421,7 +421,7 @@ public async Task BackgroundModePollingCanBeDisabledAsync() VerifyFlagValues(client, _flagData1); // we should *not* have done a poll var receivedChangeSignal = new SemaphoreSlim(0, 1); - client.FlagChanged += (sender, args) => + client.FlagTracker.FlagValueChanged += (sender, args) => { receivedChangeSignal.Release(); }; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs new file mode 100644 index 00000000..365dbd7e --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs @@ -0,0 +1,67 @@ +using System; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client +{ + public class LdClientListenersTest : BaseTest + { + public LdClientListenersTest(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void ClientSendsFlagValueChangeEvents() + { + var user = User.WithKey("user-key"); + IDataSourceUpdateSink updateSink = null; + var mockDataSourceFactory = new MockDataSourceFactoryFromLambda((ctx, up, u, bg) => + { + updateSink = up; + return new ComponentsImpl.NullDataSource(); + }); + var config = TestUtil.TestConfig("mobile-key") + .DataSource(mockDataSourceFactory) + .Logging(testLogging) + .Build(); + + var flagKey = "flagkey"; + var initialData = new DataSetBuilder() + .Add(flagKey, 1, LdValue.Of(true), 0) + .Build(); + + using (var client = TestUtil.CreateClient(config, user)) + { + updateSink.Init(user, initialData); + + var eventSink1 = new EventSink(); + var eventSink2 = new EventSink(); + EventHandler listener1 = eventSink1.Add; + EventHandler listener2 = eventSink2.Add; + client.FlagTracker.FlagValueChanged += listener1; + client.FlagTracker.FlagValueChanged += listener2; + + eventSink1.ExpectNoValue(); + eventSink2.ExpectNoValue(); + + var updatedFlag = new FeatureFlagBuilder() + .Version(2) + .Value(LdValue.Of(false)) + .Variation(1) + .Build(); + updateSink.Upsert(user, flagKey, updatedFlag.ToItemDescriptor()); + + var event1 = eventSink1.ExpectValue(); + var event2 = eventSink2.ExpectValue(); + Assert.Equal(flagKey, event1.Key); + Assert.Equal(LdValue.Of(true), event1.OldValue); + Assert.Equal(LdValue.Of(false), event1.NewValue); + Assert.Equal(event1, event2); + + eventSink1.ExpectNoValue(); + eventSink2.ExpectNoValue(); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index c06e78b3..ba0a6424 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -390,22 +390,6 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() } } - [Fact] - public void CanRegisterAndUnregisterFlagChangedHandlers() - { - using (var client = Client()) - { - EventHandler handler1 = (sender, args) => { }; - EventHandler handler2 = (sender, args) => { }; - var eventManager = client.flagChangedEventManager as FlagChangedEventManager; - client.FlagChanged += handler1; - client.FlagChanged += handler2; - client.FlagChanged -= handler1; - Assert.False(eventManager.IsHandlerRegistered(handler1)); - Assert.True(eventManager.IsHandlerRegistered(handler2)); - } - } - [Fact] public void FlagsAreLoadedFromPersistentStorageByDefault() { From aff2b641448aef83620145ce0ff9d497c36e8b64 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 28 Sep 2021 10:57:18 -0700 Subject: [PATCH 361/499] (#4) implement test data source (#130) --- .../Integrations/TestData.cs | 586 ++++++++++++++++++ .../Integrations/TestDataTest.cs | 225 +++++++ .../Integrations/TestDataWithClientTest.cs | 83 +++ .../LdClientEvaluationTests.cs | 143 +++-- .../LdClientEventTests.cs | 88 ++- .../LdClientListenersTest.cs | 24 +- .../LdClientTests.cs | 38 +- .../ModelBuilders.cs | 11 + .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 20 +- 9 files changed, 1047 insertions(+), 171 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/TestData.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs new file mode 100644 index 00000000..7aae33fc --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; + +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// A mechanism for providing dynamically updatable feature flag state in a simplified form to an SDK + /// client in test scenarios. + /// + /// + /// + /// This mechanism does not use any external resources. It provides only the data that the application + /// has put into it using the method. + /// + /// + /// The example code below uses a simple boolean flag, but more complex configurations are possible using + /// the methods of the that is returned by . + /// + /// + /// If the same instance is used to configure multiple + /// instances, any changes made to the data will propagate to all of the s. + /// + /// + /// + /// + /// var td = TestData.DataSource(); + /// td.Update(td.Flag("flag-key-1").BooleanFlag().Variation(true)); + /// + /// var config = Configuration.Builder("sdk-key") + /// .DataSource(td) + /// .Build(); + /// var client = new LdClient(config); + /// + /// // flags can be updated at any time: + /// td.update(testData.flag("flag-key-2") + /// .VariationForUser("some-user-key", false)); + /// + /// + public sealed class TestData : IDataSourceFactory + { + #region Private fields + + private readonly object _lock = new object(); + private readonly Dictionary _currentFlagVersions = + new Dictionary(); + private readonly Dictionary _currentBuilders = + new Dictionary(); + private readonly List _instances = new List(); + + #endregion + + #region Private constructor + + private TestData() { } + + #endregion + + #region Public methods + + /// + /// Creates a new instance of the test data source. + /// + /// + /// See for details. + /// + /// a new configurable test data source + public static TestData DataSource() => new TestData(); + + /// + /// Creates or copies a for building a test flag configuration. + /// + /// + /// + /// If this flag key has already been defined in this instance, then + /// the builder starts with the same configuration that was last provided for this flag. + /// + /// + /// Otherwise, it starts with a new default configuration in which the flag has true + /// and false variations, and is true by default for all users. You can change + /// any of those properties, and provide more complex behavior, using the + /// methods. + /// + /// + /// Once you have set the desired configuration, pass the builder to + /// . + /// + /// + /// the flag key + /// a flag configuration builder + /// + public FlagBuilder Flag(string key) + { + FlagBuilder existingBuilder; + lock (_lock) + { + _currentBuilders.TryGetValue(key, out existingBuilder); + } + if (existingBuilder != null) + { + return new FlagBuilder(existingBuilder); + } + return new FlagBuilder(key).BooleanFlag(); + } + + /// + /// Updates the test data with the specified flag configuration. + /// + /// + /// + /// This has the same effect as if a flag were added or modified on the LaunchDarkly dashboard. + /// It immediately propagates the flag change to any instance(s) that + /// you have already configured to use this . If no + /// has been started yet, it simply adds this flag to the test data which will be provided to any + /// that you subsequently configure. + /// + /// + /// Any subsequent changes to this instance do not affect the test data, + /// unless you call again. + /// + /// + /// a flag configuration builder + /// the same instance + /// + public TestData Update(FlagBuilder flagBuilder) + { + var key = flagBuilder._key; + var clonedBuilder = new FlagBuilder(flagBuilder); + UpdateInternal(key, clonedBuilder); + return this; + } + + private void UpdateInternal(string key, FlagBuilder builder) + { + DataSourceImpl[] instances; + int newVersion; + + lock (_lock) + { + if (!_currentFlagVersions.TryGetValue(key, out var oldVersion)) + { + oldVersion = 0; + } + newVersion = oldVersion + 1; + _currentFlagVersions[key] = newVersion; + if (builder is null) + { + _currentBuilders.Remove(key); + } + else + { + _currentBuilders[key] = builder; + } + instances = _instances.ToArray(); + } + + foreach (var instance in instances) + { + instance.DoUpdate(key, builder.CreateFlag(newVersion, instance.User)); + } + } + + /// + public IDataSource CreateDataSource( + LdClientContext context, + IDataSourceUpdateSink updateSink, + User currentUser, + bool inBackground + ) + { + var instance = new DataSourceImpl( + this, + updateSink, + currentUser, + context.BaseLogger.SubLogger("DataSource.TestData") + ); + lock (_lock) + { + _instances.Add(instance); + } + return instance; + } + + internal FullDataSet MakeInitData(User user) + { + lock (_lock) + { + var b = ImmutableList.CreateBuilder>(); + foreach (var fb in _currentBuilders) + { + if (!_currentFlagVersions.TryGetValue(fb.Key, out var version)) + { + version = 1; + _currentFlagVersions[fb.Key] = version; + } + b.Add(new KeyValuePair(fb.Key, + fb.Value.CreateFlag(version, user))); + } + return new FullDataSet(b.ToImmutable()); + } + } + + internal void ClosedInstance(DataSourceImpl instance) + { + lock (_lock) + { + _instances.Remove(instance); + } + } + + #endregion + + #region Public inner types + + /// + /// A builder for feature flag configurations to be used with . + /// + /// + /// + public sealed class FlagBuilder + { + #region Private/internal fields + + private const int TrueVariationForBoolean = 0; + private const int FalseVariationForBoolean = 1; + + internal readonly string _key; + private List _variations; + private int _defaultVariation; + private Dictionary _variationByUserKey; + private Func _variationFunc; + private FeatureFlag _preconfiguredFlag; + + #endregion + + #region Internal constructors + + internal FlagBuilder(string key) + { + _key = key; + _variations = new List(); + _defaultVariation = 0; + _variationByUserKey = new Dictionary(); + } + + internal FlagBuilder(FlagBuilder from) + { + _key = from._key; + _variations = new List(from._variations); + _defaultVariation = from._defaultVariation; + _variationFunc = from._variationFunc; + _variationByUserKey = new Dictionary(from._variationByUserKey); + _preconfiguredFlag = from._preconfiguredFlag; + } + + #endregion + + #region Public methods + + /// + /// A shortcut for setting the flag to use the standard boolean configuration. + /// + /// + /// This is the default for all new flags created with . + /// The flag will have two variations, true and false (in that order). When + /// using evaluation reasons, the reason will be set to + /// whenever the value is true, and whenever the + /// value is false. + /// + /// the builder + public FlagBuilder BooleanFlag() => + IsBooleanFlag ? this : Variations(LdValue.Of(true), LdValue.Of(false)); + + /// + /// Sets the flag to return the specified boolean variation for all users by default. + /// + /// + /// The flag's variations are set to true and false if they are not already + /// (equivalent to calling ). + /// + /// the desired true/false variation to be returned for all users + /// the builder + public FlagBuilder Variation(bool variation) => + BooleanFlag().Variation(VariationForBoolean(variation)); + + /// + /// Sets the flag to return the specified variation for all users by default. + /// + /// + /// The variation is specified by number, out of whatever variation values have already been + /// defined. + /// + /// the desired variation: 0 for the first, 1 for the second, etc. + /// the builder + public FlagBuilder Variation(int variationIndex) + { + _defaultVariation = variationIndex; + return this; + } + + /// + /// Sets the flag to return the specified variation value for all users by default. + /// + /// + /// The value may be of any JSON type, as defined by . If the value + /// matches one of the values previously specified with , + /// then the variation index is set to the index of that value. Otherwise, the value is + /// added to the variation list. + /// + /// the desired value to be returned for all users + /// the builder + public FlagBuilder Variation(LdValue value) + { + AddVariationIfNotDefined(value); + _defaultVariation = _variations.IndexOf(value); + _variationFunc = null; + return this; + } + + /// + /// Sets the flag to return the specified boolean variation for a specific user key, + /// overriding any other defaults. + /// + /// + /// The flag's variations are set to true and false if they are not already + /// (equivalent to calling ). + /// + /// a user key + /// the desired true/false variation to be returned for this user + /// the builder + public FlagBuilder VariationForUser(string userKey, bool variation) => + BooleanFlag().VariationForUser(userKey, VariationForBoolean(variation)); + + /// + /// Sets the flag to return the specified variation for a specific user key, overriding + /// any other defaults. + /// + /// + /// The variation is specified by number, out of whatever variation values have already been + /// defined. + /// + /// a user key + /// the desired variation to be returned for this user when + /// targeting is on: 0 for the first, 1 for the second, etc. + /// the builder + public FlagBuilder VariationForUser(string userKey, int variationIndex) + { + _variationByUserKey[userKey] = variationIndex; + return this; + } + + /// + /// Sets the flag to return the specified variation value for a specific user key, overriding + /// any other defaults. + /// + /// + /// The value may be of any JSON type, as defined by . If the value + /// matches one of the values previously specified with , + /// then the variation index is set to the index of that value. Otherwise, the value is + /// added to the variation list. + /// + /// a user key + /// the desired value to be returned for this user + /// the builder + public FlagBuilder VariationForUser(string userKey, LdValue value) + { + AddVariationIfNotDefined(value); + _variationByUserKey[userKey] = _variations.IndexOf(value); + return this; + } + + /// + /// Sets the flag to use a function to determine whether to return true or false for + /// any given user. + /// + /// + /// + /// The function takes a user and returns , , + /// or . A result means that the flag will + /// fall back to its default variation for all users. + /// + /// + /// The flag's variations are set to true and false if they are not already + /// (equivalent to calling ). + /// + /// + /// The function is only called for users who were not already specified by + /// . + /// + /// + /// a function to determine the variation + /// the builder + public FlagBuilder VariationFunc(Func variationFunc) => + BooleanFlag().VariationFunc(user => + { + var b = variationFunc(user); + return b.HasValue ? VariationForBoolean(b.Value) : (int?)null; + }); + + /// + /// Sets the flag to use a function to determine the variation index to return for + /// any given user. + /// + /// + /// + /// The function takes a user and returns an integer variation index or . + /// A result means that the flag will fall back to its default + /// variation for all users. + /// + /// + /// The function is only called for users who were not already specified by + /// . + /// + /// + /// a function to determine the variation + /// the builder + public FlagBuilder VariationFunc(Func variationFunc) + { + _variationFunc = variationFunc; + return this; + } + + /// + /// Sets the flag to use a function to determine the variation value to return for + /// any given user. + /// + /// + /// + /// The function takes a user and returns an or . + /// A result means that the flag will fall back to its default + /// variation for all users. + /// + /// + /// The value returned by the function must be one of the values previously specified + /// with ; otherwise it will be ignored. + /// + /// + /// The function is only called for users who were not already specified by + /// . + /// + /// + /// a function to determine the variation + /// the builder + public FlagBuilder VariationFunc(Func variationFunc) => + VariationFunc(user => + { + var v = variationFunc(user); + if (!v.HasValue || !_variations.Contains(v.Value)) + { + return null; + } + return _variations.IndexOf(v.Value); + }); + + /// + /// Changes the allowable variation values for the flag. + /// + /// + /// The value may be of any JSON type, as defined by . For instance, a + /// boolean flag normally has LdValue.Of(true), LdValue.Of(false); a string-valued + /// flag might have LdValue.Of("red"), LdValue.Of("green"), LdValue.Of("blue"); etc. + /// + /// the desired variations + /// the builder + public FlagBuilder Variations(params LdValue[] values) + { + _variations.Clear(); + _variations.AddRange(values); + return this; + } + + // For testing only + internal FlagBuilder PreconfiguredFlag(FeatureFlag preconfiguredFlag) + { + _preconfiguredFlag = preconfiguredFlag; + return this; + } + + #endregion + + #region Internal methods + + internal ItemDescriptor CreateFlag(int version, User user) + { + if (_preconfiguredFlag != null) + { + return new ItemDescriptor(version, new FeatureFlag( + _preconfiguredFlag.Value, + _preconfiguredFlag.Variation, + _preconfiguredFlag.Reason, + _preconfiguredFlag.Version > version ? _preconfiguredFlag.Version : version, + _preconfiguredFlag.FlagVersion, + _preconfiguredFlag.TrackEvents, + _preconfiguredFlag.TrackReason, + _preconfiguredFlag.DebugEventsUntilDate)); + } + int variation; + if (!_variationByUserKey.TryGetValue(user.Key, out variation)) + { + variation = _variationFunc?.Invoke(user) ?? _defaultVariation; + } + var value = (variation < 0 || variation >= _variations.Count) ? LdValue.Null : + _variations[variation]; + var reason = variation == 0 ? EvaluationReason.FallthroughReason : + EvaluationReason.OffReason; + var flag = new FeatureFlag( + value, + variation, + reason, + version, + null, + false, + false, + null + ); + return new ItemDescriptor(version, flag); + } + + internal bool IsBooleanFlag => + _variations.Count == 2 && + _variations[TrueVariationForBoolean] == LdValue.Of(true) && + _variations[FalseVariationForBoolean] == LdValue.Of(false); + + internal void AddVariationIfNotDefined(LdValue value) + { + if (!_variations.Contains(value)) + { + _variations.Add(value); + } + } + + internal static int VariationForBoolean(bool value) => + value ? TrueVariationForBoolean : FalseVariationForBoolean; + + #endregion + } + + #endregion + + #region Internal inner type + + internal class DataSourceImpl : IDataSource + { + private readonly TestData _parent; + private readonly IDataSourceUpdateSink _updateSink; + private readonly Logger _log; + + internal readonly User User; + + internal DataSourceImpl(TestData parent, IDataSourceUpdateSink updateSink, User user, Logger log) + { + _parent = parent; + _updateSink = updateSink; + User = user; + _log = log; + } + + public Task Start() + { + _updateSink.Init(User, _parent.MakeInitData(User)); + return Task.FromResult(true); + } + + public bool Initialized => true; + + public void Dispose() => + _parent.ClosedInstance(this); + + internal void DoUpdate(string key, ItemDescriptor item) + { + _log.Debug("updating \"{0}\" to {1}", key, LogValues.Defer(() => + item.Item is null ? "" : DataModelSerialization.SerializeFlag(item.Item))); + _updateSink.Upsert(User, key, item); + } + } + + #endregion + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs new file mode 100644 index 00000000..20242309 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LaunchDarkly.Sdk.Client.Interfaces; +using Xunit; +using Xunit.Abstractions; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class TestDataTest : BaseTest + { + private static readonly User _initialUser = User.WithKey("user0"); + + private readonly TestData _td = TestData.DataSource(); + private readonly MockDataSourceUpdateSink _updates = new MockDataSourceUpdateSink(); + private readonly LdClientContext _context; + + public TestDataTest(ITestOutputHelper testOutput) : base(testOutput) + { + _context = new LdClientContext(Configuration.Builder("key").Logging(testLogging).Build()); + } + + [Fact] + public void InitializesWithEmptyData() + { + CreateAndStart(); + + var initData = _updates.ExpectInit(_initialUser); + Assert.Empty(initData.Items); + } + + [Fact] + public void InitializesWithFlags() + { + _td.Update(_td.Flag("flag1").Variation(true)) + .Update(_td.Flag("flag2").Variation(false)); + + CreateAndStart(); + + var initData = _updates.ExpectInit(_initialUser); + var data = initData.Items.OrderBy(kv => kv.Key); + Assert.Collection(data, + FlagItemAssertion("flag1", 1, LdValue.Of(true), 0), + FlagItemAssertion("flag2", 1, LdValue.Of(false), 1) + ); + } + + [Fact] + public void AddsFlag() + { + CreateAndStart(); + _updates.ExpectInit(_initialUser); + + _td.Update(_td.Flag("flag1").Variation(true)); + + var item = _updates.ExpectUpsert(_initialUser, "flag1"); + VerifyUpdate(item, 1, LdValue.Of(true), 0); + } + + [Fact] + public void UpdatesFlag() + { + _td.Update(_td.Flag("flag1").Variation(true)); + + CreateAndStart(); + _updates.ExpectInit(_initialUser); + + _td.Update(_td.Flag("flag1").Variation(false)); + + var item = _updates.ExpectUpsert(_initialUser, "flag1"); + VerifyUpdate(item, 2, LdValue.Of(false), 1); + } + + [Fact] + public void FlagConfigBoolean() + { + var expectTrue = FlagValueAssertion(LdValue.Of(true), 0); + var expectFalse = FlagValueAssertion(LdValue.Of(false), 1); + + VerifyFlag(f => f, expectTrue); + VerifyFlag(f => f.BooleanFlag(), expectTrue); + VerifyFlag(f => f.Variation(true), expectTrue); + VerifyFlag(f => f.Variation(false), expectFalse); + + VerifyFlag(f => f.Variation(true).VariationForUser(_initialUser.Key, false), expectFalse); + VerifyFlag(f => f.Variation(false).VariationForUser(_initialUser.Key, true), expectTrue); + + VerifyFlag(f => f.Variation(true).VariationFunc(u => false), expectFalse); + VerifyFlag(f => f.Variation(false).VariationFunc(u => true), expectTrue); + + // VariationForUser takes precedence over VariationFunc + VerifyFlag(f => f.Variation(true).VariationForUser(_initialUser.Key, false) + .VariationFunc(u => true), expectFalse); + VerifyFlag(f => f.Variation(false).VariationForUser(_initialUser.Key, true) + .VariationFunc(u => false), expectTrue); + } + + [Fact] + public void FlagConfigByVariationIndex() + { + LdValue aVal = LdValue.Of("a"), bVal = LdValue.Of("b"); + int aIndex = 0, bIndex = 1; + var ab = new LdValue[] { aVal, bVal }; + var expectA = FlagValueAssertion(LdValue.Of("a"), aIndex); + var expectB = FlagValueAssertion(LdValue.Of("b"), bIndex); + + VerifyFlag(f => f.Variations(ab), expectA); + VerifyFlag(f => f.Variations(ab).Variation(aIndex), expectA); + VerifyFlag(f => f.Variations(ab).Variation(bIndex), expectB); + + VerifyFlag(f => f.Variations(ab).Variation(aIndex).VariationForUser(_initialUser.Key, bIndex), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bIndex).VariationForUser(_initialUser.Key, aIndex), expectA); + + VerifyFlag(f => f.Variations(ab).Variation(aIndex).VariationFunc(u => bIndex), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bIndex).VariationFunc(u => aIndex), expectA); + + // VariationForUser takes precedence over VariationFunc + VerifyFlag(f => f.Variations(ab).Variation(aIndex).VariationForUser(_initialUser.Key, bIndex) + .VariationFunc(u => aIndex), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bIndex).VariationForUser(_initialUser.Key, aIndex) + .VariationFunc(u => bIndex), expectA); + } + + [Fact] + public void FlagConfigByValue() + { + LdValue aVal = LdValue.Of("a"), bVal = LdValue.Of("b"); + int aIndex = 0, bIndex = 1; + var ab = new LdValue[] { aVal, bVal }; + var expectA = FlagValueAssertion(LdValue.Of("a"), aIndex); + var expectB = FlagValueAssertion(LdValue.Of("b"), bIndex); + + VerifyFlag(f => f.Variations(ab).Variation(aVal), expectA); + VerifyFlag(f => f.Variations(ab).Variation(bVal), expectB); + + VerifyFlag(f => f.Variations(ab).Variation(aVal).VariationForUser(_initialUser.Key, bVal), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bVal).VariationForUser(_initialUser.Key, aVal), expectA); + + VerifyFlag(f => f.Variations(ab).Variation(aVal).VariationFunc(u => bVal), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bVal).VariationFunc(u => aVal), expectA); + + // VariationForUser takes precedence over VariationFunc + VerifyFlag(f => f.Variations(ab).Variation(aVal).VariationForUser(_initialUser.Key, bVal) + .VariationFunc(u => aVal), expectB); + VerifyFlag(f => f.Variations(ab).Variation(bVal).VariationForUser(_initialUser.Key, aVal) + .VariationFunc(u => bVal), expectA); + } + + [Fact] + public void UsePreconfiguredFlag() + { + CreateAndStart(); + _updates.ExpectInit(_initialUser); + + var flag = new FeatureFlagBuilder().Version(1).Value(true).Variation(0).Reason(EvaluationReason.OffReason) + .TrackEvents(true).TrackReason(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(123)).Build(); + _td.Update(_td.Flag("flag1").PreconfiguredFlag(flag)); + + var item1 = _updates.ExpectUpsert(_initialUser, "flag1"); + Assert.Equal(flag, item1.Item); + + _td.Update(_td.Flag("flag1").PreconfiguredFlag(flag)); + + var item2 = _updates.ExpectUpsert(_initialUser, "flag1"); + var updatedFlag = new FeatureFlagBuilder(flag).Version(2).Build(); + Assert.Equal(updatedFlag, item2.Item); + } + + private void CreateAndStart() + { + var ds = _td.CreateDataSource(_context, _updates, _initialUser, false); + var started = ds.Start(); + Assert.True(started.IsCompleted); + } + + private Action> FlagItemAssertion( + string key, + int version, + LdValue value, + int? variation + ) + { + return kv => + { + Assert.Equal(key, kv.Key); + VerifyUpdate(kv.Value, version, value, variation); + }; + } + + private Action FlagValueAssertion( + LdValue value, + int? variation + ) + { + return item => + { + Assert.Equal(value, item.Item.Value); + Assert.Equal(variation, item.Item.Variation); + }; + } + + private void VerifyUpdate(ItemDescriptor item, int version, LdValue value, int? variation) + { + Assert.Equal(version, item.Version); + Assert.Equal(value, item.Item.Value); + Assert.Equal(variation, item.Item.Variation); + } + + private void VerifyFlag(Func builderFn, + Action assertion) + { + var tdTemp = TestData.DataSource(); + using (var ds = tdTemp.CreateDataSource(_context, _updates, _initialUser, false)) + { + ds.Start(); + _updates.ExpectInit(_initialUser); + tdTemp.Update(builderFn(tdTemp.Flag("flag"))); + var up = _updates.ExpectUpsert(_initialUser, "flag"); + assertion(up); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs new file mode 100644 index 00000000..6692be60 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs @@ -0,0 +1,83 @@ +using System; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class TestDataWithClientTest : BaseTest + { + private readonly TestData _td = TestData.DataSource(); + private readonly Configuration _config; + private readonly User _user = User.WithKey("userkey"); + + public TestDataWithClientTest(ITestOutputHelper testOutput) : base(testOutput) + { + _config = Configuration.Builder("mobile-key") + .DataSource(_td) + .Events(Components.NoEvents) + .Build(); + } + + [Fact] + public void InitializesWithEmptyData() + { + using (var client = LdClient.Init(_config, _user, TimeSpan.FromSeconds(1))) + { + Assert.True(client.Initialized); + } + } + + [Fact] + public void InitializesWithFlag() + { + _td.Update(_td.Flag("flag").Variation(true)); + + using (var client = LdClient.Init(_config, _user, TimeSpan.FromSeconds(1))) + { + Assert.True(client.BoolVariation("flag", false)); + } + } + + [Fact] + public void UpdatesFlag() + { + using (var client = LdClient.Init(_config, _user, TimeSpan.FromSeconds(1))) + { + Assert.False(client.BoolVariation("flag", false)); + + _td.Update(_td.Flag("flag").Variation(true)); + + Assert.True(client.BoolVariation("flag", false)); + } + } + + [Fact] + public void CanSetValuePerUser() + { + _td.Update(_td.Flag("flag") + .Variations(LdValue.Of("red"), LdValue.Of("green"), LdValue.Of("blue")) + .Variation(LdValue.Of("red")) + .VariationForUser("user1", LdValue.Of("green")) + .VariationForUser("user2", LdValue.Of("blue")) + .VariationFunc(user => + user.GetAttribute(UserAttribute.ForName("favoriteColor")) + )); + var user1 = User.WithKey("user1"); + var user2 = User.WithKey("user2"); + var user3 = User.Builder("user3").Custom("favoriteColor", "green").Build(); + + using (var client = LdClient.Init(_config, user1, TimeSpan.FromSeconds(1))) + { + Assert.Equal("green", client.StringVariation("flag", "")); + + client.Identify(user2, TimeSpan.FromSeconds(1)); + + Assert.Equal("blue", client.StringVariation("flag", "")); + + client.Identify(user3, TimeSpan.FromSeconds(1)); + + Assert.Equal("green", client.StringVariation("flag", "")); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs index 7d6601f4..0cd7cfa5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using LaunchDarkly.Sdk.Client.Integrations; using Xunit; using Xunit.Abstractions; @@ -6,32 +7,32 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientEvaluationTests : BaseTest { - static readonly string appKey = "some app key"; - static readonly string nonexistentFlagKey = "some flag key"; + const string appKey = "some app key"; + const string flagKey = "flag-key"; + const string nonexistentFlagKey = "some flag key"; static readonly User user = User.WithKey("userkey"); + private readonly TestData _testData = TestData.DataSource(); + public LdClientEvaluationTests(ITestOutputHelper testOutput) : base(testOutput) { } - private LdClient ClientWithFlagsJson(string flagsJson) - { - var config = TestUtil.ConfigWithFlagsJson(user, appKey, flagsJson).Logging(testLogging).Build(); - return TestUtil.CreateClient(config, user); - } + private LdClient MakeClient() => + LdClient.Init(TestUtil.TestConfig("mobile-key").DataSource(_testData).Build(), user, TimeSpan.FromSeconds(1)); [Fact] public void BoolVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(true)); - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag(flagKey).Variation(true)); + using (var client = MakeClient()) { - Assert.True(client.BoolVariation("flag-key", false)); + Assert.True(client.BoolVariation(flagKey, false)); } } [Fact] public void BoolVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = MakeClient()) { Assert.False(client.BoolVariation(nonexistentFlagKey)); } @@ -41,38 +42,39 @@ public void BoolVariationReturnsDefaultForUnknownFlag() public void BoolVariationDetailReturnsValue() { var reason = EvaluationReason.OffReason; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(true), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var flag = new FeatureFlagBuilder().Value(true).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) { var expected = new EvaluationDetail(true, 1, reason); - Assert.Equal(expected, client.BoolVariationDetail("flag-key", false)); + Assert.Equal(expected, client.BoolVariationDetail(flagKey, false)); } } [Fact] public void IntVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3)); - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(3))); + using (var client = MakeClient()) { - Assert.Equal(3, client.IntVariation("flag-key", 0)); + Assert.Equal(3, client.IntVariation(flagKey, 0)); } } [Fact] public void IntVariationCoercesFloatValue() - { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3.0f)); - using (var client = ClientWithFlagsJson(flagsJson)) + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(3.25f))); + using (var client = MakeClient()) { - Assert.Equal(3, client.IntVariation("flag-key", 0)); + Assert.Equal(3, client.IntVariation(flagKey, 0)); } } [Fact] public void IntVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = MakeClient()) { Assert.Equal(1, client.IntVariation(nonexistentFlagKey, 1)); } @@ -80,40 +82,41 @@ public void IntVariationReturnsDefaultForUnknownFlag() [Fact] public void IntVariationDetailReturnsValue() - { + { var reason = EvaluationReason.OffReason; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(3), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of(3)).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) { var expected = new EvaluationDetail(3, 1, reason); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 0)); + Assert.Equal(expected, client.IntVariationDetail(flagKey, 0)); } } [Fact] public void FloatVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2.5f)); - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(2.5f))); + using (var client = MakeClient()) { - Assert.Equal(2.5f, client.FloatVariation("flag-key", 0)); + Assert.Equal(2.5f, client.FloatVariation(flagKey, 0)); } } [Fact] public void FloatVariationCoercesIntValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2)); - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(2))); + using (var client = MakeClient()) { - Assert.Equal(2.0f, client.FloatVariation("flag-key", 0)); + Assert.Equal(2.0f, client.FloatVariation(flagKey, 0)); } } [Fact] public void FloatVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = MakeClient()) { Assert.Equal(0.5f, client.FloatVariation(nonexistentFlagKey, 0.5f)); } @@ -123,28 +126,29 @@ public void FloatVariationReturnsDefaultForUnknownFlag() public void FloatVariationDetailReturnsValue() { var reason = EvaluationReason.OffReason; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of(2.5f), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of(2.5f)).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) { var expected = new EvaluationDetail(2.5f, 1, reason); - Assert.Equal(expected, client.FloatVariationDetail("flag-key", 0.5f)); + Assert.Equal(expected, client.FloatVariationDetail(flagKey, 0.5f)); } } [Fact] public void StringVariationReturnsValue() { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of("string value"))); + using (var client = MakeClient()) { - Assert.Equal("string value", client.StringVariation("flag-key", "")); + Assert.Equal("string value", client.StringVariation(flagKey, "")); } } [Fact] public void StringVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = MakeClient()) { Assert.Equal("d", client.StringVariation(nonexistentFlagKey, "d")); } @@ -154,29 +158,30 @@ public void StringVariationReturnsDefaultForUnknownFlag() public void StringVariationDetailReturnsValue() { var reason = EvaluationReason.OffReason; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value"), 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of("string value")).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) { var expected = new EvaluationDetail("string value", 1, reason); - Assert.Equal(expected, client.StringVariationDetail("flag-key", "")); + Assert.Equal(expected, client.StringVariationDetail(flagKey, "")); } } [Fact] public void JsonVariationReturnsValue() { - var jsonValue = LdValue.Convert.String.ObjectFrom(new Dictionary { { "thing", "stuff" } }); - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue); - using (var client = ClientWithFlagsJson(flagsJson)) + var jsonValue = LdValue.BuildObject().Add("thing", "stuff").Build(); + _testData.Update(_testData.Flag(flagKey).Variation(jsonValue)); + using (var client = MakeClient()) { - Assert.Equal(jsonValue, client.JsonVariation("flag-key", LdValue.Of(3))); + Assert.Equal(jsonValue, client.JsonVariation(flagKey, LdValue.Of(3))); } } [Fact] public void JsonVariationReturnsDefaultForUnknownFlag() { - using (var client = ClientWithFlagsJson("{}")) + using (var client = MakeClient()) { var defaultVal = LdValue.Of(3); Assert.Equal(defaultVal, client.JsonVariation(nonexistentFlagKey, defaultVal)); @@ -185,14 +190,15 @@ public void JsonVariationReturnsDefaultForUnknownFlag() [Fact] public void JsonVariationDetailReturnsValue() - { - var jsonValue = LdValue.Convert.String.ObjectFrom(new Dictionary { { "thing", "stuff" } }); + { + var jsonValue = LdValue.BuildObject().Add("thing", "stuff").Build(); var reason = EvaluationReason.OffReason; - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", jsonValue, 1, reason); - using (var client = ClientWithFlagsJson(flagsJson)) + var flag = new FeatureFlagBuilder().Value(jsonValue).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) { var expected = new EvaluationDetail(jsonValue, 1, reason); - var result = client.JsonVariationDetail("flag-key", LdValue.Of(3)); + var result = client.JsonVariationDetail(flagKey, LdValue.Of(3)); Assert.Equal(expected.Value, result.Value); Assert.Equal(expected.VariationIndex, result.VariationIndex); Assert.Equal(expected.Reason, result.Reason); @@ -202,8 +208,9 @@ public void JsonVariationDetailReturnsValue() [Fact] public void AllFlagsReturnsAllFlagValues() { - var flagsJson = @"{""flag1"":{""value"":""a""},""flag2"":{""value"":""b""}}"; - using (var client = ClientWithFlagsJson(flagsJson)) + _testData.Update(_testData.Flag("flag1").Variation(LdValue.Of("a"))); + _testData.Update(_testData.Flag("flag2").Variation(LdValue.Of("b"))); + using (var client = MakeClient()) { var result = client.AllFlags(); Assert.Equal(2, result.Count); @@ -214,32 +221,32 @@ public void AllFlagsReturnsAllFlagValues() [Fact] public void DefaultValueReturnedIfValueTypeIsDifferent() - { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); - using (var client = ClientWithFlagsJson(flagsJson)) + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of("string value"))); + using (var client = MakeClient()) { - Assert.Equal(3, client.IntVariation("flag-key", 3)); + Assert.Equal(3, client.IntVariation(flagKey, 3)); } } [Fact] public void DefaultValueAndReasonIsReturnedIfValueTypeIsDifferent() - { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Of("string value")); - using (var client = ClientWithFlagsJson(flagsJson)) + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of("string value"))); + using (var client = MakeClient()) { var expected = new EvaluationDetail(3, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); - Assert.Equal(expected, client.IntVariationDetail("flag-key", 3)); + Assert.Equal(expected, client.IntVariationDetail(flagKey, 3)); } } [Fact] public void DefaultValueReturnedIfFlagValueIsNull() - { - string flagsJson = TestUtil.JsonFlagsWithSingleFlag("flag-key", LdValue.Null); - using (var client = ClientWithFlagsJson(flagsJson)) + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Null)); + using (var client = MakeClient()) { - Assert.Equal(3, client.IntVariation("flag-key", 3)); + Assert.Equal(3, client.IntVariation(flagKey, 3)); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 08fd3347..2f4a345f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -1,6 +1,6 @@ using System; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal; using Xunit; using Xunit.Abstractions; @@ -12,26 +12,19 @@ namespace LaunchDarkly.Sdk.Client public class LdClientEventTests : BaseTest { private static readonly User user = User.WithKey("userkey"); + private readonly TestData _testData = TestData.DataSource(); private MockEventProcessor eventProcessor = new MockEventProcessor(); - private IEventProcessorFactory _factory; - - public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { - _factory = new SingleEventProcessorFactory(eventProcessor); - } + private IEventProcessorFactory _factory; - public LdClient MakeClient(User user) => - MakeClient(user, new DataSetBuilder().Build()); - - public LdClient MakeClient(User user, FullDataSet initialData) => - MakeClient(user, DataModelSerialization.SerializeAll(initialData)); - - public LdClient MakeClient(User user, string flagsJson) + public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { - var config = TestUtil.ConfigWithFlagsJson(user, "appkey", flagsJson); - config.Events(_factory).Logging(testLogging); - return TestUtil.CreateClient(config.Build(), user); - } - + _factory = new SingleEventProcessorFactory(eventProcessor); + } + + private LdClient MakeClient(User u) => + LdClient.Init(TestUtil.TestConfig().DataSource(_testData).Events(_factory).Build(), + u, TimeSpan.FromSeconds(1)); + [Fact] public void IdentifySendsIdentifyEvent() { @@ -149,11 +142,13 @@ public void IdentifyDoesNotSendAliasEventIfOptedOUt() User oldUser = User.Builder("anon-key").Anonymous(true).Build(); User newUser = User.WithKey("real-key"); - var config = TestUtil.ConfigWithFlagsJson(oldUser, "appkey", "{}"); - config.Events(_factory).Logging(testLogging); - config.AutoAliasingOptOut(true); + var config = TestUtil.TestConfig() + .Events(_factory) + .Logging(testLogging) + .AutoAliasingOptOut(true) + .Build(); - using (LdClient client = TestUtil.CreateClient(config.Build(), oldUser)) + using (LdClient client = TestUtil.CreateClient(config, oldUser)) { User actualOldUser = client.User; // so we can get any automatic properties that the client added client.Identify(newUser, TimeSpan.FromSeconds(1)); @@ -187,11 +182,10 @@ public void IdentifyDoesNotSendAliasEventIfNewUserIsAnonymousOrOldUserIsNot( [Fact] public void VariationSendsFeatureEventForValidFlag() { - var data = new DataSetBuilder() - .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) - .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)).Build()) - .Build(); - using (LdClient client = MakeClient(user, data)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)).Build(); + _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); + using (LdClient client = MakeClient(user)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -214,11 +208,10 @@ public void VariationSendsFeatureEventForValidFlag() [Fact] public void FeatureEventUsesFlagVersionIfProvided() { - var data = new DataSetBuilder() - .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) - .FlagVersion(1500).Build()) - .Build(); - using (LdClient client = MakeClient(user, data)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .FlagVersion(1500).Build(); + _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); + using (LdClient client = MakeClient(user)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -238,10 +231,9 @@ public void FeatureEventUsesFlagVersionIfProvided() [Fact] public void VariationSendsFeatureEventForDefaultValue() { - var data = new DataSetBuilder() - .Add("flag", new FeatureFlagBuilder().Version(1000).Build()) - .Build(); - using (LdClient client = MakeClient(user, data)) + var flag = new FeatureFlagBuilder().Version(1000).Build(); + _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); + using (LdClient client = MakeClient(user)) { string result = client.StringVariation("flag", "b"); Assert.Equal("b", result); @@ -283,7 +275,7 @@ public void VariationSendsFeatureEventForUnknownFlag() [Fact] public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") + var config = TestUtil.TestConfig() .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) .Events(_factory) .Logging(testLogging); @@ -310,11 +302,10 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() [Fact] public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() { - var data = new DataSetBuilder() - .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) - .TrackReason(true).Reason(EvaluationReason.OffReason).Build()) - .Build(); - using (LdClient client = MakeClient(user, data)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackReason(true).Reason(EvaluationReason.OffReason).Build(); + _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); + using (LdClient client = MakeClient(user)) { string result = client.StringVariation("flag", "b"); Assert.Equal("a", result); @@ -337,12 +328,11 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() [Fact] public void VariationDetailSendsFeatureEventWithReasonForValidFlag() { - var data = new DataSetBuilder() - .Add("flag", new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) - .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)) - .Reason(EvaluationReason.OffReason).Build()) - .Build(); - using (LdClient client = MakeClient(user, data)) + var flag = new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) + .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)) + .Reason(EvaluationReason.OffReason).Build(); + _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); + using (LdClient client = MakeClient(user)) { EvaluationDetail result = client.StringVariationDetail("flag", "b"); Assert.Equal("a", result.Value); @@ -391,7 +381,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() [Fact] public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.ConfigWithFlagsJson(user, "appkey", "{}") + var config = TestUtil.TestConfig() .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) .Events(_factory) .Logging(testLogging); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs index 365dbd7e..abe28cfe 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs @@ -1,6 +1,6 @@ using System; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -15,26 +15,17 @@ public LdClientListenersTest(ITestOutputHelper testOutput) : base(testOutput) { public void ClientSendsFlagValueChangeEvents() { var user = User.WithKey("user-key"); - IDataSourceUpdateSink updateSink = null; - var mockDataSourceFactory = new MockDataSourceFactoryFromLambda((ctx, up, u, bg) => - { - updateSink = up; - return new ComponentsImpl.NullDataSource(); - }); + var testData = TestData.DataSource(); var config = TestUtil.TestConfig("mobile-key") - .DataSource(mockDataSourceFactory) + .DataSource(testData) .Logging(testLogging) .Build(); var flagKey = "flagkey"; - var initialData = new DataSetBuilder() - .Add(flagKey, 1, LdValue.Of(true), 0) - .Build(); + testData.Update(testData.Flag(flagKey).Variation(true)); using (var client = TestUtil.CreateClient(config, user)) { - updateSink.Init(user, initialData); - var eventSink1 = new EventSink(); var eventSink2 = new EventSink(); EventHandler listener1 = eventSink1.Add; @@ -45,12 +36,7 @@ public void ClientSendsFlagValueChangeEvents() eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); - var updatedFlag = new FeatureFlagBuilder() - .Version(2) - .Value(LdValue.Of(false)) - .Variation(1) - .Build(); - updateSink.Upsert(user, flagKey, updatedFlag.ToItemDescriptor()); + testData.Update(testData.Flag(flagKey).Variation(false)); var event1 = eventSink1.ExpectValue(); var event2 = eventSink2.ExpectValue(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index ba0a6424..387b6964 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -57,7 +57,7 @@ public async void InitPassesUserToUpdateProcessorFactory() MockPollingProcessor stub = new MockPollingProcessor("{}"); User testUser = User.WithKey("new-user"); - var config = TestUtil.ConfigWithFlagsJson(testUser, appKey, "{}") + var config = TestUtil.TestConfig() .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -74,9 +74,9 @@ public async void InitPassesUserToUpdateProcessorFactory() public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProcessorFactory() { MockPollingProcessor stub = new MockPollingProcessor("{}"); - User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); - - var config = TestUtil.ConfigWithFlagsJson(anonUserIn, appKey, "{}") + User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); + + var config = TestUtil.TestConfig() .DataSource(stub.AsFactory()) .Logging(testLogging) .Build(); @@ -141,7 +141,7 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func { TestUtil.ClearClient(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}").Logging(testLogging).Build(); + var config = TestUtil.TestConfig().Logging(testLogging).Build(); using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } Assert.Null(LdClient.Instance); // Dispose() is called automatically at end of "using" block @@ -275,7 +275,7 @@ public void ConnectionChangeShouldStopUpdateProcessor() { var mockUpdateProc = new MockPollingProcessor(null); var mockConnectivityStateManager = new MockConnectivityStateManager(true); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .DataSource(mockUpdateProc.AsFactory()) .ConnectivityStateManager(mockConnectivityStateManager) .Logging(testLogging) @@ -292,7 +292,7 @@ public void UserWithNullKeyWillHaveUniqueKeySet() { var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; - var config = TestUtil.ConfigWithFlagsJson(userWithNullKey, appKey, "{}") + var config = TestUtil.TestConfig() .DeviceInfo(new MockDeviceInfo(uniqueId)) .Logging(testLogging) .Build(); @@ -308,7 +308,7 @@ public void UserWithEmptyKeyWillHaveUniqueKeySet() { var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; - var config = TestUtil.ConfigWithFlagsJson(userWithEmptyKey, appKey, "{}") + var config = TestUtil.TestConfig() .DeviceInfo(new MockDeviceInfo(uniqueId)) .Logging(testLogging) .Build(); @@ -324,7 +324,7 @@ public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() { var userWithNullKey = User.WithKey(null); var uniqueId = "some-unique-key"; - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .DeviceInfo(new MockDeviceInfo(uniqueId)) .Logging(testLogging) .Build(); @@ -341,7 +341,7 @@ public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() { var userWithEmptyKey = User.WithKey(""); var uniqueId = "some-unique-key"; - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .DeviceInfo(new MockDeviceInfo(uniqueId)) .Logging(testLogging) .Build(); @@ -368,7 +368,7 @@ public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() .Custom("attr", "value") .Build(); var uniqueId = "some-unique-key"; - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .DeviceInfo(new MockDeviceInfo(uniqueId)) .Logging(testLogging) .Build(); @@ -396,7 +396,7 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() var storage = new MockPersistentDataStore(); var flags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); storage.Init(simpleUser, DataModelSerialization.SerializeAll(flags)); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .Persistence(new SinglePersistentDataStoreFactory(storage)) .Offline(true) .Logging(testLogging) @@ -412,7 +412,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() { var storage = new MockPersistentDataStore(); var initialFlags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .DataSource(MockPollingProcessor.Factory(TestUtil.MakeJsonData(initialFlags))) .Persistence(new SinglePersistentDataStoreFactory(storage)) .Logging(testLogging) @@ -431,7 +431,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() public void EventProcessorIsOnlineByDefault() { var eventProcessor = new MockEventProcessor(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .Events(new SingleEventProcessorFactory(eventProcessor)) .Logging(testLogging) .Build(); @@ -446,7 +446,7 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() { var connectivityStateManager = new MockConnectivityStateManager(true); var eventProcessor = new MockEventProcessor(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .ConnectivityStateManager(connectivityStateManager) .Events(new SingleEventProcessorFactory(eventProcessor)) .Offline(true) @@ -476,7 +476,7 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() { var connectivityStateManager = new MockConnectivityStateManager(false); var eventProcessor = new MockEventProcessor(); - var config = TestUtil.ConfigWithFlagsJson(simpleUser, appKey, "{}") + var config = TestUtil.TestConfig() .ConnectivityStateManager(connectivityStateManager) .Events(new SingleEventProcessorFactory(eventProcessor)) .Logging(testLogging) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs index 4522cb44..71f62c91 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs @@ -20,6 +20,17 @@ public FeatureFlagBuilder() { } + public FeatureFlagBuilder(FeatureFlag from) + { + _value = from.Value; + _version = from.Version; + _variation = from.Variation; + _trackEvents = from.TrackEvents; + _trackReason = from.TrackReason; + _debugEventsUntilDate = from.DebugEventsUntilDate; + _reason = from.Reason; + } + public FeatureFlag Build() { return new FeatureFlag(_value, _variation, _reason, _version, _flagVersion, _trackEvents, _trackReason, _debugEventsUntilDate); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 6e1c5a0b..a09736ca 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.JsonStream; +using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using static LaunchDarkly.Sdk.Client.DataModel; @@ -132,29 +133,16 @@ internal static string MakeJsonData(FullDataSet data) return w.GetString(); } - internal static string JsonFlagsWithSingleFlag(string flagKey, LdValue value, int? variation = null, EvaluationReason? reason = null) => - MakeJsonData(new DataSetBuilder() - .Add(flagKey, new FeatureFlagBuilder().Value(value).Variation(variation).Reason(reason).Build()) - .Build()); + internal static ConfigurationBuilder TestConfig() => TestConfig("mobile-key"); internal static ConfigurationBuilder TestConfig(string appKey) => Configuration.Builder(appKey) .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .Events(new SingleEventProcessorFactory(new MockEventProcessor())) - .DataSource(MockPollingProcessor.Factory(null)) + .Events(Components.NoEvents) + .DataSource(TestData.DataSource()) .Persistence(Components.NoPersistence) .DeviceInfo(new MockDeviceInfo()); - internal static ConfigurationBuilder ConfigWithFlagsJson(User user, string appKey, string flagsJson) - { - var mockStore = new MockPersistentDataStore(); - if (user != null && user.Key != null) - { - mockStore.Init(user, flagsJson); - } - return TestConfig(appKey).Persistence(new SinglePersistentDataStoreFactory(mockStore)); - } - public static string NormalizeJsonUser(LdValue json) { // It's undefined whether a user with no custom attributes will have "custom":{} or not From 7b9d93c284aab20c596d07688518f317ec668172 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 28 Sep 2021 10:58:16 -0700 Subject: [PATCH 362/499] (#5) use new concurrency helpers in dotnet-sdk-internal (#131) --- .../Integrations/PollingDataSourceBuilder.cs | 1 + .../Interfaces/LdClientContext.cs | 14 + .../Internal/DataSources/PollingDataSource.cs | 26 +- .../DataSources/StreamingDataSource.cs | 1 + .../Internal/FlagTrackerImpl.cs | 20 +- .../LaunchDarkly.ClientSdk.csproj | 2 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 7 +- .../Preferences.netstandard.cs | 2 +- .../Resources/Resource.designer.cs | 3976 ++++++++--------- .../DataSourceUpdateSinkImplTest.cs | 3 +- .../DataSources/PollingDataSourceTest.cs | 5 +- .../DataSources/StreamingDataSourceTest.cs | 12 - 12 files changed, 2029 insertions(+), 2040 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 8c3dbfa7..093f9291 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -141,6 +141,7 @@ bool inBackground requestor, _pollInterval, TimeSpan.Zero, + context.TaskExecutor, logger ); } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index 3cf8bfc2..7fea1911 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -1,5 +1,6 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Internal; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -34,12 +35,23 @@ public sealed class LdClientContext /// public HttpConfiguration Http { get; } + internal TaskExecutor TaskExecutor { get; } + /// /// Creates an instance. /// /// the SDK configuration public LdClientContext( Configuration configuration + ) : this(configuration, null) { } + + /// + /// Creates an instance. + /// + /// the SDK configuration + internal LdClientContext( + Configuration configuration, + object eventSender ) { this.Basic = new BasicConfiguration(configuration.MobileKey); @@ -52,6 +64,8 @@ Configuration configuration this.EvaluationReasons = configuration.EvaluationReasons; this.Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) .CreateHttpConfiguration(this.Basic); + + this.TaskExecutor = new TaskExecutor(eventSender, this.BaseLogger); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index 5b0c3493..bc4d8272 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -1,8 +1,10 @@ using System; +using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client.Internal.DataSources @@ -15,8 +17,9 @@ internal sealed class PollingDataSource : IDataSource private readonly TimeSpan _pollingInterval; private readonly TimeSpan _initialDelay; private readonly Logger _log; + private readonly TaskExecutor _taskExecutor; private readonly TaskCompletionSource _startTask; - private readonly TaskCompletionSource _stopTask; + private volatile CancellationTokenSource _canceller; private readonly AtomicBoolean _initialized = new AtomicBoolean(false); private volatile bool _disposed; @@ -26,6 +29,7 @@ internal PollingDataSource( IFeatureFlagRequestor featureFlagRequestor, TimeSpan pollingInterval, TimeSpan initialDelay, + TaskExecutor taskExecutor, Logger log) { this._featureFlagRequestor = featureFlagRequestor; @@ -33,9 +37,9 @@ internal PollingDataSource( this._user = user; this._pollingInterval = pollingInterval; this._initialDelay = initialDelay; + this._taskExecutor = taskExecutor; this._log = log; _startTask = new TaskCompletionSource(); - _stopTask = new TaskCompletionSource(); } public Task Start() @@ -52,25 +56,12 @@ public Task Start() _log.Info("Starting LaunchDarkly PollingProcessor with interval: {0}", _pollingInterval); } - Task.Run(() => UpdateTaskLoopAsync()); + _canceller = _taskExecutor.StartRepeatingTask(_initialDelay, _pollingInterval, UpdateTaskAsync); return _startTask.Task; } public bool Initialized => _initialized.Get(); - private async Task UpdateTaskLoopAsync() - { - if (_initialDelay > TimeSpan.Zero) - { - await Task.Delay(_initialDelay); - } - while (!_disposed) - { - await UpdateTaskAsync(); - await Task.Delay(_pollingInterval); - } - } - private async Task UpdateTaskAsync() { try @@ -115,8 +106,7 @@ private void Dispose(bool disposing) { if (disposing) { - // Log that the polling has stopped - _disposed = true; + _canceller?.Cancel(); _featureFlagRequestor.Dispose(); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index 9ba1f0bb..a8760faa 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -7,6 +7,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Http; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; diff --git a/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs index 4f17a721..fe507dd3 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs @@ -9,36 +9,24 @@ internal sealed class FlagTrackerImpl : IFlagTracker { public event EventHandler FlagValueChanged; + private readonly TaskExecutor _taskExecutor; private readonly Logger _log; internal FlagTrackerImpl( + TaskExecutor taskExecutor, Logger log ) { + _taskExecutor = taskExecutor; _log = log; } internal void FireEvent(FlagValueChangeEvent ev) { var copyOfHandlers = FlagValueChanged; - var sender = this; if (copyOfHandlers != null) { - foreach (var h in copyOfHandlers.GetInvocationList()) - { - // Note, this schedules the listeners separately, rather than scheduling a single task that runs them all. - PlatformSpecific.AsyncScheduler.ScheduleAction(() => - { - try - { - h.DynamicInvoke(sender, ev); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Unexpected exception from FlagValueChanged event handler", e); - } - }); - } + _taskExecutor.ScheduleEvent(ev, copyOfHandlers); } } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 8b013121..ea13a542 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index c73117fa..a9089c2a 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -13,6 +13,7 @@ using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; namespace LaunchDarkly.Sdk.Client { @@ -40,6 +41,7 @@ public sealed class LdClient : ILdClient readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor _eventProcessor; readonly IFlagTracker _flagTracker; + readonly TaskExecutor _taskExecutor; private readonly Logger _log; // Mutable client state (some state is also in the ConnectionManager) @@ -123,8 +125,9 @@ public sealed class LdClient : ILdClient _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - _context = new LdClientContext(configuration); + _context = new LdClientContext(configuration, this); _log = _context.BaseLogger; + _taskExecutor = _context.TaskExecutor; _log.Info("Starting LaunchDarkly Client {0}", Version); @@ -132,7 +135,7 @@ public sealed class LdClient : ILdClient _user = DecorateUser(user); - var flagTracker = new FlagTrackerImpl(_log); + var flagTracker = new FlagTrackerImpl(_taskExecutor, _log); _flagTracker = flagTracker; var persistentStore = configuration.PersistentDataStoreFactory is null ? diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs index 03829e8b..7f04d090 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index 4ce014f4..b11b4230 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -8,9 +8,9 @@ // //------------------------------------------------------------------------------ -[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.ClientSdk.Android.Tests.Resource", IsApplication=true)] +[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.Sdk.Client.Android.Tests.Resource", IsApplication=true)] -namespace LaunchDarkly.ClientSdk.Android.Tests +namespace LaunchDarkly.Sdk.Client.Android.Tests { @@ -25,1992 +25,1992 @@ static Resource() public static void UpdateIdValues() { - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_fade_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_fade_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_enter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_popup_enter; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_exit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_popup_exit; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_shrink_fade_out_from_bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_shrink_fade_out_from_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_slide_in_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_top = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_slide_in_top; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_slide_out_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_top = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.abc_slide_out_top; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_in = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.design_bottom_sheet_slide_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_out = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.design_bottom_sheet_slide_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_in = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.design_snackbar_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_out = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.design_snackbar_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.EnterFromLeft; - global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.EnterFromRight; - global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.ExitToLeft; - global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.ExitToRight; - global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_enter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.tooltip_enter; - global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_exit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animation.tooltip_exit; - global::Xamarin.Forms.Platform.Android.Resource.Animator.design_appbar_state_list_animator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Animator.design_appbar_state_list_animator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarDivider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarDivider; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarItemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarPopupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarPopupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSplitStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarSplitStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarTabBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarTabStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarTabTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarWidgetTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionBarWidgetTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionDropDownStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionDropDownStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionMenuTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionMenuTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeCloseButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeCloseDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCopyDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeCopyDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCutDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeCutDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeFindDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeFindDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePasteDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModePasteDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePopupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModePopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSelectAllDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeSelectAllDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeShareDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeShareDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSplitBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeSplitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeWebSearchDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionModeWebSearchDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionOverflowButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowMenuStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionOverflowMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionProviderClass = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionProviderClass; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionViewClass = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.actionViewClass; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.activityChooserViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.activityChooserViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogButtonGroupStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alertDialogButtonGroupStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogCenterButtons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alertDialogCenterButtons; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alertDialogStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alertDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.allowStacking = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.allowStacking; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alpha; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alphabeticModifiers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.alphabeticModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowHeadLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.arrowHeadLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowShaftLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.arrowShaftLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoCompleteTextViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoCompleteTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMaxTextSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoSizeMaxTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMinTextSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoSizeMinTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizePresetSizes = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoSizePresetSizes; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeStepGranularity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoSizeStepGranularity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeTextType = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.autoSizeTextType; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.background; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundSplit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundStacked = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.backgroundStacked; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.barLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.barLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_autoHide = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.behavior_autoHide; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_hideable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.behavior_hideable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_overlapTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.behavior_overlapTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_peekHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.behavior_peekHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_skipCollapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.behavior_skipCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.borderWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderlessButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.borderlessButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetDialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.bottomSheetDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.bottomSheetStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonBarButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNegativeButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonBarNegativeButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNeutralButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonBarNeutralButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarPositiveButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonBarPositiveButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonPanelSideLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonPanelSideLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyleSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.buttonTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardBackgroundColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardBackgroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardCornerRadius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardCornerRadius; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardElevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardElevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardMaxElevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardMaxElevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardPreventCornerOverlap = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardPreventCornerOverlap; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardUseCompatPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.cardUseCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkboxStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.checkboxStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkedTextViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.checkedTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.closeIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.closeItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.collapseContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.collapseIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.collapsedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.collapsedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.color; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorAccent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorAccent; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorBackgroundFloating = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorBackgroundFloating; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorButtonNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorButtonNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlActivated = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorControlActivated; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlHighlight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorControlHighlight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorControlNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorError = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorError; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimary = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorPrimary; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimaryDark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorPrimaryDark; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorSwitchThumbNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.colorSwitchThumbNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.commitIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.commitIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEndWithActions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStartWithNavigation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentScrim = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.contentScrim; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.controlBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.controlBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.counterEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterMaxLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.counterMaxLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterOverflowTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.counterOverflowTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.counterTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.customNavigationLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.customNavigationLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.defaultQueryHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.defaultQueryHint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogPreferredPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dialogPreferredPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.displayOptions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.displayOptions; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.divider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.divider; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerHorizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dividerHorizontal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dividerPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerVertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dividerVertical; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawableSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.drawableSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawerArrowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.drawerArrowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropDownListViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dropDownListViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropdownListPreferredItemHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.dropdownListPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.editTextBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.editTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.editTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.elevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.errorEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.errorTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandActivityOverflowButtonDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandActivityOverflowButtonDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expanded = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expanded; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMargin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.expandedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fabSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fabSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fastScrollEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalThumbDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fastScrollHorizontalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalTrackDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fastScrollHorizontalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalThumbDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fastScrollVerticalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalTrackDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fastScrollVerticalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.font = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.font; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontFamily = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderCerts; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderPackage; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontProviderQuery; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontWeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.foregroundInsidePadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.foregroundInsidePadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.gapBetweenBars = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.gapBetweenBars; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.goIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.goIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.headerLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.headerLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.height; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hideOnContentScroll = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.hideOnContentScroll; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintAnimationEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.hintAnimationEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.hintEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.hintTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeAsUpIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.homeLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.icon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.iconTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.iconTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconifiedByDefault = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.iconifiedByDefault; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.imageButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.imageButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.indeterminateProgressStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.indeterminateProgressStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.initialActivityCount = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.initialActivityCount; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.insetForeground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.insetForeground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.isLightTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.isLightTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemIconTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.itemPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.keylines = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.keylines; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layoutManager = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layoutManager; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchorGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_anchorGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_behavior = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_behavior; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_collapseMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseParallaxMultiplier = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_collapseParallaxMultiplier; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_dodgeInsetEdges = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_dodgeInsetEdges; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_insetEdge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_insetEdge; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_keyline = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_keyline; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollFlags = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_scrollFlags; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollInterpolator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.layout_scrollInterpolator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listChoiceBackgroundIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listChoiceBackgroundIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listDividerAlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listDividerAlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listMenuViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listMenuViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPopupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPreferredItemHeightLarge; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPreferredItemHeightSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPreferredItemPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.listPreferredItemPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.logo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.logo; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.logoDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.logoDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxActionInlineWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.maxActionInlineWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxButtonHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.maxButtonHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.measureWithLargestChild = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.measureWithLargestChild; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.menu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.multiChoiceItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.multiChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.navigationContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.navigationIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.navigationMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.numericModifiers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.numericModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.overlapAnchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.overlapAnchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingBottomNoButtons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.paddingBottomNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.paddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.paddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingTopNoTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.paddingTopNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.panelBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.panelMenuListTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.panelMenuListWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.passwordToggleContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.passwordToggleDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.passwordToggleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.passwordToggleTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.passwordToggleTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupMenuStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.popupMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.popupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.preserveIconSpacing = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.preserveIconSpacing; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.pressedTranslationZ = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.pressedTranslationZ; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.progressBarPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.progressBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.queryBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.queryHint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.radioButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.radioButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.ratingBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.ratingBarStyleIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.ratingBarStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.reverseLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.reverseLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.rippleColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.rippleColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimAnimationDuration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.scrimAnimationDuration; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimVisibleHeightTrigger = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.scrimVisibleHeightTrigger; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchHintIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.searchHintIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.searchIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.searchViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.seekBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.seekBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.selectableItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackgroundBorderless = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.selectableItemBackgroundBorderless; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showAsAction = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.showAsAction; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showDividers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.showDividers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.showText; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.singleChoiceItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.singleChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spanCount = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.spanCount; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinBars = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.spinBars; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerDropDownItemStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.spinnerDropDownItemStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.spinnerStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.splitTrack = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.splitTrack; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.srcCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.srcCompat; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.stackFromEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.stackFromEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_above_anchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.state_above_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.state_collapsed; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.state_collapsible; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.statusBarBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarScrim = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.statusBarScrim; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subMenuArrow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.subMenuArrow; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.submitBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.submitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.subtitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.subtitleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.suggestionRowLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.suggestionRowLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchMinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.switchMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.switchPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.switchStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.switchTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabContentStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabContentStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabIndicatorColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabIndicatorHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMaxWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabMaxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabPaddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabPaddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabSelectedTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabSelectedTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tabTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAllCaps = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceLargePopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceLargePopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceListItem; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSecondary = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceListItemSecondary; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceListItemSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearancePopupMenuHeader = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearancePopupMenuHeader; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultSubtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceSearchResultSubtitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceSearchResultTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSmallPopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textAppearanceSmallPopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorAlertDialogListItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textColorAlertDialogListItem; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorError = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textColorError; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorSearchUrl = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.textColorSearchUrl; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.theme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.theme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thickness = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.thickness; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTextPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.thumbTextPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.thumbTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.thumbTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tickMark; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tickMarkTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tickMarkTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.title; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargins = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleMargins; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarId = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.toolbarId; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarNavigationButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.toolbarNavigationButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.toolbarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipForegroundColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tooltipForegroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipFrameBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tooltipFrameBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.tooltipText; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.track = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.track; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.trackTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.trackTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.useCompatPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.useCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.voiceIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.voiceIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBarOverlay = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowActionBarOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionModeOverlay = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowActionModeOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowFixedHeightMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowFixedHeightMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowFixedWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowFixedWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowMinWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowMinWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowNoTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Attribute.windowNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_allow_stacked_button_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Boolean.abc_allow_stacked_button_bar; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_actionMenuItemAllCaps = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Boolean.abc_config_actionMenuItemAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_closeDialogWhenTouchOutside = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Boolean.abc_config_closeDialogWhenTouchOutside; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_borderless_text_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_btn_colored_borderless_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_text_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_btn_colored_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_color_highlight_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_color_highlight_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_hint_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_hint_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_input_method_navigation_guard = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_input_method_navigation_guard; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_primary_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_primary_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_search_url_text; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_normal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_search_url_text_normal; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_pressed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_search_url_text_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_selected = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_search_url_text_selected; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_secondary_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_secondary_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_btn_checkable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_btn_checkable; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_default = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_default; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_edittext = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_edittext; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_seek_thumb = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_seek_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_spinner; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_switch_track = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.abc_tint_switch_track; - global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.accent_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.accent_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.background_floating_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.background_floating_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.background_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.background_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_inverse_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_inverse_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.bright_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.button_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.button_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_dark_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.cardview_dark_background; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_light_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.cardview_light_background; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_end_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.cardview_shadow_end_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_start_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.cardview_shadow_start_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_bottom_navigation_shadow_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_bottom_navigation_shadow_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_error = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_error; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_end_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_shadow_end_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_mid_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_shadow_mid_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_start_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_shadow_start_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_inner_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_stroke_end_inner_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_outer_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_stroke_end_outer_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_inner_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_stroke_top_inner_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_outer_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_fab_stroke_top_outer_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_snackbar_background_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_snackbar_background_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_tint_password_toggle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.design_tint_password_toggle; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.dim_foreground_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.dim_foreground_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.dim_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.dim_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.error_color_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.error_color_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.highlighted_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.highlighted_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_800 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_blue_grey_800; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_900 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_blue_grey_900; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_950 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_blue_grey_950; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_200 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_deep_teal_200; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_500 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_deep_teal_500; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_100 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_100; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_300 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_300; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_50 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_50; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_600 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_600; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_800 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_800; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_850 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_850; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_900 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.material_grey_900; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_action_color_filter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.notification_action_color_filter; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.notification_icon_bg_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.notification_material_background_media_default_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_dark_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_dark_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_text_default_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_text_default_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_text_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.primary_text_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.ripple_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.ripple_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.secondary_text_default_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.secondary_text_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.secondary_text_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_normal_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.switch_thumb_normal_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.tooltip_background_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Color.tooltip_background_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_with_nav = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_with_nav; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_height_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_end_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_end_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_start_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_elevation_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_elevation_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_icon_vertical_padding_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_icon_vertical_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_end_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_end_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_start_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_progress_bar_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_progress_bar_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_max_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_stacked_max_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_tab_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_stacked_tab_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_top_margin_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_top_margin_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_height_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_button_min_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_button_min_width_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_overflow_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_action_button_min_width_overflow_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_alert_dialog_button_bar_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_alert_dialog_button_bar_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_button_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_vertical_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_button_inset_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_button_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_vertical_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_button_padding_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_cascading_menus_min_smallest_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_cascading_menus_min_smallest_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_config_prefDialogWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_config_prefDialogWidth; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_corner_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_control_corner_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_inset_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_control_inset_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_padding_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_control_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_major = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_minor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_major = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_minor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_top_no_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_list_padding_top_no_title; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_major = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_min_width_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_minor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_min_width_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_top_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_padding_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_title_divider_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dialog_title_divider_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_icon_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_icon_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_left = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_left; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_right = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_right; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_bottom_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_bottom_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_top_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_edit_text_inset_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_floating_window_z = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_floating_window_z; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_list_item_padding_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_list_item_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_panel_menu_list_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_panel_menu_list_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_progress_bar_height_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_progress_bar_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_search_view_preferred_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_search_view_preferred_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_background_height_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_seekbar_track_background_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_progress_height_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_seekbar_track_progress_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_select_dialog_padding_start_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_select_dialog_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_switch_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_switch_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_1_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_body_1_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_2_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_body_2_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_button_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_button_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_caption_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_caption_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_1_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_display_1_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_2_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_display_2_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_3_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_display_3_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_4_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_display_4_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_headline_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_headline_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_large_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_large_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_medium_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_medium_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_header_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_menu_header_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_menu_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_small_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_small_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subhead_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_subhead_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subtitle_material_toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_subtitle_material_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material_toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.abc_text_size_title_material_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_compat_inset_shadow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.cardview_compat_inset_shadow; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.cardview_default_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_radius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.cardview_default_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_appbar_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_appbar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_item_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_active_item_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_text_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_active_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_item_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_min_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_item_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_margin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_shadow_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_shadow_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_text_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_navigation_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_modal_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_sheet_modal_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_peek_height_min = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_bottom_sheet_peek_height_min; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_border_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_border_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_image_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_image_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_mini = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_size_mini; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_normal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_size_normal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_translation_z_pressed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_fab_translation_z_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_icon_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_padding_bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_padding_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_separator_vertical_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_navigation_separator_vertical_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_action_inline_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_action_inline_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_background_corner_radius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_background_corner_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_extra_spacing_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_extra_spacing_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_min_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical_2lines = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical_2lines; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_text_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_snackbar_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_max_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_tab_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_scrollable_min_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_tab_scrollable_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_tab_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size_2line = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.design_tab_text_size_2line; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.disabled_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.disabled_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_default_thickness = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.fastscroll_default_thickness; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_margin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.fastscroll_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_minimum_range = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.fastscroll_minimum_range; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_colored; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.highlight_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.hint_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.hint_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_velocity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_velocity; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_action_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_subtext_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_top_pad; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_corner_radius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_corner_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_horizontal_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_horizontal_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_margin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_extra_offset = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_precise_anchor_extra_offset; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_threshold = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_precise_anchor_threshold; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_vertical_padding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_vertical_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_non_touch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_y_offset_non_touch; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_touch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Dimension.tooltip_y_offset_touch; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ab_share_pack_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ab_share_pack_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_action_bar_item_background_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_action_bar_item_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_borderless_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_borderless_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_check_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_000 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_015 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_015; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_colored_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_colored_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_default_mtrl_shape = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_default_mtrl_shape; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_radio_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_000 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_015 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_015; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_internal_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_cab_background_internal_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_cab_background_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_cab_background_top_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_control_background_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_control_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_dialog_material_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_dialog_material_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_edit_text_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_edit_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_ab_back_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_ab_back_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_clear_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_clear_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_go_search_api_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_go_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_overflow_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_overflow_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_share_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_menu_share_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_search_api_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_16dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_16dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_36dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_36dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_48dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_black_48dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_16dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_16dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_36dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_36dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_48dp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_star_half_black_48dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_voice_search_api_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ic_voice_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_item_background_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_item_background_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_divider_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_divider_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_focused_holo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_focused_holo; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_longpressed_holo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_longpressed_holo; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_pressed_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_pressed_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_list_selector_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_popup_background_mtrl_mult = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_popup_background_mtrl_mult; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_indicator_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ratingbar_indicator_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ratingbar_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_small_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_ratingbar_small_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_primary_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_scrubber_primary_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_track_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_scrubber_track_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_thumb_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_seekbar_thumb_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_tick_mark_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_seekbar_tick_mark_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_track_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_seekbar_track_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_mtrl_am_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_spinner_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_textfield_background_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_spinner_textfield_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_thumb_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_switch_thumb_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_track_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_switch_track_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_tab_indicator_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_tab_indicator_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_cursor_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_cursor_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_activated_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_textfield_activated_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_default_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_textfield_default_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_default_mtrl_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_textfield_search_default_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_textfield_search_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_vector_test = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.abc_vector_test; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.avd_hide_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.avd_show_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_bottom_navigation_item_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_bottom_navigation_item_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_fab_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_fab_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_ic_visibility; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility_off = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_ic_visibility_off; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_password_eye = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_password_eye; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_snackbar_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.design_snackbar_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.navigation_empty_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.navigation_empty_icon; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_action_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_action_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg_low; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg_normal; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_icon_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_icon_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notification_tile_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.tooltip_frame_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Drawable.tooltip_frame_light; - global::Xamarin.Forms.Platform.Android.Resource.Id.ALT = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.ALT; - global::Xamarin.Forms.Platform.Android.Resource.Id.CTRL = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.CTRL; - global::Xamarin.Forms.Platform.Android.Resource.Id.FUNCTION = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.FUNCTION; - global::Xamarin.Forms.Platform.Android.Resource.Id.META = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.META; - global::Xamarin.Forms.Platform.Android.Resource.Id.SHIFT = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.SHIFT; - global::Xamarin.Forms.Platform.Android.Resource.Id.SYM = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.SYM; - global::Xamarin.Forms.Platform.Android.Resource.Id.action0 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action0; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_activity_content = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_activity_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_root = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_root; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_spinner; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_bar_title; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_context_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_context_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_divider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_divider; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_image = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_image; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_divider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_menu_divider; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_presenter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_menu_presenter; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_mode_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar_stub = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_mode_bar_stub; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_close_button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_mode_close_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.action_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.actions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.actions; - global::Xamarin.Forms.Platform.Android.Resource.Id.activity_chooser_view_content = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.activity_chooser_view_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.add = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.add; - global::Xamarin.Forms.Platform.Android.Resource.Id.alertTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.alertTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.all = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.all; - global::Xamarin.Forms.Platform.Android.Resource.Id.always = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.always; - global::Xamarin.Forms.Platform.Android.Resource.Id.async = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.async; - global::Xamarin.Forms.Platform.Android.Resource.Id.auto = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.auto; - global::Xamarin.Forms.Platform.Android.Resource.Id.beginning = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.beginning; - global::Xamarin.Forms.Platform.Android.Resource.Id.blocking = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.blocking; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.bottom; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_navarea = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.bottomtab_navarea; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_tabbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.bottomtab_tabbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.buttonPanel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.buttonPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.cancel_action = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.cancel_action; - global::Xamarin.Forms.Platform.Android.Resource.Id.center = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.center; - global::Xamarin.Forms.Platform.Android.Resource.Id.center_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.center_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.center_vertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.center_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.checkbox = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.checkbox; - global::Xamarin.Forms.Platform.Android.Resource.Id.chronometer = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.chronometer; - global::Xamarin.Forms.Platform.Android.Resource.Id.clip_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.clip_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.clip_vertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.clip_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.collapseActionView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.collapseActionView; - global::Xamarin.Forms.Platform.Android.Resource.Id.container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.container; - global::Xamarin.Forms.Platform.Android.Resource.Id.contentPanel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.contentPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.coordinator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.coordinator; - global::Xamarin.Forms.Platform.Android.Resource.Id.custom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.custom; - global::Xamarin.Forms.Platform.Android.Resource.Id.customPanel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.customPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.decor_content_parent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.decor_content_parent; - global::Xamarin.Forms.Platform.Android.Resource.Id.default_activity_button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.default_activity_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_bottom_sheet = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.design_bottom_sheet; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.design_menu_item_action_area; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area_stub = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.design_menu_item_action_area_stub; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.design_menu_item_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_navigation_view = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.design_navigation_view; - global::Xamarin.Forms.Platform.Android.Resource.Id.disableHome = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.disableHome; - global::Xamarin.Forms.Platform.Android.Resource.Id.edit_query = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.edit_query; - global::Xamarin.Forms.Platform.Android.Resource.Id.end = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.end; - global::Xamarin.Forms.Platform.Android.Resource.Id.end_padder = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.end_padder; - global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlways = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.enterAlways; - global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlwaysCollapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.enterAlwaysCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Id.exitUntilCollapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.exitUntilCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Id.expand_activities_button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.expand_activities_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.expanded_menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.expanded_menu; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.fill; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.fill_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill_vertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.fill_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.@fixed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.@fixed; - global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_appbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.flyoutcontent_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_recycler = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.flyoutcontent_recycler; - global::Xamarin.Forms.Platform.Android.Resource.Id.forever = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.forever; - global::Xamarin.Forms.Platform.Android.Resource.Id.ghost_view = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.ghost_view; - global::Xamarin.Forms.Platform.Android.Resource.Id.home = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.home; - global::Xamarin.Forms.Platform.Android.Resource.Id.homeAsUp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.homeAsUp; - global::Xamarin.Forms.Platform.Android.Resource.Id.icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.icon_group = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.icon_group; - global::Xamarin.Forms.Platform.Android.Resource.Id.ifRoom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.ifRoom; - global::Xamarin.Forms.Platform.Android.Resource.Id.image = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.image; - global::Xamarin.Forms.Platform.Android.Resource.Id.info = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.info; - global::Xamarin.Forms.Platform.Android.Resource.Id.italic = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.italic; - global::Xamarin.Forms.Platform.Android.Resource.Id.item_touch_helper_previous_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.item_touch_helper_previous_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Id.largeLabel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.largeLabel; - global::Xamarin.Forms.Platform.Android.Resource.Id.left = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.left; - global::Xamarin.Forms.Platform.Android.Resource.Id.line1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.line1; - global::Xamarin.Forms.Platform.Android.Resource.Id.line3 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.line3; - global::Xamarin.Forms.Platform.Android.Resource.Id.listMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.listMode; - global::Xamarin.Forms.Platform.Android.Resource.Id.list_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.list_item; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_appbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.main_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_scrollview = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.main_scrollview; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_tablayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.main_tablayout; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.main_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.masked = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.masked; - global::Xamarin.Forms.Platform.Android.Resource.Id.media_actions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.media_actions; - global::Xamarin.Forms.Platform.Android.Resource.Id.message = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.message; - global::Xamarin.Forms.Platform.Android.Resource.Id.middle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.middle; - global::Xamarin.Forms.Platform.Android.Resource.Id.mini = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.mini; - global::Xamarin.Forms.Platform.Android.Resource.Id.multiply = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.multiply; - global::Xamarin.Forms.Platform.Android.Resource.Id.navigation_header_container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.navigation_header_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.never = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.never; - global::Xamarin.Forms.Platform.Android.Resource.Id.none = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.none; - global::Xamarin.Forms.Platform.Android.Resource.Id.normal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.normal; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.notification_background; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.notification_main_column; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column_container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.notification_main_column_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.parallax = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.parallax; - global::Xamarin.Forms.Platform.Android.Resource.Id.parentPanel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.parentPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.parent_matrix = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.parent_matrix; - global::Xamarin.Forms.Platform.Android.Resource.Id.pin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.pin; - global::Xamarin.Forms.Platform.Android.Resource.Id.progress_circular = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.progress_circular; - global::Xamarin.Forms.Platform.Android.Resource.Id.progress_horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.progress_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.radio = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.radio; - global::Xamarin.Forms.Platform.Android.Resource.Id.right = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.right; - global::Xamarin.Forms.Platform.Android.Resource.Id.right_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.right_icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.right_side = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.right_side; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_image_matrix = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.save_image_matrix; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_non_transition_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.save_non_transition_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_scale_type = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.save_scale_type; - global::Xamarin.Forms.Platform.Android.Resource.Id.screen = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.screen; - global::Xamarin.Forms.Platform.Android.Resource.Id.scroll = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.scroll; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.scrollIndicatorDown; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorUp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.scrollIndicatorUp; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.scrollView; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.scrollable; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_badge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_badge; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_close_btn = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_close_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_edit_frame = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_edit_frame; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_go_btn = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_go_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_mag_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_mag_icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_plate = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_plate; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_src_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_src_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_voice_btn = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.search_voice_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.select_dialog_listview = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.select_dialog_listview; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_appbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.shellcontent_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_scrollview = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.shellcontent_scrollview; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.shellcontent_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.shortcut = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.shortcut; - global::Xamarin.Forms.Platform.Android.Resource.Id.showCustom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.showCustom; - global::Xamarin.Forms.Platform.Android.Resource.Id.showHome = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.showHome; - global::Xamarin.Forms.Platform.Android.Resource.Id.showTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.smallLabel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.smallLabel; - global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_action = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.snackbar_action; - global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.snackbar_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.snap = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.snap; - global::Xamarin.Forms.Platform.Android.Resource.Id.spacer = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.spacer; - global::Xamarin.Forms.Platform.Android.Resource.Id.split_action_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.split_action_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_atop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.src_atop; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_in = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.src_in; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_over = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.src_over; - global::Xamarin.Forms.Platform.Android.Resource.Id.start = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.start; - global::Xamarin.Forms.Platform.Android.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.status_bar_latest_event_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.submenuarrow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.submenuarrow; - global::Xamarin.Forms.Platform.Android.Resource.Id.submit_area = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.submit_area; - global::Xamarin.Forms.Platform.Android.Resource.Id.tabMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Id.tag_transition_group = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.tag_transition_group; - global::Xamarin.Forms.Platform.Android.Resource.Id.text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.text; - global::Xamarin.Forms.Platform.Android.Resource.Id.text2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.text2; - global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoButtons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.textSpacerNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.textSpacerNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.text_input_password_toggle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.text_input_password_toggle; - global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_counter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.textinput_counter; - global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_error = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.textinput_error; - global::Xamarin.Forms.Platform.Android.Resource.Id.time = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.time; - global::Xamarin.Forms.Platform.Android.Resource.Id.title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.title; - global::Xamarin.Forms.Platform.Android.Resource.Id.titleDividerNoCustom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.titleDividerNoCustom; - global::Xamarin.Forms.Platform.Android.Resource.Id.title_template = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.title_template; - global::Xamarin.Forms.Platform.Android.Resource.Id.top = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.top; - global::Xamarin.Forms.Platform.Android.Resource.Id.topPanel = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.topPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.touch_outside = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.touch_outside; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_current_scene = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.transition_current_scene; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_layout_save = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.transition_layout_save; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_position = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.transition_position; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_scene_layoutid_cache = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.transition_scene_layoutid_cache; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_transform = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.transition_transform; - global::Xamarin.Forms.Platform.Android.Resource.Id.uniform = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.uniform; - global::Xamarin.Forms.Platform.Android.Resource.Id.up = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.up; - global::Xamarin.Forms.Platform.Android.Resource.Id.useLogo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.useLogo; - global::Xamarin.Forms.Platform.Android.Resource.Id.view_offset_helper = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.view_offset_helper; - global::Xamarin.Forms.Platform.Android.Resource.Id.visible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.visible; - global::Xamarin.Forms.Platform.Android.Resource.Id.withText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.withText; - global::Xamarin.Forms.Platform.Android.Resource.Id.wrap_content = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Id.wrap_content; - global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityDefaultDur = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.abc_config_activityDefaultDur; - global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityShortDur = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.abc_config_activityShortDur; - global::Xamarin.Forms.Platform.Android.Resource.Integer.app_bar_elevation_anim_duration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.app_bar_elevation_anim_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.bottom_sheet_slide_duration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.bottom_sheet_slide_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.cancel_button_image_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Integer.config_tooltipAnimTime = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.config_tooltipAnimTime; - global::Xamarin.Forms.Platform.Android.Resource.Integer.design_snackbar_text_max_lines = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.design_snackbar_text_max_lines; - global::Xamarin.Forms.Platform.Android.Resource.Integer.hide_password_duration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.hide_password_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.show_password_duration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.show_password_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_title_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_bar_title_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_up_container = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_bar_up_container; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_item_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_menu_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_bar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_mode_bar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_close_item_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_action_mode_close_item_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_activity_chooser_view; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view_list_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_activity_chooser_view_list_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_button_bar_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_alert_dialog_button_bar_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_alert_dialog_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_title_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_alert_dialog_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_dialog_title_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_dialog_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_expanded_menu_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_expanded_menu_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_checkbox = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_list_menu_item_checkbox; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_list_menu_item_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_list_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_radio = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_list_menu_item_radio; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_header_item_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_popup_menu_header_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_item_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_popup_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_content_include = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_screen_content_include; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_screen_simple; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple_overlay_action_mode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_screen_simple_overlay_action_mode; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_screen_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_dropdown_item_icons_2line = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_search_dropdown_item_icons_2line; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_view = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_search_view; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_select_dialog_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.abc_select_dialog_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.BottomTabLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.BottomTabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_navigation_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_bottom_navigation_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_sheet_dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_bottom_sheet_dialog; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_layout_snackbar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar_include = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_layout_snackbar_include; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_layout_tab_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_layout_tab_text; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_menu_item_action_area = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_menu_item_action_area; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_header = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_item_header; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_separator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_item_separator; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_subheader = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_item_subheader; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_menu; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_navigation_menu_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_text_input_password_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.design_text_input_password_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.FlyoutContent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.FlyoutContent; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_action_tombstone; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_action = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_media_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_media_cancel_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_big_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_big_media_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_big_media_narrow; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_custom_big; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_icon_group; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_lines_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_media_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_time = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.notification_template_part_time; - global::Xamarin.Forms.Platform.Android.Resource.Layout.RootLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.RootLayout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_item_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.select_dialog_item_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_multichoice_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.select_dialog_multichoice_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_singlechoice_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.select_dialog_singlechoice_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.ShellContent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.ShellContent; - global::Xamarin.Forms.Platform.Android.Resource.Layout.support_simple_spinner_dropdown_item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.support_simple_spinner_dropdown_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.tooltip = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Layout.tooltip; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_home_description = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_action_bar_home_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_up_description = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_action_bar_up_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_menu_overflow_description = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_action_menu_overflow_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_mode_done = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_action_mode_done; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_activity_chooser_view_see_all = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_activity_chooser_view_see_all; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_activitychooserview_choose_application = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_activitychooserview_choose_application; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_off = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_capital_off; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_on = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_capital_on; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_1_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_body_1_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_2_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_body_2_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_button_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_button_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_caption_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_caption_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_1_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_display_1_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_2_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_display_2_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_3_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_display_3_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_4_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_display_4_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_headline_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_headline_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_menu_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_menu_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_subhead_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_subhead_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_title_material = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_font_family_title_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_search_hint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_search_hint; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_clear = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_searchview_description_clear; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_query = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_searchview_description_query; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_search = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_searchview_description_search; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_submit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_searchview_description_submit; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_voice = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_searchview_description_voice; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_shareactionprovider_share_with; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with_application = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_shareactionprovider_share_with_application; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_toolbar_collapse_description = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.abc_toolbar_collapse_description; - global::Xamarin.Forms.Platform.Android.Resource.String.appbar_scrolling_view_behavior = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.appbar_scrolling_view_behavior; - global::Xamarin.Forms.Platform.Android.Resource.String.bottom_sheet_behavior = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.bottom_sheet_behavior; - global::Xamarin.Forms.Platform.Android.Resource.String.character_counter_pattern = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.character_counter_pattern; - global::Xamarin.Forms.Platform.Android.Resource.String.password_toggle_content_description = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.password_toggle_content_description; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.path_password_eye; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_strike_through = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.path_password_eye_mask_strike_through; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_visible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.path_password_eye_mask_visible; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_strike_through = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.path_password_strike_through; - global::Xamarin.Forms.Platform.Android.Resource.String.search_menu_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.search_menu_title; - global::Xamarin.Forms.Platform.Android.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.AlertDialog_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.AlertDialog_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Animation_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_DropDownUp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Animation_AppCompat_DropDownUp; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Tooltip = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Animation_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_Design_BottomSheetDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Animation_Design_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_DropDownUp = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_DropDownUp; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Tooltip = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Animation_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_CardView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_CardView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitle_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_DialogWindowTitle_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitleBackground_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_DialogWindowTitleBackground_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body1; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body2; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Caption = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Caption; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display1; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display2; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display3 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display3; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display4 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display4; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Headline = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Headline; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_CompactMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_CompactMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_EditText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V14_Widget_Design_AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V14_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Widget_Design_AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V21_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_AppCompat_Toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V26_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_Design_AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V26_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_EditText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_Toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_EditText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ImageButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ImageButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListMenuView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_TabLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Base_Widget_Design_TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.CardView; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.CardView_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.CardView_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V11_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V11_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V14_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V14_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V21_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V21_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V25_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_V25_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_Widget_AppCompat_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Platform_Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body1; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Caption = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Caption; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display1 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display1; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display3 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display3; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display4 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display4; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Headline = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Headline; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Counter; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Counter_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Error = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Error; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Hint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Hint; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Snackbar_Message = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Snackbar_Message; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Tab = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Design_Tab; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_CompactMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_CompactMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_NoActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_NoActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_Light_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_NoActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_AppCompat_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_BottomSheetDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_BottomSheetDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design_Light_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_NoActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design_Light_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_NoActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Theme_Design_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Light = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Colored = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Button_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_EditText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ImageButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ImageButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActivityChooserView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListPopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListView_DropDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_SearchView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListMenuView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_Menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ListView_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Indicator; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_SearchView_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar_Discrete; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_Underlined; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomNavigationView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_BottomNavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomSheet_Modal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_BottomSheet_Modal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CollapsingToolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_CollapsingToolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CoordinatorLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_CoordinatorLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_FloatingActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_FloatingActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_NavigationView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_NavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_ScrimInsetsFrameLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_ScrimInsetsFrameLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_Snackbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_Snackbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TabLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TextInputLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Style.Widget_Design_TextInputLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundSplit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundStacked = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_backgroundStacked; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEndWithActions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStartWithNavigation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_customNavigationLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_customNavigationLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_displayOptions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_displayOptions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_divider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_divider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_hideOnContentScroll = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_hideOnContentScroll; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeAsUpIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_homeLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_indeterminateProgressStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_indeterminateProgressStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_itemPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_itemPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_logo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_logo; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_navigationMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_navigationMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_popupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_progressBarPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_progressBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_titleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBar_titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout_android_layout_gravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionBarLayout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMenuItemView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView_android_minWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMenuItemView_android_minWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_backgroundSplit = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_closeItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_closeItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_subtitleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_titleTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActionMode_titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_initialActivityCount = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ActivityChooserView_initialActivityCount; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_android_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_android_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_buttonPanelSideLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_buttonPanelSideLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_listItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_listLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_multiChoiceItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_multiChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_showTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_singleChoiceItemLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AlertDialog_singleChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_expanded = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_expanded; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatImageView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_android_src = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatImageView_android_src; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_srcCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatImageView_srcCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatImageView_tint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatImageView_tintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_android_thumb = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_android_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMark; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_textAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_textAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_android_textAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_android_textAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizePresetSizes = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizePresetSizes; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeTextType = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeTextType; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_fontFamily = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_textAllCaps = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTextView_textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarDivider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarItemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarPopupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarPopupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSplitStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSplitStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionDropDownStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionDropDownStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCutDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCutDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeFindDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeFindDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePasteDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePasteDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeShareDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeShareDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSplitBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSplitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_activityChooserViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_activityChooserViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowIsFloating = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowIsFloating; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_borderlessButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_borderlessButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyleSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkboxStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_checkboxStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkedTextViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_checkedTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorAccent = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorAccent; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorBackgroundFloating = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorBackgroundFloating; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorButtonNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorButtonNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlActivated = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlActivated; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlHighlight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlHighlight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorError = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorError; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimary = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimary; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimaryDark = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimaryDark; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_controlBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_controlBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogPreferredPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dialogPreferredPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerHorizontal = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dividerHorizontal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerVertical = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dividerVertical; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropDownListViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dropDownListViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_editTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_homeAsUpIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_imageButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_imageButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listDividerAlertDialog = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listDividerAlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listMenuViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listMenuViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPopupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupMenuStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_popupMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupWindowStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_popupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_radioButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_radioButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_searchViewStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_searchViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_seekBarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_seekBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_switchStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_switchStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorSearchUrl = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_textColorSearchUrl; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipForegroundColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipForegroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipFrameBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipFrameBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBarOverlay = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBarOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionModeOverlay = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionModeOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMajor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMinor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowNoTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.AppCompatTheme_windowNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemIconTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView_itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomNavigationView_menu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ButtonBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout_allowStacking = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ButtonBarLayout_allowStacking; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_android_minHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_android_minWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardBackgroundColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardBackgroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardCornerRadius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardCornerRadius; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardElevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardElevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardMaxElevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardMaxElevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardPreventCornerOverlap = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardPreventCornerOverlap; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardUseCompatPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_cardUseCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_contentPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CardView_contentPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_contentScrim = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_contentScrim; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_titleEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_titleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_toolbarId = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_toolbarId; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ColorStateListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ColorStateListItem_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_alpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ColorStateListItem_android_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ColorStateListItem_android_color; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CompoundButton; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_android_button = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CompoundButton_android_button; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CompoundButton_buttonTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CompoundButton_buttonTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_keylines = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_keylines; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_statusBarBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_statusBarBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DesignTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetDialogTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_textColorError = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DesignTheme_textColorError; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowHeadLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowHeadLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowShaftLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowShaftLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_barLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_barLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_color = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_color; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_drawableSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_drawableSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_gapBetweenBars = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_gapBetweenBars; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_spinBars = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_spinBars; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_thickness = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.DrawerArrowToggle_thickness; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_borderWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_borderWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_fabSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_fabSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_pressedTranslationZ = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_pressedTranslationZ; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_rippleColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_rippleColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_useCompatPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_useCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foreground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foreground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAligned = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAligned; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_gravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_orientation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_orientation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_weightSum = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_weightSum; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_divider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_divider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_dividerPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_dividerPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_showDividers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_showDividers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_checkableBehavior = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_checkableBehavior; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_enabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_enabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_id = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_menuCategory = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_menuCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_orderInCategory = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_orderInCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_visible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuGroup_android_visible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_actionLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionProviderClass = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_actionProviderClass; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionViewClass = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_actionViewClass; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_alphabeticModifiers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_alphabeticModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_alphabeticShortcut = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_alphabeticShortcut; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checkable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_checkable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checked = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_checked; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_enabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_enabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_id = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_menuCategory = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_menuCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_numericShortcut = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_numericShortcut; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_onClick = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_onClick; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_orderInCategory = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_orderInCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_titleCondensed = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_titleCondensed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_visible = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_android_visible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_contentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_contentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_iconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_iconTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_numericModifiers = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_numericModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_showAsAction = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_showAsAction; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_tooltipText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuItem_tooltipText; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_headerBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_headerBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_horizontalDivider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_horizontalDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemIconDisabledAlpha = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_itemIconDisabledAlpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_verticalDivider = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_verticalDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_windowAnimationStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_android_windowAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_preserveIconSpacing = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_preserveIconSpacing; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_subMenuArrow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.MenuView_subMenuArrow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_fitsSystemWindows = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_android_fitsSystemWindows; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_maxWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_headerLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_headerLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemIconTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_menu = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.NavigationView_menu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupAnimationStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindow_android_popupAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindow_android_popupBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_overlapAnchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindow_overlapAnchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindowBackgroundState; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecycleListView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingBottomNoButtons = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecycleListView_paddingBottomNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingTopNoTitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecycleListView_paddingTopNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_descendantFocusability = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_android_descendantFocusability; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_orientation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_android_orientation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_layoutManager = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_layoutManager; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_reverseLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_reverseLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_spanCount = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_spanCount; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_stackFromEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.RecyclerView_stackFromEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_focusable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_android_focusable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_imeOptions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_android_imeOptions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_inputType = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_android_inputType; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_maxWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_closeIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_closeIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_commitIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_commitIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_defaultQueryHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_defaultQueryHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_goIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_goIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_iconifiedByDefault = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_iconifiedByDefault; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_queryBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_queryHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchHintIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_searchHintIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_searchIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_submitBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_submitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_suggestionRowLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_suggestionRowLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_voiceIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SearchView_voiceIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SnackbarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_android_maxWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SnackbarLayout_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_elevation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SnackbarLayout_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_maxActionInlineWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SnackbarLayout_maxActionInlineWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_dropDownWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner_android_dropDownWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_entries = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner_android_entries; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_popupBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner_android_popupBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_prompt = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner_android_prompt; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_popupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Spinner_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOff = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_textOff; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOn = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_textOn; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_thumb = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_android_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_showText = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_showText; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_splitTrack = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_splitTrack; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchMinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_switchTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTextPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTextPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_thumbTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_track = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_track; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_trackTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.SwitchCompat_trackTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_icon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabItem_android_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabItem_android_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_text = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabItem_android_text; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabBackground = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabContentStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabContentStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMaxWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabMaxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMinWidth = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPadding = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabSelectedTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabSelectedTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TabLayout_tabTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_fontFamily = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDx = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDx; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDy = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDy; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowRadius = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_shadowRadius; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColorHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorLink = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textColorLink; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textSize = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textStyle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_textStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_typeface = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_android_typeface; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_fontFamily = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_textAllCaps = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextAppearance_textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_hint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_android_hint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_textColorHint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_android_textColorHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterMaxLength = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterMaxLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_counterTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_errorEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_errorTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintAnimationEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintAnimationEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_hintTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleDrawable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleEnabled = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_gravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_android_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_minHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_android_minHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_buttonGravity = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_buttonGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_collapseContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_collapseIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEndWithActions = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetLeft = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetRight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStartWithNavigation = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logo = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_logo; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logoDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_logoDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_maxButtonHeight = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_maxButtonHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationContentDescription = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_navigationContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationIcon = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_navigationIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_popupTheme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitle = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_subtitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_subtitleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_title = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargin = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginBottom = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginTop = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargins = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleMargins; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextAppearance = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextColor = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.Toolbar_titleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_focusable = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View_android_focusable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_theme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View_android_theme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingEnd = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View_paddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingStart = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View_paddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_theme = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.View_theme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_android_background = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTint = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewStubCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_id = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.ClientSdk.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_enter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_popup_enter; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_exit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_popup_exit; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_shrink_fade_out_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_shrink_fade_out_from_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_in_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_in_top; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_out_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_out_top; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_bottom_sheet_slide_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_bottom_sheet_slide_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_snackbar_in; + global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_snackbar_out; + global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.EnterFromLeft; + global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.EnterFromRight; + global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.ExitToLeft; + global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.ExitToRight; + global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_enter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.tooltip_enter; + global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_exit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.tooltip_exit; + global::Xamarin.Forms.Platform.Android.Resource.Animator.design_appbar_state_list_animator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animator.design_appbar_state_list_animator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarDivider; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarPopupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarPopupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSplitStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarSplitStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarWidgetTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarWidgetTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionDropDownStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionDropDownStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionMenuTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionMenuTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCloseButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCloseDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCopyDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCopyDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCutDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCutDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeFindDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeFindDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePasteDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModePasteDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModePopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSelectAllDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeSelectAllDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeShareDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeShareDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSplitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeSplitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeWebSearchDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeWebSearchDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionOverflowButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionOverflowMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionProviderClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionProviderClass; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionViewClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionViewClass; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.activityChooserViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.activityChooserViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogButtonGroupStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogButtonGroupStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogCenterButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogCenterButtons; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.allowStacking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.allowStacking; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alpha; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.alphabeticModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alphabeticModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowHeadLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.arrowHeadLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowShaftLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.arrowShaftLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoCompleteTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoCompleteTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMaxTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeMaxTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMinTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeMinTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizePresetSizes = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizePresetSizes; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeStepGranularity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeStepGranularity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeTextType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeTextType; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.background; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundStacked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundStacked; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.barLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.barLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_autoHide = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_autoHide; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_hideable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_hideable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_overlapTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_overlapTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_peekHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_peekHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_skipCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_skipCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.borderWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderlessButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.borderlessButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.bottomSheetDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.bottomSheetStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNegativeButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarNegativeButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNeutralButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarNeutralButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarPositiveButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarPositiveButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonPanelSideLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonPanelSideLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardBackgroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardBackgroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardCornerRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardCornerRadius; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardElevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardMaxElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardMaxElevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardPreventCornerOverlap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardPreventCornerOverlap; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardUseCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardUseCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkboxStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.checkboxStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkedTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.checkedTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.closeIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.closeItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapseContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapseIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapsedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapsedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.color; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorAccent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorAccent; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorBackgroundFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorBackgroundFloating; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorButtonNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorButtonNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlActivated = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlActivated; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlHighlight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlHighlight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorError; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorPrimary; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimaryDark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorPrimaryDark; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorSwitchThumbNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorSwitchThumbNormal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.commitIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.commitIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentScrim; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.controlBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.controlBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterMaxLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterMaxLength; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterOverflowTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterOverflowTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.customNavigationLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.customNavigationLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.defaultQueryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.defaultQueryHint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogPreferredPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dialogPreferredPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.displayOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.displayOptions; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.divider; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerHorizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerHorizontal; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerVertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerVertical; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawableSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.drawableSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawerArrowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.drawerArrowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropDownListViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dropDownListViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropdownListPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dropdownListPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.elevation; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.errorEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.errorTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandActivityOverflowButtonDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandActivityOverflowButtonDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expanded; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fabSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fabSize; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollHorizontalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollHorizontalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollVerticalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollVerticalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.foregroundInsidePadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.foregroundInsidePadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.gapBetweenBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.gapBetweenBars; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.goIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.goIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.headerLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.headerLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.height; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hideOnContentScroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hideOnContentScroll; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintAnimationEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintAnimationEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.homeLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.icon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconifiedByDefault = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconifiedByDefault; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.imageButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.imageButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.indeterminateProgressStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.indeterminateProgressStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.initialActivityCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.initialActivityCount; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.insetForeground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.insetForeground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.isLightTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.isLightTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.keylines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.keylines; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layoutManager = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layoutManager; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchorGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_anchorGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_behavior; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_collapseMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseParallaxMultiplier = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_collapseParallaxMultiplier; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_dodgeInsetEdges = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_dodgeInsetEdges; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_insetEdge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_insetEdge; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_keyline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_keyline; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollFlags = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_scrollFlags; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollInterpolator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_scrollInterpolator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listChoiceBackgroundIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listChoiceBackgroundIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listDividerAlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listDividerAlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listMenuViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listMenuViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeightLarge; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeightSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.logo; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.logoDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.logoDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxActionInlineWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.maxActionInlineWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxButtonHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.maxButtonHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.measureWithLargestChild = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.measureWithLargestChild; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.menu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.multiChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.multiChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.numericModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.numericModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.overlapAnchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.overlapAnchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingBottomNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingBottomNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingTopNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingTopNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelMenuListTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelMenuListWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.preserveIconSpacing = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.preserveIconSpacing; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.pressedTranslationZ = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.pressedTranslationZ; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.progressBarPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.progressBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.queryBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.queryHint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.radioButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.radioButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyleIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.reverseLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.reverseLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.rippleColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.rippleColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimAnimationDuration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.scrimAnimationDuration; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimVisibleHeightTrigger = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.scrimVisibleHeightTrigger; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchHintIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchHintIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.seekBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.seekBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.selectableItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackgroundBorderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.selectableItemBackgroundBorderless; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showAsAction = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showAsAction; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showDividers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showDividers; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showText; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.singleChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.singleChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spanCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spanCount; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinBars; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerDropDownItemStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinnerDropDownItemStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinnerStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.splitTrack = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.splitTrack; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.srcCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.srcCompat; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.stackFromEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.stackFromEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_above_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_above_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_collapsed; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_collapsible; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.statusBarBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.statusBarScrim; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subMenuArrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subMenuArrow; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.submitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.submitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.suggestionRowLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.suggestionRowLayout; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabContentStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabContentStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabGravity; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabIndicatorColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabIndicatorHeight; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMaxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMaxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabSelectedTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabSelectedTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceLargePopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceLargePopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItem; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSecondary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItemSecondary; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItemSmall; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearancePopupMenuHeader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearancePopupMenuHeader; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultSubtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSearchResultSubtitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSearchResultTitle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSmallPopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSmallPopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorAlertDialogListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorAlertDialogListItem; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorError; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorSearchUrl = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorSearchUrl; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.theme; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thickness; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTextPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTextPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMark; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMarkTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMarkTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.title; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargins = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMargins; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarId; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarNavigationButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarNavigationButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipForegroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipForegroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipFrameBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipFrameBackground; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipText; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.track; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.trackTint; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.trackTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.useCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.useCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.voiceIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.voiceIcon; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBarOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionBarOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionModeOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionModeOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedHeightMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedHeightMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowMinWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowMinWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_allow_stacked_button_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_allow_stacked_button_bar; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_actionMenuItemAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_actionMenuItemAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_closeDialogWhenTouchOutside = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_closeDialogWhenTouchOutside; + global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_borderless_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_btn_colored_borderless_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_btn_colored_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_color_highlight_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_color_highlight_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_hint_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_hint_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_input_method_navigation_guard = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_input_method_navigation_guard; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_normal; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_selected = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_selected; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_secondary_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_secondary_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_btn_checkable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_btn_checkable; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_default = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_default; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_edittext = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_edittext; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_seek_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_seek_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_spinner; + global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_switch_track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_switch_track; + global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.accent_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.accent_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_floating_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_floating_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_inverse_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_inverse_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.button_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.button_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_dark_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_dark_background; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_light_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_light_background; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_end_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_shadow_end_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_start_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_shadow_start_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_bottom_navigation_shadow_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_bottom_navigation_shadow_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_error; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_end_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_end_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_mid_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_mid_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_start_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_start_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_inner_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_end_inner_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_outer_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_end_outer_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_inner_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_top_inner_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_outer_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_top_outer_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_snackbar_background_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_snackbar_background_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.design_tint_password_toggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_tint_password_toggle; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.error_color_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.error_color_material; + global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.foreground_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.foreground_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.highlighted_text_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.highlighted_text_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_800 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_800; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_900 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_900; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_950 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_950; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_200 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_deep_teal_200; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_500 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_deep_teal_500; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_100 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_100; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_300 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_300; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_50 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_50; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_600 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_600; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_800 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_800; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_850 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_850; + global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_900 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_900; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_dark_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_dark_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_disabled_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_disabled_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_normal_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_normal_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.tooltip_background_dark; + global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.tooltip_background_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_with_nav = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_with_nav; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_end_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_end_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_elevation_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_elevation_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_icon_vertical_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_icon_vertical_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_end_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_end_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_progress_bar_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_progress_bar_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_max_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_stacked_max_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_tab_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_stacked_tab_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_top_margin_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_top_margin_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_width_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_overflow_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_width_overflow_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_alert_dialog_button_bar_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_alert_dialog_button_bar_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_inset_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_padding_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_cascading_menus_min_smallest_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_cascading_menus_min_smallest_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_config_prefDialogWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_config_prefDialogWidth; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_corner_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_inset_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_inset_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_top_no_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_list_padding_top_no_title; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_min_width_major; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_min_width_minor; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_padding_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_padding_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_title_divider_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_title_divider_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_icon_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_left = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_left; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_right = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_right; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_bottom_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_bottom_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_floating_window_z = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_floating_window_z; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_list_item_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_list_item_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_panel_menu_list_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_panel_menu_list_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_progress_bar_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_progress_bar_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_search_view_preferred_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_search_view_preferred_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_background_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_seekbar_track_background_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_progress_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_seekbar_track_progress_height_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_select_dialog_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_select_dialog_padding_start_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_switch_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_switch_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_body_1_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_body_2_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_button_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_button_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_caption_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_caption_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_1_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_2_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_3_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_3_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_4_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_4_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_headline_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_headline_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_large_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_large_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_medium_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_medium_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_header_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_menu_header_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_menu_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_small_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_small_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subhead_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_subhead_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subtitle_material_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_subtitle_material_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_title_material_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_compat_inset_shadow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_compat_inset_shadow; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_default_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_default_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_appbar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_appbar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_item_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_active_item_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_active_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_item_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_item_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_shadow_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_shadow_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_modal_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_sheet_modal_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_peek_height_min = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_sheet_peek_height_min; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_border_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_border_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_image_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_image_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_mini = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_size_mini; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_size_normal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_translation_z_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_translation_z_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_icon_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_padding_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_padding_bottom; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_separator_vertical_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_separator_vertical_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_action_inline_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_action_inline_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_background_corner_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_background_corner_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_extra_spacing_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_extra_spacing_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical_2lines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical_2lines; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_max_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_scrollable_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_scrollable_min_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size_2line = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_text_size_2line; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.disabled_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.disabled_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_default_thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_default_thickness; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_minimum_range = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_minimum_range; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_colored; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_dark; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_light; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_velocity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_velocity; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_corner_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_corner_radius; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_horizontal_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_horizontal_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_margin; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_extra_offset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_precise_anchor_extra_offset; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_threshold = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_precise_anchor_threshold; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_vertical_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_vertical_padding; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_non_touch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_y_offset_non_touch; + global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_touch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_y_offset_touch; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ab_share_pack_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ab_share_pack_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_action_bar_item_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_action_bar_item_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_borderless_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_borderless_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_015 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_015; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_colored_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_colored_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_default_mtrl_shape = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_default_mtrl_shape; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_015 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_015; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_internal_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_internal_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_top_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_top_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_control_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_control_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_dialog_material_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_dialog_material_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_edit_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_edit_text_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_ab_back_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_ab_back_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_clear_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_clear_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_go_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_go_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_overflow_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_overflow_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_share_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_share_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_16dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_16dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_36dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_36dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_48dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_48dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_16dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_16dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_36dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_36dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_48dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_48dp; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_voice_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_voice_search_api_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_item_background_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_item_background_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_divider_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_divider_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_focused_holo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_focused_holo; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_longpressed_holo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_longpressed_holo; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_pressed_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_pressed_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_holo_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_holo_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_popup_background_mtrl_mult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_popup_background_mtrl_mult; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_indicator_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_indicator_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_small_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_small_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_primary_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_primary_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_track_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_track_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_thumb_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_thumb_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_tick_mark_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_tick_mark_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_track_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_track_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_spinner_mtrl_am_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_textfield_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_spinner_textfield_background_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_thumb_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_switch_thumb_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_track_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_switch_track_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_tab_indicator_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_tab_indicator_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_cursor_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_cursor_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_light; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_activated_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_activated_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_default_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_default_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_default_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_default_mtrl_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_material; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_vector_test = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_vector_test; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.avd_hide_password; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.avd_show_password; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_bottom_navigation_item_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_bottom_navigation_item_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_fab_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_fab_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_ic_visibility; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility_off = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_ic_visibility_off; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_password_eye = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_password_eye; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_snackbar_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_snackbar_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.navigation_empty_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.navigation_empty_icon; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.tooltip_frame_dark; + global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.tooltip_frame_light; + global::Xamarin.Forms.Platform.Android.Resource.Id.ALT = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ALT; + global::Xamarin.Forms.Platform.Android.Resource.Id.CTRL = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.CTRL; + global::Xamarin.Forms.Platform.Android.Resource.Id.FUNCTION = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.FUNCTION; + global::Xamarin.Forms.Platform.Android.Resource.Id.META = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.META; + global::Xamarin.Forms.Platform.Android.Resource.Id.SHIFT = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.SHIFT; + global::Xamarin.Forms.Platform.Android.Resource.Id.SYM = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.SYM; + global::Xamarin.Forms.Platform.Android.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_activity_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_activity_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_root = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_root; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_spinner; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_title; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_context_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_context_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_menu_divider; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_presenter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_menu_presenter; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar_stub = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_bar_stub; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_close_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_close_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; + global::Xamarin.Forms.Platform.Android.Resource.Id.activity_chooser_view_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.activity_chooser_view_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.add = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.add; + global::Xamarin.Forms.Platform.Android.Resource.Id.alertTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.alertTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.all = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.all; + global::Xamarin.Forms.Platform.Android.Resource.Id.always = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.always; + global::Xamarin.Forms.Platform.Android.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; + global::Xamarin.Forms.Platform.Android.Resource.Id.auto = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.auto; + global::Xamarin.Forms.Platform.Android.Resource.Id.beginning = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.beginning; + global::Xamarin.Forms.Platform.Android.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottom; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_navarea = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottomtab_navarea; + global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_tabbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottomtab_tabbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.buttonPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.buttonPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; + global::Xamarin.Forms.Platform.Android.Resource.Id.center = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center; + global::Xamarin.Forms.Platform.Android.Resource.Id.center_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.center_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.checkbox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.checkbox; + global::Xamarin.Forms.Platform.Android.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; + global::Xamarin.Forms.Platform.Android.Resource.Id.clip_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.clip_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.clip_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.clip_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.collapseActionView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.collapseActionView; + global::Xamarin.Forms.Platform.Android.Resource.Id.container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.container; + global::Xamarin.Forms.Platform.Android.Resource.Id.contentPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.contentPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.coordinator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.coordinator; + global::Xamarin.Forms.Platform.Android.Resource.Id.custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.custom; + global::Xamarin.Forms.Platform.Android.Resource.Id.customPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.customPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.decor_content_parent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.decor_content_parent; + global::Xamarin.Forms.Platform.Android.Resource.Id.default_activity_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.default_activity_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_bottom_sheet = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_bottom_sheet; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_action_area; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area_stub = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_action_area_stub; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.design_navigation_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_navigation_view; + global::Xamarin.Forms.Platform.Android.Resource.Id.disableHome = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.disableHome; + global::Xamarin.Forms.Platform.Android.Resource.Id.edit_query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.edit_query; + global::Xamarin.Forms.Platform.Android.Resource.Id.end = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end; + global::Xamarin.Forms.Platform.Android.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; + global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlways = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.enterAlways; + global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlwaysCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.enterAlwaysCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Id.exitUntilCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.exitUntilCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Id.expand_activities_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.expand_activities_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.expanded_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.expanded_menu; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.fill_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill_vertical; + global::Xamarin.Forms.Platform.Android.Resource.Id.@fixed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.@fixed; + global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.flyoutcontent_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_recycler = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.flyoutcontent_recycler; + global::Xamarin.Forms.Platform.Android.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; + global::Xamarin.Forms.Platform.Android.Resource.Id.ghost_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ghost_view; + global::Xamarin.Forms.Platform.Android.Resource.Id.home = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.home; + global::Xamarin.Forms.Platform.Android.Resource.Id.homeAsUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.homeAsUp; + global::Xamarin.Forms.Platform.Android.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; + global::Xamarin.Forms.Platform.Android.Resource.Id.ifRoom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ifRoom; + global::Xamarin.Forms.Platform.Android.Resource.Id.image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.image; + global::Xamarin.Forms.Platform.Android.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; + global::Xamarin.Forms.Platform.Android.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; + global::Xamarin.Forms.Platform.Android.Resource.Id.item_touch_helper_previous_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.item_touch_helper_previous_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Id.largeLabel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.largeLabel; + global::Xamarin.Forms.Platform.Android.Resource.Id.left = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.left; + global::Xamarin.Forms.Platform.Android.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; + global::Xamarin.Forms.Platform.Android.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; + global::Xamarin.Forms.Platform.Android.Resource.Id.listMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.listMode; + global::Xamarin.Forms.Platform.Android.Resource.Id.list_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.list_item; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_scrollview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_scrollview; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_tablayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_tablayout; + global::Xamarin.Forms.Platform.Android.Resource.Id.main_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.masked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.masked; + global::Xamarin.Forms.Platform.Android.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; + global::Xamarin.Forms.Platform.Android.Resource.Id.message = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.message; + global::Xamarin.Forms.Platform.Android.Resource.Id.middle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.middle; + global::Xamarin.Forms.Platform.Android.Resource.Id.mini = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.mini; + global::Xamarin.Forms.Platform.Android.Resource.Id.multiply = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.multiply; + global::Xamarin.Forms.Platform.Android.Resource.Id.navigation_header_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.navigation_header_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.never = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.never; + global::Xamarin.Forms.Platform.Android.Resource.Id.none = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.none; + global::Xamarin.Forms.Platform.Android.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; + global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; + global::Xamarin.Forms.Platform.Android.Resource.Id.parallax = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parallax; + global::Xamarin.Forms.Platform.Android.Resource.Id.parentPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parentPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.parent_matrix = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parent_matrix; + global::Xamarin.Forms.Platform.Android.Resource.Id.pin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.pin; + global::Xamarin.Forms.Platform.Android.Resource.Id.progress_circular = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.progress_circular; + global::Xamarin.Forms.Platform.Android.Resource.Id.progress_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.progress_horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Id.radio = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.radio; + global::Xamarin.Forms.Platform.Android.Resource.Id.right = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right; + global::Xamarin.Forms.Platform.Android.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_image_matrix = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_image_matrix; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_non_transition_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_non_transition_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Id.save_scale_type = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_scale_type; + global::Xamarin.Forms.Platform.Android.Resource.Id.screen = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.screen; + global::Xamarin.Forms.Platform.Android.Resource.Id.scroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scroll; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollIndicatorDown; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollIndicatorUp; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollView; + global::Xamarin.Forms.Platform.Android.Resource.Id.scrollable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollable; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_badge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_badge; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_button; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_close_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_close_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_edit_frame = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_edit_frame; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_go_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_go_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_mag_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_mag_icon; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_plate = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_plate; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_src_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_src_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.search_voice_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_voice_btn; + global::Xamarin.Forms.Platform.Android.Resource.Id.select_dialog_listview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.select_dialog_listview; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_appbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_scrollview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_scrollview; + global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Id.shortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shortcut; + global::Xamarin.Forms.Platform.Android.Resource.Id.showCustom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showCustom; + global::Xamarin.Forms.Platform.Android.Resource.Id.showHome = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showHome; + global::Xamarin.Forms.Platform.Android.Resource.Id.showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.smallLabel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.smallLabel; + global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snackbar_action; + global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snackbar_text; + global::Xamarin.Forms.Platform.Android.Resource.Id.snap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snap; + global::Xamarin.Forms.Platform.Android.Resource.Id.spacer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.spacer; + global::Xamarin.Forms.Platform.Android.Resource.Id.split_action_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.split_action_bar; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_atop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_atop; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_in; + global::Xamarin.Forms.Platform.Android.Resource.Id.src_over = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_over; + global::Xamarin.Forms.Platform.Android.Resource.Id.start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.start; + global::Xamarin.Forms.Platform.Android.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; + global::Xamarin.Forms.Platform.Android.Resource.Id.submenuarrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.submenuarrow; + global::Xamarin.Forms.Platform.Android.Resource.Id.submit_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.submit_area; + global::Xamarin.Forms.Platform.Android.Resource.Id.tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; + global::Xamarin.Forms.Platform.Android.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; + global::Xamarin.Forms.Platform.Android.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; + global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textSpacerNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textSpacerNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Id.text_input_password_toggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text_input_password_toggle; + global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_counter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textinput_counter; + global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textinput_error; + global::Xamarin.Forms.Platform.Android.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; + global::Xamarin.Forms.Platform.Android.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; + global::Xamarin.Forms.Platform.Android.Resource.Id.titleDividerNoCustom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.titleDividerNoCustom; + global::Xamarin.Forms.Platform.Android.Resource.Id.title_template = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title_template; + global::Xamarin.Forms.Platform.Android.Resource.Id.top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.top; + global::Xamarin.Forms.Platform.Android.Resource.Id.topPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.topPanel; + global::Xamarin.Forms.Platform.Android.Resource.Id.touch_outside = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.touch_outside; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_current_scene = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_current_scene; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_layout_save = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_layout_save; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_position = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_position; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_scene_layoutid_cache = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_scene_layoutid_cache; + global::Xamarin.Forms.Platform.Android.Resource.Id.transition_transform = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_transform; + global::Xamarin.Forms.Platform.Android.Resource.Id.uniform = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.uniform; + global::Xamarin.Forms.Platform.Android.Resource.Id.up = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.up; + global::Xamarin.Forms.Platform.Android.Resource.Id.useLogo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.useLogo; + global::Xamarin.Forms.Platform.Android.Resource.Id.view_offset_helper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.view_offset_helper; + global::Xamarin.Forms.Platform.Android.Resource.Id.visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.visible; + global::Xamarin.Forms.Platform.Android.Resource.Id.withText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.withText; + global::Xamarin.Forms.Platform.Android.Resource.Id.wrap_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.wrap_content; + global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityDefaultDur = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.abc_config_activityDefaultDur; + global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityShortDur = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.abc_config_activityShortDur; + global::Xamarin.Forms.Platform.Android.Resource.Integer.app_bar_elevation_anim_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.app_bar_elevation_anim_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.bottom_sheet_slide_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.bottom_sheet_slide_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Integer.config_tooltipAnimTime = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.config_tooltipAnimTime; + global::Xamarin.Forms.Platform.Android.Resource.Integer.design_snackbar_text_max_lines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.design_snackbar_text_max_lines; + global::Xamarin.Forms.Platform.Android.Resource.Integer.hide_password_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.hide_password_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.show_password_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.show_password_duration; + global::Xamarin.Forms.Platform.Android.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_title_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_bar_title_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_up_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_bar_up_container; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_menu_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_mode_bar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_close_item_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_mode_close_item_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_activity_chooser_view; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view_list_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_activity_chooser_view_list_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_button_bar_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_button_bar_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_dialog_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_dialog_title_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_expanded_menu_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_expanded_menu_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_checkbox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_checkbox; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_radio = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_radio; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_header_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_popup_menu_header_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_popup_menu_item_layout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_content_include = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_content_include; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_simple; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple_overlay_action_mode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_simple_overlay_action_mode; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_dropdown_item_icons_2line = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_search_dropdown_item_icons_2line; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_search_view; + global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_select_dialog_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_select_dialog_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.BottomTabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.BottomTabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_navigation_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_bottom_navigation_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_sheet_dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_bottom_sheet_dialog; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_snackbar; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar_include = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_snackbar_include; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_tab_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_tab_text; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_menu_item_action_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_menu_item_action_area; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_header; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_separator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_separator; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_subheader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_subheader; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_menu; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_menu_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.design_text_input_password_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_text_input_password_icon; + global::Xamarin.Forms.Platform.Android.Resource.Layout.FlyoutContent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.FlyoutContent; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; + global::Xamarin.Forms.Platform.Android.Resource.Layout.RootLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.RootLayout; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_item_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_item_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_multichoice_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_multichoice_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_singlechoice_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_singlechoice_material; + global::Xamarin.Forms.Platform.Android.Resource.Layout.ShellContent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.ShellContent; + global::Xamarin.Forms.Platform.Android.Resource.Layout.support_simple_spinner_dropdown_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.support_simple_spinner_dropdown_item; + global::Xamarin.Forms.Platform.Android.Resource.Layout.tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.tooltip; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_home_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_bar_home_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_up_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_bar_up_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_menu_overflow_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_menu_overflow_description; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_mode_done = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_mode_done; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_activity_chooser_view_see_all = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_activity_chooser_view_see_all; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_activitychooserview_choose_application = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_activitychooserview_choose_application; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_off = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_capital_off; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_on = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_capital_on; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_body_1_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_body_2_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_button_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_button_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_caption_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_caption_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_1_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_2_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_3_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_3_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_4_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_4_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_headline_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_headline_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_menu_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_menu_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_subhead_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_subhead_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_title_material; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_search_hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_search_hint; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_clear = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_clear; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_query; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_search = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_search; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_submit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_submit; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_voice = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_voice; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_shareactionprovider_share_with; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with_application = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_shareactionprovider_share_with_application; + global::Xamarin.Forms.Platform.Android.Resource.String.abc_toolbar_collapse_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_toolbar_collapse_description; + global::Xamarin.Forms.Platform.Android.Resource.String.appbar_scrolling_view_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.appbar_scrolling_view_behavior; + global::Xamarin.Forms.Platform.Android.Resource.String.bottom_sheet_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.bottom_sheet_behavior; + global::Xamarin.Forms.Platform.Android.Resource.String.character_counter_pattern = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.character_counter_pattern; + global::Xamarin.Forms.Platform.Android.Resource.String.password_toggle_content_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.password_toggle_content_description; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_strike_through = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye_mask_strike_through; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye_mask_visible; + global::Xamarin.Forms.Platform.Android.Resource.String.path_password_strike_through = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_strike_through; + global::Xamarin.Forms.Platform.Android.Resource.String.search_menu_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.search_menu_title; + global::Xamarin.Forms.Platform.Android.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.AlertDialog_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.AlertDialog_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_DropDownUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_DropDownUp; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_Design_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_Design_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_DropDownUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_DropDownUp; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_CardView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitle_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_DialogWindowTitle_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitleBackground_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_DialogWindowTitleBackground_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body1; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body2; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Caption = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Caption; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display1; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display2; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display3; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display4 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display4; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Headline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Headline; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_CompactMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_CompactMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V14_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V14_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ImageButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ImageButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_Design_TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V11_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V11_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V14_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V14_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V21_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V21_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V25_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V25_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body1; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Caption = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Caption; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display1; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display3; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display4 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display4; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Headline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Headline; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Tooltip; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Counter; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Counter_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Error; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Hint; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Snackbar_Message = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Snackbar_Message; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Tab = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Tab; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_CompactMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_CompactMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_DarkActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light_BottomSheetDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_NoActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert; + global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Light; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Colored; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_Switch; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_EditText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ImageButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ImageButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView_Menu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu_Overflow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Indicator; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Small; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SearchView_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar_Discrete; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_Underlined; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_TextView_SpinnerItem; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomNavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_BottomNavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomSheet_Modal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_BottomSheet_Modal; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CollapsingToolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_CollapsingToolbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CoordinatorLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_CoordinatorLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_FloatingActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_FloatingActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_NavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_NavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_ScrimInsetsFrameLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_ScrimInsetsFrameLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_Snackbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_Snackbar; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TextInputLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_TextInputLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundStacked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_backgroundStacked; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_customNavigationLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_customNavigationLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_displayOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_displayOptions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_divider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_hideOnContentScroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_hideOnContentScroll; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_homeLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_indeterminateProgressStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_indeterminateProgressStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_itemPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_itemPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_logo; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_navigationMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_navigationMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_progressBarPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_progressBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBarLayout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuItemView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView_android_minWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuItemView_android_minWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_backgroundSplit; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_closeItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_closeItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_subtitleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_titleTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_initialActivityCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView_initialActivityCount; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_android_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_buttonPanelSideLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_buttonPanelSideLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_listItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_listLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_multiChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_multiChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_showTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_singleChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_singleChoiceItemLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_expanded; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_android_src = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_android_src; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_srcCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_srcCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_tint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_tintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_android_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_android_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMark; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_textAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_textAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_android_textAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_android_textAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizePresetSizes = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizePresetSizes; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeTextType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeTextType; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarPopupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarPopupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSplitStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSplitStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionDropDownStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionDropDownStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCutDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCutDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeFindDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeFindDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePasteDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePasteDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeShareDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeShareDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSplitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSplitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_activityChooserViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_activityChooserViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowIsFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowIsFloating; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_borderlessButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_borderlessButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkboxStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_checkboxStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkedTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_checkedTextViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorAccent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorAccent; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorBackgroundFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorBackgroundFloating; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorButtonNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorButtonNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlActivated = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlActivated; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlHighlight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlHighlight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorError; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimary; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimaryDark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimaryDark; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_controlBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_controlBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogPreferredPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dialogPreferredPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerHorizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dividerHorizontal; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerVertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dividerVertical; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropDownListViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dropDownListViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_homeAsUpIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_imageButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_imageButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listDividerAlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listDividerAlertDialog; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listMenuViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listMenuViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPopupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_popupMenuStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_popupWindowStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_radioButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_radioButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_searchViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_searchViewStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_seekBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_seekBarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_switchStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_switchStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorSearchUrl = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textColorSearchUrl; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipForegroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipForegroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipFrameBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipFrameBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBarOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBarOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionModeOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionModeOverlay; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMajor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMinor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_menu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ButtonBarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout_allowStacking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ButtonBarLayout_allowStacking; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_android_minHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_android_minWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardBackgroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardBackgroundColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardCornerRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardCornerRadius; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardElevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardMaxElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardMaxElevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardPreventCornerOverlap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardPreventCornerOverlap; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardUseCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardUseCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_contentScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_contentScrim; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_titleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_titleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_toolbarId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_toolbarId; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_android_alpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_android_color; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_android_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_android_button; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_buttonTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_buttonTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_keylines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_keylines; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_statusBarBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_statusBarBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetDialogTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_textColorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_textColorError; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowHeadLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowHeadLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowShaftLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowShaftLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_barLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_barLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_color; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_drawableSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_drawableSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_gapBetweenBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_gapBetweenBars; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_spinBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_spinBars; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_thickness; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_borderWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_borderWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_fabSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_fabSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_pressedTranslationZ = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_pressedTranslationZ; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_rippleColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_rippleColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_useCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_useCompatPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foreground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foreground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAligned = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAligned; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_orientation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_orientation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_weightSum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_weightSum; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_divider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_dividerPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_dividerPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_showDividers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_showDividers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_checkableBehavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_checkableBehavior; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_enabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_enabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_menuCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_menuCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_orderInCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_orderInCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_visible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionProviderClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionProviderClass; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionViewClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionViewClass; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_alphabeticModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_alphabeticModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_alphabeticShortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_alphabeticShortcut; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checkable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_checkable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_checked; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_enabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_enabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_menuCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_menuCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_numericShortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_numericShortcut; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_onClick = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_onClick; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_orderInCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_orderInCategory; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_titleCondensed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_titleCondensed; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_visible; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_contentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_contentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_iconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_iconTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_numericModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_numericModifiers; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_showAsAction = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_showAsAction; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_tooltipText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_tooltipText; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_headerBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_headerBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_horizontalDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_horizontalDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemIconDisabledAlpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemIconDisabledAlpha; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_verticalDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_verticalDivider; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_windowAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_windowAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_preserveIconSpacing = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_preserveIconSpacing; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_subMenuArrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_subMenuArrow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_fitsSystemWindows = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_fitsSystemWindows; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_headerLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_headerLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemIconTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_menu; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_android_popupAnimationStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_android_popupBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_overlapAnchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_overlapAnchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindowBackgroundState; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingBottomNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView_paddingBottomNoButtons; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingTopNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView_paddingTopNoTitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_descendantFocusability = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_android_descendantFocusability; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_orientation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_android_orientation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_layoutManager = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_layoutManager; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_reverseLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_reverseLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_spanCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_spanCount; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_stackFromEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_stackFromEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_focusable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_focusable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_imeOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_imeOptions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_inputType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_inputType; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_closeIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_closeIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_commitIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_commitIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_defaultQueryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_defaultQueryHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_goIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_goIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_iconifiedByDefault = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_iconifiedByDefault; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_queryBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_queryHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchHintIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_searchHintIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_searchIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_submitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_submitBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_suggestionRowLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_suggestionRowLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_voiceIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_voiceIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_android_maxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_elevation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_maxActionInlineWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_maxActionInlineWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_dropDownWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_dropDownWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_entries = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_entries; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_popupBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_popupBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_prompt = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_prompt; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOff = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_textOff; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_textOn; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_thumb; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_showText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_showText; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_splitTrack = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_splitTrack; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTextPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTextPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_track; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_trackTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_trackTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_icon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_layout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_text; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabBackground; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabContentStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabContentStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMaxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMaxWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMinWidth; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPadding; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabSelectedTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabSelectedTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDx = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDx; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDy; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowRadius; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColorHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorLink = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColorLink; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textSize; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textStyle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_typeface = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_typeface; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_fontFamily; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_textAllCaps; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_android_hint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_textColorHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_android_textColorHint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterMaxLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterMaxLength; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_errorEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_errorTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintAnimationEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintAnimationEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleDrawable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleEnabled; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_android_gravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_minHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_android_minHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_buttonGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_buttonGravity; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_collapseContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_collapseIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetEndWithActions; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetLeft; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetRight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetStartWithNavigation; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_logo; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logoDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_logoDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_maxButtonHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_maxButtonHeight; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_navigationContentDescription; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_navigationIcon; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_popupTheme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitle; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_title; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMargin; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginBottom; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginTop; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargins = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMargins; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleTextAppearance; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleTextColor; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_focusable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_android_focusable; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_android_theme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_paddingEnd; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_paddingStart; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_theme; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_android_background; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTint; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_id; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; + global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; } public partial class Animation diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 755fe840..8f241e54 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -1,5 +1,6 @@ using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Internal; using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -19,7 +20,7 @@ public class DataSourceUpdateSinkImplTest : BaseTest public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) { _store = new InMemoryDataStore(); - _flagTracker = new FlagTrackerImpl(testLogger); + _flagTracker = new FlagTrackerImpl(new TaskExecutor(null, testLogger), testLogger); _updateSink = new DataSourceUpdateSinkImpl(_store, _flagTracker); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index cdd8391e..003aefa8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -1,6 +1,7 @@ using System; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Internal; using Xunit; using Xunit.Abstractions; @@ -23,12 +24,14 @@ IDataSource MakeDataSource() { var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); user = User.WithKey("user1Key"); + var executor = new TaskExecutor(null, testLogger); return new PollingDataSource( - new DataSourceUpdateSinkImpl(_store, new FlagTrackerImpl(testLogger)), + new DataSourceUpdateSinkImpl(_store, new FlagTrackerImpl(executor, testLogger)), user, mockFeatureFlagRequestor, TimeSpan.FromSeconds(30), TimeSpan.Zero, + executor, testLogger ); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index a03e8eb8..48f74bba 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -182,18 +182,6 @@ string DeleteFlag() return flagToDelete; } - string UpdatedFlagWithLowerVersion() - { - var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":1,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; - return updatedFlagAsJson; - } - - string DeleteFlagWithLowerVersion() - { - var flagToDelete = "{\"key\":\"int-flag\",\"version\":1}"; - return flagToDelete; - } - void PUTMessageSentToProcessor() { MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("put", initialFlagsJson, null)); From a3e2bf33038d69cccce258e279d7b82da9797fa5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 28 Sep 2021 16:20:22 -0700 Subject: [PATCH 363/499] data source status API --- .../Integrations/TestData.cs | 34 ++ .../Interfaces/DataSourceStatus.cs | 338 ++++++++++++++++++ .../Interfaces/IDataSourceStatusProvider.cs | 120 +++++++ .../Interfaces/IDataSourceUpdateSink.cs | 30 ++ .../Interfaces/ILdClient.cs | 11 + .../Interfaces/LdClientContext.cs | 4 - .../Internal/DataSources/ConnectionManager.cs | 9 +- .../DataSourceStatusProviderImpl.cs | 31 ++ .../DataSources/DataSourceUpdateSinkImpl.cs | 85 ++++- .../Internal/DataSources/PollingDataSource.cs | 34 +- .../DataSources/StreamingDataSource.cs | 44 ++- .../Internal/FlagTrackerImpl.cs | 27 +- .../Internal/Interfaces/IDataStore.cs | 2 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 23 +- .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 13 + .../ILdClientExtensionsTest.cs | 1 + .../DataSourceUpdateSinkImplTest.cs | 5 +- .../DataSources/PollingDataSourceTest.cs | 5 +- .../LDClientEndToEndTests.cs | 2 +- .../LdClientDataSourceStatusTests.cs | 274 ++++++++++++++ .../LdClientEventTests.cs | 6 +- .../LdClientListenersTest.cs | 34 +- .../LdClientTests.cs | 10 +- .../MockComponents.cs | 93 +++-- 24 files changed, 1133 insertions(+), 102 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs index 7aae33fc..4f951a88 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -167,6 +167,33 @@ private void UpdateInternal(string key, FlagBuilder builder) } } + /// + /// Simulates a change in the data source status. + /// + /// + /// Use this if you want to test the behavior of application code that uses + /// to track whether the data source is having + /// problems (for example, a network failure interrupting the streaming connection). It does + /// not actually stop the data source from working, so even if you have + /// simulated an outage, calling will still send updates. + /// + /// one of the constants defined by + /// an optional instance + /// + public TestData UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) + { + DataSourceImpl[] instances; + lock (_lock) + { + instances = _instances.ToArray(); + } + foreach (var instance in instances) + { + instance.DoUpdateStatus(newState, newError); + } + return this; + } + /// public IDataSource CreateDataSource( LdClientContext context, @@ -579,6 +606,13 @@ internal void DoUpdate(string key, ItemDescriptor item) item.Item is null ? "" : DataModelSerialization.SerializeFlag(item.Item))); _updateSink.Upsert(User, key, item); } + + internal void DoUpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) + { + _log.Debug("updating status to {0}{1}", newState, + newError.HasValue ? (" (" + newError.Value + ")") : ""); + _updateSink.UpdateStatus(newState, newError); + } } #endregion diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs new file mode 100644 index 00000000..42195c7e --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs @@ -0,0 +1,338 @@ +using System; +using System.IO; +using System.Text; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Information about the data source's status and about the last status change. + /// + /// + /// + public struct DataSourceStatus + { + /// + /// An enumerated value representing the overall current state of the data source. + /// + public DataSourceState State { get; set; } + + /// + /// The date/time that the value of most recently changed. + /// + /// + /// The meaning of this depends on the current state: + /// + /// For , it is the time that the SDK started initializing. + /// For , it is the time that the data source most recently entered a valid + /// state, after previously having been either or + /// . + /// For , it is the time that the data source most recently entered an + /// error state, after previously having been . + /// For , it is the time that the data source encountered an unrecoverable error + /// or that the SDK was explicitly shut down. + /// + /// + public DateTime StateSince { get; set; } + + /// + /// Information about the last error that the data source encountered, if any. + /// + /// + /// This property should be updated whenever the data source encounters a problem, even if it does not cause + /// to change. For instance, if a stream connection fails and the state changes to + /// , and then subsequent attempts to restart the connection also fail, the + /// state will remain but the error information will be updated each time-- + /// and the last error will still be reported in this property even if the state later becomes + /// . + /// + public ErrorInfo? LastError { get; set; } + + /// + public override string ToString() => + string.Format("DataSourceStatus({0},{1},{2})", State, StateSince, LastError); + + /// + /// A description of an error condition that the data source encountered. + /// + /// + public struct ErrorInfo + { + /// + /// An enumerated value representing the general category of the error. + /// + public ErrorKind Kind { get; set; } + + /// + /// The HTTP status code if the error was , or zero otherwise. + /// + public int StatusCode { get; set; } + + /// + /// Any additional human-readable information relevant to the error. + /// + /// + /// The format of this message is subject to change and should not be relied on programmatically. + /// + public string Message { get; set; } + + /// + /// The date/time that the error occurred. + /// + public DateTime Time { get; set; } + + /// + /// Constructs an instance based on an exception. + /// + /// the exception + /// an ErrorInfo + public static ErrorInfo FromException(Exception e) => new ErrorInfo + { + Kind = e is IOException ? ErrorKind.NetworkError : ErrorKind.Unknown, + Message = e.Message, + Time = DateTime.Now + }; + + /// + /// Constructs an instance based on an HTTP error status. + /// + /// the status code + /// an ErrorInfo + public static ErrorInfo FromHttpError(int statusCode) => new ErrorInfo + { + Kind = ErrorKind.ErrorResponse, + StatusCode = statusCode, + Time = DateTime.Now + }; + + /// + public override string ToString() + { + var s = new StringBuilder(); + s.Append(Kind.Identifier()); + if (StatusCode > 0 || !string.IsNullOrEmpty(Message)) + { + s.Append("("); + if (StatusCode > 0) + { + s.Append(StatusCode); + } + if (!string.IsNullOrEmpty(Message)) + { + if (StatusCode > 0) + { + s.Append(","); + } + s.Append(Message); + } + s.Append(")"); + } + s.Append("@"); + s.Append(Time); + return s.ToString(); + } + } + + /// + /// An enumeration describing the general type of an error reported in . + /// + public enum ErrorKind + { + /// + /// An unexpected error, such as an uncaught exception, further described by . + /// + Unknown, + + /// + /// An I/O error such as a dropped connection. + /// + NetworkError, + + /// + /// The LaunchDarkly service returned an HTTP response with an error status, available with + /// . + /// + ErrorResponse, + + /// + /// The SDK received malformed data from the LaunchDarkly service. + /// + InvalidData, + + /// + /// The data source itself is working, but when it tried to put an update into the data store, the data + /// store failed (so the SDK may not have the latest data). + /// + /// + /// Data source implementations do not need to report this kind of error; it will be automatically + /// reported by the SDK whenever one of the update methods of throws an + /// exception. + /// + StoreError + } + } + + /// + /// An enumeration of possible values for . + /// + public enum DataSourceState + { + /// + /// The initial state of the data source when the SDK is being initialized. + /// + /// + /// If it encounters an error that requires it to retry initialization, the state will remain at + /// until it either succeeds and becomes , or + /// permanently fails and becomes . + /// + Initializing, + + /// + /// Indicates that the data source is currently operational and has not had any problems since the + /// last time it received data. + /// + /// + /// In streaming mode, this means that there is currently an open stream connection and that at least + /// one initial message has been received on the stream. In polling mode, it means that the last poll + /// request succeeded. + /// + Valid, + + /// + /// Indicates that the data source encountered an error that it will attempt to recover from. + /// + /// + /// + /// In streaming mode, this means that the stream connection failed, or had to be dropped due to some + /// other error, and will be retried after a backoff delay. In polling mode, it means that the last poll + /// request failed, and a new poll request will be made after the configured polling interval. + /// + /// + /// This is different from , which would mean that the SDK knows the + /// device is not online at all and is waiting for it to be online again. + /// + /// + Interrupted, + + /// + /// Indicates that the SDK is in background mode and background updating has been disabled. + /// + /// + /// On mobile devices, if the application containing the SDK is put into the background, by default + /// the SDK will still check for feature flag updates occasionally. However, if this has been disabled + /// with EnableBackgroundUpdating(false), the SDK will instead stop the data source and wait + /// until it is in the foreground again. During that time, the state is BackgroundDisabled. + /// + /// + BackgroundDisabled, + + /// + /// Indicates that the SDK is aware of a lack of network connectivity. + /// + /// + /// + /// On mobile devices, if wi-fi is turned off or there is no wi-fi connection and cellular data is + /// unavailable, the device OS will tell the SDK that the network is unavailable. The SDK then enters + /// this state, where it will not try to make any network connections since they would be guaranteed to + /// fail, until the OS informs it that the network is available again. + /// + /// + /// This is different from , which would mean that the SDK thinks network + /// requests ought to be working but for some reason they are not (due to either a network problem + /// that the device OS does not know about, or a problem with the service endpoint). + /// + /// + /// The .NET Standard version of the SDK is not able to detect network status, so desktop applications + /// will see the state if the network is turned off. + /// + /// + NetworkUnavailable, + + /// + /// Indicates that the application has told the SDK to stay offline. + /// + /// + /// This means that either the SDK was originally configured with Offline(true) and has not been + /// changed to be online since then, or that it was originally online but has been put offline with + /// or . + /// It is not a permanent condition; the application can change this at any time. + /// + /// + /// + /// + SetOffline, + + /// + /// Indicates that the data source has been permanently shut down. + /// + /// + /// This could be because it encountered an unrecoverable error (for instance, the LaunchDarkly service + /// rejected the SDK key; an invalid SDK key will never become valid), or because the SDK client was + /// explicitly shut down. + /// + Shutdown + } + + /// + /// Extension helper methods for use with data source status types. + /// + public static class DataSourceStatusExtensions + { + /// + /// Returns a standardized string identifier for a . + /// + /// + /// These Java-style uppercase identifiers (INITIALIZING, VALID, etc.) may be used in + /// logging for consistency across SDKs. + /// + /// a state value + /// a string identifier + public static string Identifier(this DataSourceState state) + { + switch (state) + { + case DataSourceState.Initializing: + return "INITIALIZING"; + case DataSourceState.Valid: + return "VALID"; + case DataSourceState.Interrupted: + return "INTERRUPTED"; + case DataSourceState.NetworkUnavailable: + return "NETWORK_UNAVAILABLE"; + case DataSourceState.SetOffline: + return "SET_OFFLINE"; + case DataSourceState.Shutdown: + return "SHUTDOWN"; + default: + return state.ToString(); + } + } + + /// + /// Returns a standardized string identifier for a . + /// + /// + /// These Java-style uppercase identifiers (ERROR_RESPONSE, NETWORK_ERROR, etc.) may be + /// used in logging for consistency across SDKs. + /// + /// an error kind value + /// a string identifier + public static string Identifier(this DataSourceStatus.ErrorKind errorKind) + { + switch (errorKind) + { + case DataSourceStatus.ErrorKind.ErrorResponse: + return "ERROR_RESPONSE"; + case DataSourceStatus.ErrorKind.InvalidData: + return "INVALID_DATA"; + case DataSourceStatus.ErrorKind.NetworkError: + return "NETWORK_ERROR"; + case DataSourceStatus.ErrorKind.StoreError: + return "STORE_ERROR"; + case DataSourceStatus.ErrorKind.Unknown: + return "UNKNOWN"; + default: + return errorKind.ToString(); + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs new file mode 100644 index 00000000..0d07dbb8 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading.Tasks; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// An interface for querying the status of the SDK's data source. + /// + /// + /// + /// The data source is the component that receives updates to feature flag data. Normally this is a streaming + /// connection, but it could be polling or test data depending on your configuration. + /// + /// + /// An implementation of this interface is returned by . + /// Application code never needs to implement this interface. + /// + /// + /// + public interface IDataSourceStatusProvider + { + /// + /// The current status of the data source. + /// + /// + /// + /// All of the built-in data source implementations are guaranteed to update this status whenever they + /// successfully initialize, encounter an error, or recover after an error. + /// + /// + /// For a custom data source implementation, it is the responsibility of the data source to report its + /// status via ; if it does not do so, the status will always be reported + /// as . + /// + /// + DataSourceStatus Status { get; } + + /// + /// An event for receiving notifications of status changes. + /// + /// + /// + /// Any handlers attached to this event will be notified whenever any property of the status has changed. + /// See for an explanation of the meaning of each property and what could cause it + /// to change. + /// + /// + /// Notifications will be dispatched on a background task. It is the listener's responsibility to return + /// as soon as possible so as not to block subsequent notifications. + /// + /// + event EventHandler StatusChanged; + + /// + /// A synchronous method for waiting for a desired connection state. + /// + /// + /// + /// If the current state is already when this method is called, it immediately + /// returns. Otherwise, it blocks until 1. the state has become , 2. the state + /// has become (since that is a permanent condition), or 3. the specified + /// timeout elapses. + /// + /// + /// A scenario in which this might be useful is if you want to create the without waiting + /// for it to initialize, and then wait for initialization at a later time or on a different thread: + /// + /// + /// // create the client but do not wait + /// var config = Configuration.Builder("my-sdk-key").StartWaitTime(TimeSpan.Zero).Build(); + /// var client = new LDClient(config); + /// + /// // later, possibly on another thread: + /// var inited = client.DataSourceStatusProvider.WaitFor(DataSourceState.Valid, + /// TimeSpan.FromSeconds(10)); + /// if (!inited) { + /// // do whatever is appropriate if initialization has timed out + /// } + /// + /// + /// the desired connection state (normally this would be + /// ) + /// the maximum amount of time to wait-- or to block + /// indefinitely + /// true if the connection is now in the desired state; false if it timed out, or if the state + /// changed to and that was not the desired state + /// + bool WaitFor(DataSourceState desiredState, TimeSpan timeout); + + /// + /// An asynchronous method for waiting for a desired connection state. + /// + /// + /// + /// This method behaves identically to except that it is asynchronous. The following + /// example is the asynchronous equivalent of the example code shown for : + /// + /// + /// // create the client but do not wait + /// var config = Configuration.Builder("my-sdk-key").StartWaitTime(TimeSpan.Zero).Build(); + /// var client = new LDClient(config); + /// + /// // later, possibly on another thread: + /// var inited = await client.DataSourceStatusProvider.WaitFor(DataSourceState.Valid, + /// TimeSpan.FromSeconds(10)); + /// if (!inited) { + /// // do whatever is appropriate if initialization has timed out + /// } + /// + /// + /// the desired connection state (normally this would be + /// ) + /// the maximum amount of time to wait-- or to block + /// indefinitely + /// true if the connection is now in the desired state; false if it timed out, or if the state + /// changed to and that was not the desired state + /// + Task WaitForAsync(DataSourceState desiredState, TimeSpan timeout); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs index d01183e7..ca3dad3c 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -2,6 +2,10 @@ namespace LaunchDarkly.Sdk.Client.Interfaces { + // Note: In .NET server-side SDK 6.x, Java SDK 5.x, and Go SDK 5.x, where this component was added, it + // is called "DataSourceUpdates". This name was thought to be a bit confusing, since it receives updates + // rather than providing them, so as of .NET client-side SDK 2.x we are calling it an "update sink". + /// /// Interface that an implementation of will use to push data into the SDK. /// @@ -27,5 +31,31 @@ public interface IDataSourceUpdateSink /// the feature flag key /// the item data void Upsert(User user, string key, ItemDescriptor data); + + /// + /// Informs the SDK of a change in the data source's status. + /// + /// + /// + /// Data source implementations should use this method if they have any concept of being in a valid + /// state, a temporarily disconnected state, or a permanently stopped state. + /// + /// + /// If is different from the previous state, and/or + /// is non-null, the SDK will start returning the new status(adding a timestamp for the change) from + /// , and will trigger status change events to any + /// registered listeners. + /// + /// + /// A special case is that if is , + /// but the previous state was , the state will + /// remain at because + /// is only meaningful after a successful startup. + /// + /// + /// the data source state + /// information about a new error, if any + /// + void UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index bf1546ad..6d2af89b 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -14,6 +14,17 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// public interface ILdClient : IDisposable { + /// + /// A mechanism for tracking the status of the data source. + /// + /// + /// The data source is the mechanism that the SDK uses to get feature flag configurations, such as a + /// streaming connection (the default) or poll requests. The + /// has methods for checking whether the data source is (as far as the SDK knows) currently operational, + /// and tracking changes in this status. This property will never be null. + /// + IDataSourceStatusProvider DataSourceStatusProvider { get; } + /// /// A mechanism for tracking changes in feature flag configurations. /// diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index 7fea1911..c927fa00 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -45,10 +45,6 @@ public LdClientContext( Configuration configuration ) : this(configuration, null) { } - /// - /// Creates an instance. - /// - /// the SDK configuration internal LdClientContext( Configuration configuration, object eventSender diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index 2d65283c..e705c804 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -25,6 +25,7 @@ internal sealed class ConnectionManager : IDisposable { private readonly Logger _log; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private readonly IDataSourceUpdateSink _updateSink; private bool _disposed = false; private bool _started = false; private bool _initialized = false; @@ -53,8 +54,9 @@ internal sealed class ConnectionManager : IDisposable /// public bool Initialized => LockUtils.WithReadLock(_lock, () => _initialized); - internal ConnectionManager(Logger log) + internal ConnectionManager(IDataSourceUpdateSink updateSink, Logger log) { + _updateSink = updateSink; _log = log; } @@ -245,6 +247,7 @@ private Task OpenOrCloseConnectionIfNecessary() { if (_dataSource == null && _dataSourceConstructor != null) { + _updateSink.UpdateStatus(DataSourceState.Initializing, null); _dataSource = _dataSourceConstructor(); return _dataSource.Start() .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); @@ -255,6 +258,10 @@ private Task OpenOrCloseConnectionIfNecessary() _dataSource?.Dispose(); _dataSource = null; _initialized = true; + _updateSink.UpdateStatus( + _forceOffline ? DataSourceState.SetOffline : DataSourceState.NetworkUnavailable, + null + ); return Task.FromResult(true); } return Task.FromResult(false); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs new file mode 100644 index 00000000..de840108 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal.Concurrent; + +namespace LaunchDarkly.Sdk.Client.Internal.DataSources +{ + internal sealed class DataSourceStatusProviderImpl : IDataSourceStatusProvider + { + private readonly DataSourceUpdateSinkImpl _updateSink; + + public event EventHandler StatusChanged + { + add => _updateSink.StatusChanged += value; + remove => _updateSink.StatusChanged -= value; + } + + public DataSourceStatus Status => _updateSink.CurrentStatus; + + internal DataSourceStatusProviderImpl(DataSourceUpdateSinkImpl updateSink) + { + _updateSink = updateSink; + } + + public bool WaitFor(DataSourceState desiredState, TimeSpan timeout) => + AsyncUtils.WaitSafely(() => _updateSink.WaitForAsync(desiredState, timeout)); + + public Task WaitForAsync(DataSourceState desiredState, TimeSpan timeout) => + _updateSink.WaitForAsync(desiredState, timeout); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index 25cab958..8ec1e483 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -1,7 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading.Tasks; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; @@ -11,18 +16,34 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink { private readonly IDataStore _dataStore; - private readonly FlagTrackerImpl _flagTracker; private readonly object _lastValuesLock = new object(); + private readonly TaskExecutor _taskExecutor; + private volatile ImmutableDictionary> _lastValues = ImmutableDictionary.Create>(); - public DataSourceUpdateSinkImpl( + private readonly StateMonitor _status; + internal DataSourceStatus CurrentStatus => _status.Current; + + internal event EventHandler FlagValueChanged; + internal event EventHandler StatusChanged; + + internal DataSourceUpdateSinkImpl( IDataStore dataStore, - FlagTrackerImpl flagTracker + bool isConfiguredOffline, + TaskExecutor taskExecutor, + Logger log ) { _dataStore = dataStore; - _flagTracker = flagTracker; + _taskExecutor = taskExecutor; + var initialStatus = new DataSourceStatus + { + State = isConfiguredOffline ? DataSourceState.SetOffline : DataSourceState.Initializing, + StateSince = DateTime.Now, + LastError = null + }; + _status = new StateMonitor(initialStatus, MaybeUpdateStatus, log); } public void Init(User user, FullDataSet data) @@ -46,6 +67,8 @@ public void Init(User user, FullDataSet data) _lastValues = _lastValues.SetItem(user.Key, newValues); } + UpdateStatus(DataSourceState.Valid, null); + if (oldValues != null) { List events = new List(); @@ -77,7 +100,7 @@ public void Init(User user, FullDataSet data) } foreach (var e in events) { - _flagTracker.FireEvent(e); + _taskExecutor.ScheduleEvent(e, FlagValueChanged); } } } @@ -117,8 +140,56 @@ public void Upsert(User user, string key, ItemDescriptor data) data.Item?.Value ?? LdValue.Null, data.Item is null ); - _flagTracker.FireEvent(eventArgs); + _taskExecutor.ScheduleEvent(eventArgs, FlagValueChanged); } } + + private struct StateAndError + { + public DataSourceState State { get; set; } + public DataSourceStatus.ErrorInfo? Error { get; set; } + } + + private static DataSourceStatus? MaybeUpdateStatus( + DataSourceStatus oldStatus, + StateAndError update + ) + { + var newState = + (update.State == DataSourceState.Interrupted && oldStatus.State == DataSourceState.Initializing) + ? DataSourceState.Initializing // see comment on IDataSourceUpdateSink.UpdateStatus + : update.State; + + if (newState == oldStatus.State && !update.Error.HasValue) + { + return null; + } + return new DataSourceStatus + { + State = newState, + StateSince = newState == oldStatus.State ? oldStatus.StateSince : DateTime.Now, + LastError = update.Error ?? oldStatus.LastError + }; + } + + public void UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) + { + var updated = _status.Update(new StateAndError { State = newState, Error = newError }, + out var newStatus); + + if (updated) + { + _taskExecutor.ScheduleEvent(newStatus, StatusChanged); + } + } + + internal async Task WaitForAsync(DataSourceState desiredState, TimeSpan timeout) + { + var newStatus = await _status.WaitForAsync( + status => status.State == desiredState || status.State == DataSourceState.Shutdown, + timeout + ); + return newStatus.HasValue && newStatus.Value.State == desiredState; + } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index bc4d8272..9c184ead 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; @@ -21,7 +22,6 @@ internal sealed class PollingDataSource : IDataSource private readonly TaskCompletionSource _startTask; private volatile CancellationTokenSource _canceller; private readonly AtomicBoolean _initialized = new AtomicBoolean(false); - private volatile bool _disposed; internal PollingDataSource( IDataSourceUpdateSink updateSink, @@ -80,15 +80,35 @@ private async Task UpdateTaskAsync() } } } - catch (UnsuccessfulResponseException ex) when (ex.StatusCode == 401) + catch (UnsuccessfulResponseException ex) { - _log.Error("Error Updating features: '{0}'", LogValues.ExceptionSummary(ex)); - _log.Error("Received 401 error, no further polling requests will be made since SDK key is invalid"); - if (!_initialized.Get()) + var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(ex.StatusCode); + + if (HttpErrors.IsRecoverable(ex.StatusCode)) + { + _log.Warn(HttpErrors.ErrorMessage(ex.StatusCode, "polling request", "will retry")); + _updateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + } + else { - _startTask.SetException(ex); + _log.Error(HttpErrors.ErrorMessage(ex.StatusCode, "polling request", "")); + _updateSink.UpdateStatus(DataSourceState.Shutdown, errorInfo); + + // if client is initializing, make it stop waiting + _startTask.TrySetResult(false); + + ((IDisposable)this).Dispose(); } - ((IDisposable)this).Dispose(); + } + catch (JsonReadException ex) + { + _log.Error("Polling request received malformed data: {0}", LogValues.ExceptionSummary(ex)); + _updateSink.UpdateStatus(DataSourceState.Interrupted, + new DataSourceStatus.ErrorInfo + { + Kind = DataSourceStatus.ErrorKind.InvalidData, + Time = DateTime.Now + }); } catch (Exception ex) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index a8760faa..2489bac4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -142,6 +142,16 @@ private void OnMessage(object sender, EventSource.MessageReceivedEventArgs e) { _log.Error("LaunchDarkly service request failed or received invalid data: {0}", LogValues.ExceptionSummary(ex)); + + var errorInfo = new DataSourceStatus.ErrorInfo + { + Kind = DataSourceStatus.ErrorKind.InvalidData, + Message = ex.Message, + Time = DateTime.Now + }; + _updateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + + _eventSource.Restart(false); } catch (Exception ex) { @@ -152,16 +162,40 @@ private void OnMessage(object sender, EventSource.MessageReceivedEventArgs e) private void OnError(object sender, EventSource.ExceptionEventArgs e) { var ex = e.Exception; - LogHelpers.LogException(_log, "Encountered EventSource error", ex); - if (ex is EventSource.EventSourceServiceUnsuccessfulResponseException respEx) + var recoverable = true; + DataSourceStatus.ErrorInfo errorInfo; + + if (ex is EventSourceServiceUnsuccessfulResponseException respEx) { int status = respEx.StatusCode; - _log.Error(HttpErrors.ErrorMessage(status, "streaming connection", "will retry")); + errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(status); if (!HttpErrors.IsRecoverable(status)) { - _initTask.TrySetException(ex); // sends this exception to the client if we haven't already started up - Dispose(true); + recoverable = false; + _log.Error(HttpErrors.ErrorMessage(status, "streaming connection", "")); } + else + { + _log.Warn(HttpErrors.ErrorMessage(status, "streaming connection", "will retry")); + } + } + else + { + errorInfo = DataSourceStatus.ErrorInfo.FromException(ex); + _log.Warn("Encountered EventSource error: {0}", LogValues.ExceptionSummary(ex)); + _log.Debug(LogValues.ExceptionTrace(ex)); + } + + _updateSink.UpdateStatus(recoverable ? DataSourceState.Interrupted : DataSourceState.Shutdown, + errorInfo); + + if (!recoverable) + { + // Make _initTask complete to tell the client to stop waiting for initialization. We use + // TrySetResult rather than SetResult here because it might have already been completed + // (if for instance the stream started successfully, then restarted and got a 401). + _initTask.TrySetResult(false); + ((IDisposable)this).Dispose(); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs index fe507dd3..a9807795 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/FlagTrackerImpl.cs @@ -1,33 +1,24 @@ using System; -using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Client.Internal.DataSources; namespace LaunchDarkly.Sdk.Client.Internal { internal sealed class FlagTrackerImpl : IFlagTracker { - public event EventHandler FlagValueChanged; + private readonly DataSourceUpdateSinkImpl _updateSink; - private readonly TaskExecutor _taskExecutor; - private readonly Logger _log; - - internal FlagTrackerImpl( - TaskExecutor taskExecutor, - Logger log - ) + public event EventHandler FlagValueChanged { - _taskExecutor = taskExecutor; - _log = log; + add =>_updateSink.FlagValueChanged += value; + remove => _updateSink.FlagValueChanged -= value; } - internal void FireEvent(FlagValueChangeEvent ev) + internal FlagTrackerImpl( + DataSourceUpdateSinkImpl updateSink + ) { - var copyOfHandlers = FlagValueChanged; - if (copyOfHandlers != null) - { - _taskExecutor.ScheduleEvent(ev, copyOfHandlers); - } + _updateSink = updateSink; } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs index 532db724..58ef59ab 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs @@ -31,7 +31,7 @@ internal interface IDataStore : IDisposable /// Overwrites the store's contents for a specific user with a serialized data set. /// /// the current user - /// + /// the data set void Init(User user, FullDataSet allData); /// diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index a9089c2a..b8923932 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -33,6 +33,7 @@ public sealed class LdClient : ILdClient readonly Configuration _config; readonly LdClientContext _context; readonly IDataSourceFactory _dataSourceFactory; + readonly IDataSourceStatusProvider _dataSourceStatusProvider; readonly IDataSourceUpdateSink _dataSourceUpdateSink; readonly IDataStore _dataStore; readonly ConnectionManager _connectionManager; @@ -85,6 +86,9 @@ public sealed class LdClient : ILdClient /// public bool Initialized => _connectionManager.Initialized; + /// + public IDataSourceStatusProvider DataSourceStatusProvider => _dataSourceStatusProvider; + /// public IFlagTracker FlagTracker => _flagTracker; @@ -135,9 +139,6 @@ public sealed class LdClient : ILdClient _user = DecorateUser(user); - var flagTracker = new FlagTrackerImpl(_taskExecutor, _log); - _flagTracker = flagTracker; - var persistentStore = configuration.PersistentDataStoreFactory is null ? new DefaultPersistentDataStore(_log.SubLogger(LogNames.DataStoreSubLog)) : configuration.PersistentDataStoreFactory.CreatePersistentDataStore(_context); @@ -146,12 +147,21 @@ public sealed class LdClient : ILdClient persistentStore, _log.SubLogger(LogNames.DataStoreSubLog) ); - _dataSourceUpdateSink = new DataSourceUpdateSinkImpl(_dataStore, flagTracker); + var dataSourceUpdateSink = new DataSourceUpdateSinkImpl( + _dataStore, + configuration.Offline, + _taskExecutor, + _log.SubLogger(LogNames.DataSourceSubLog) + ); + _dataSourceUpdateSink = dataSourceUpdateSink; _dataStore.Preload(_user); + _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); + _flagTracker = new FlagTrackerImpl(dataSourceUpdateSink); + _dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); - _connectionManager = new ConnectionManager(_log); + _connectionManager = new ConnectionManager(_dataSourceUpdateSink, _log); _connectionManager.SetForceOffline(configuration.Offline); if (configuration.Offline) { @@ -651,6 +661,8 @@ void Dispose(bool disposing) { _log.Info("Shutting down the LaunchDarkly client"); + _dataSourceUpdateSink.UpdateStatus(DataSourceState.Shutdown, null); + _backgroundModeManager.BackgroundModeChanged -= OnBackgroundModeChanged; _connectionManager.Dispose(); _dataStore.Dispose(); @@ -691,6 +703,7 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { _log.Debug("Background updating is disabled"); await _connectionManager.SetDataSourceConstructor(null, false); + _dataSourceUpdateSink.UpdateStatus(DataSourceState.BackgroundDisabled, null); return; } _log.Debug("Background updating is enabled, starting polling processor"); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 5dd9222b..70fa13d6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Internal; using Xunit; using Xunit.Abstractions; @@ -10,9 +11,13 @@ namespace LaunchDarkly.Sdk.Client [Collection("serialize all tests")] public class BaseTest : IDisposable { + protected const string BasicMobileKey = "mobile-key"; + protected static readonly User BasicUser = User.WithKey("user-key"); + protected readonly LoggingConfigurationBuilder testLogging; protected readonly Logger testLogger; protected readonly LogCapture logCapture = Logs.Capture(); + protected readonly TaskExecutor BasicTaskExecutor; public BaseTest() : this(capture => capture) { } @@ -25,6 +30,7 @@ protected BaseTest(Func adapterFn) var adapter = adapterFn(logCapture); testLogger = adapter.Level(LogLevel.Debug).Logger(""); testLogging = Components.Logging(adapter).Level(LogLevel.Debug); + BasicTaskExecutor = new TaskExecutor("test-sender", testLogger); } protected void ClearCachedFlags(User user) @@ -36,5 +42,12 @@ public void Dispose() { TestUtil.ClearClient(); } + + protected ConfigurationBuilder BasicConfig() => + Configuration.Builder(BasicMobileKey) + .DataSource(new MockDataSource().AsSingletonFactory()) + .Events(Components.NoEvents) + .Logging(testLogging) + .Persistence(Components.NoPersistence); // unless we're specifically testing flag caching, this helps to prevent test state contamination } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index c28f4339..9e742d15 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -118,6 +118,7 @@ public EvaluationDetail StringVariationDetail(string key, string default public bool Initialized => true; public bool Offline => false; + public IDataSourceStatusProvider DataSourceStatusProvider => null; public IFlagTracker FlagTracker => null; public IDictionary AllFlags() => diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 8f241e54..5e39ded6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -1,6 +1,5 @@ using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Internal; using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -20,8 +19,8 @@ public class DataSourceUpdateSinkImplTest : BaseTest public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) { _store = new InMemoryDataStore(); - _flagTracker = new FlagTrackerImpl(new TaskExecutor(null, testLogger), testLogger); - _updateSink = new DataSourceUpdateSinkImpl(_store, _flagTracker); + _updateSink = new DataSourceUpdateSinkImpl(_store, false, BasicTaskExecutor, testLogger); + _flagTracker = new FlagTrackerImpl(_updateSink); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 003aefa8..da0b24ff 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -24,14 +24,13 @@ IDataSource MakeDataSource() { var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); user = User.WithKey("user1Key"); - var executor = new TaskExecutor(null, testLogger); return new PollingDataSource( - new DataSourceUpdateSinkImpl(_store, new FlagTrackerImpl(executor, testLogger)), + new DataSourceUpdateSinkImpl(_store, false, BasicTaskExecutor, testLogger), user, mockFeatureFlagRequestor, TimeSpan.FromSeconds(30), TimeSpan.Zero, - executor, + BasicTaskExecutor, testLogger ); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 1f669558..bbdcc358 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -481,7 +481,7 @@ public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) private Configuration BaseConfig(Func extraConfig = null) { var builderInternal = Configuration.Builder(_mobileKey) - .Events(new SingleEventProcessorFactory(new MockEventProcessor())); + .Events(new MockEventProcessor().AsSingletonFactory()); builderInternal .Logging(testLogging) .Persistence(Components.NoPersistence); // unless we're specifically testing flag caching, this helps to prevent test state contamination diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs new file mode 100644 index 00000000..baba8499 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs @@ -0,0 +1,274 @@ +using System; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client +{ + public class LdClientDataSourceStatusTests : BaseTest + { + // This is separate from LdClientListenersTest because the client-side .NET SDK has + // more complicated connection-status behavior than the server-side one and we need + // to test more scenarios. For basic scenarios, we can just use TestData to inject + // status changes; for others, we need to use mock components to simulate the + // inputs that can lead to status changes. + + public LdClientDataSourceStatusTests(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void DataSourceStatusProviderReturnsLatestStatus() + { + var testData = TestData.DataSource(); + var config = BasicConfig().DataSource(testData).Build(); + var timeBeforeStarting = DateTime.Now; + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var initialStatus = client.DataSourceStatusProvider.Status; + Assert.Equal(DataSourceState.Valid, initialStatus.State); + Assert.True(initialStatus.StateSince >= timeBeforeStarting); + Assert.Null(initialStatus.LastError); + + var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(401); + testData.UpdateStatus(DataSourceState.Shutdown, errorInfo); + + var newStatus = client.DataSourceStatusProvider.Status; + Assert.Equal(DataSourceState.Shutdown, newStatus.State); + Assert.True(newStatus.StateSince >= errorInfo.Time); + Assert.Equal(errorInfo, newStatus.LastError); + } + } + + [Fact] + public void DataSourceStatusProviderSendsStatusUpdates() + { + var testData = TestData.DataSource(); + var config = BasicConfig().DataSource(testData).Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(401); + testData.UpdateStatus(DataSourceState.Shutdown, errorInfo); + + var newStatus = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Shutdown, newStatus.State); + Assert.True(newStatus.StateSince >= errorInfo.Time); + Assert.Equal(errorInfo, newStatus.LastError); + } + } + + [Fact] + public void DataSourceStatusStartsAsInitializing() + { + var config = BasicConfig() + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var initialStatus = client.DataSourceStatusProvider.Status; + Assert.Equal(DataSourceState.Initializing, initialStatus.State); + Assert.Null(initialStatus.LastError); + } + } + + [Fact] + public void DataSourceStatusRemainsInitializingAfterErrorIfNeverInitialized() + { + var dataSourceFactory = new CapturingDataSourceFactory(); + + var config = BasicConfig() + .DataSource(dataSourceFactory) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(503); + dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Initializing, newStatus1.State); + Assert.Equal(errorInfo, newStatus1.LastError); + } + } + + [Fact] + public void DataSourceStatusIsInterruptedAfterErrorIfAlreadyInitialized() + { + var dataSourceFactory = new CapturingDataSourceFactory(); + + var config = BasicConfig() + .DataSource(dataSourceFactory) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Valid, null); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Valid, newStatus1.State); + + var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(503); + dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + + var newStatus2 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Interrupted, newStatus2.State); + Assert.Equal(errorInfo, newStatus2.LastError); + } + } + + [Fact] + public void DataSourceStatusStartsAsSetOfflineIfConfiguredOffline() + { + var config = BasicConfig().Offline(true).Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var initialStatus = client.DataSourceStatusProvider.Status; + Assert.Equal(DataSourceState.SetOffline, initialStatus.State); + Assert.Null(initialStatus.LastError); + } + } + + [Fact] + public void DataSourceStatusIsRestoredWhenNoLongerSetOffline() + { + var testData = TestData.DataSource(); + var config = BasicConfig().DataSource(testData).Offline(true).Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.SetOffline, TimeSpan.FromSeconds(5))); + + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + client.SetOffline(false, TimeSpan.FromSeconds(1)); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Initializing, newStatus1.State); + + var newStatus2 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Valid, newStatus2.State); + } + } + + [Fact] + public void DataSourceStatusStartsAsNetworkUnavailableIfNetworkIsUnavailable() + { + var connectivity = new MockConnectivityStateManager(false); + var config = BasicConfig().ConnectivityStateManager(connectivity).Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var initialStatus = client.DataSourceStatusProvider.Status; + Assert.Equal(DataSourceState.NetworkUnavailable, initialStatus.State); + Assert.Null(initialStatus.LastError); + } + } + + [Fact] + public void DataSourceStatusIsRestoredWhenNetworkIsAvailableAgain() + { + var testData = TestData.DataSource(); + var connectivity = new MockConnectivityStateManager(false); + var config = BasicConfig() + .DataSource(testData) + .ConnectivityStateManager(connectivity) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.NetworkUnavailable, TimeSpan.FromSeconds(5))); + + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + connectivity.Connect(true); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Initializing, newStatus1.State); + + var newStatus2 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Valid, newStatus2.State); + } + } + + [Fact] + public void SetOfflineStatusOverridesNetworkUnavailableStatus() + { + var testData = TestData.DataSource(); + var connectivity = new MockConnectivityStateManager(false); + var config = BasicConfig() + .DataSource(testData) + .ConnectivityStateManager(connectivity) + .Offline(true) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.SetOffline, TimeSpan.FromSeconds(5))); + + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + client.SetOffline(false, TimeSpan.FromSeconds(1)); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.NetworkUnavailable, newStatus1.State); + + connectivity.Connect(true); + + var newStatus2 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Initializing, newStatus2.State); + + var newStatus3 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Valid, newStatus3.State); + } + } + + [Fact] + public void BackgroundDisabledState() + { + var testData = TestData.DataSource(); + var backgrounder = new MockBackgroundModeManager(); + var config = BasicConfig() + .BackgroundModeManager(backgrounder) + .DataSource(testData) + .EnableBackgroundUpdating(false) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.Valid, TimeSpan.FromSeconds(5))); + + var statuses = new EventSink(); + client.DataSourceStatusProvider.StatusChanged += statuses.Add; + + backgrounder.UpdateBackgroundMode(true); + + var newStatus1 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.BackgroundDisabled, newStatus1.State); + + backgrounder.UpdateBackgroundMode(false); + + var newStatus2 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Initializing, newStatus2.State); + + var newStatus3 = statuses.ExpectValue(); + Assert.Equal(DataSourceState.Valid, newStatus3.State); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 2f4a345f..4664f0d5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -18,7 +18,7 @@ public class LdClientEventTests : BaseTest public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { - _factory = new SingleEventProcessorFactory(eventProcessor); + _factory = eventProcessor.AsSingletonFactory(); } private LdClient MakeClient(User u) => @@ -276,7 +276,7 @@ public void VariationSendsFeatureEventForUnknownFlag() public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.TestConfig() - .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Events(_factory) .Logging(testLogging); @@ -382,7 +382,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { var config = TestUtil.TestConfig() - .DataSource(MockUpdateProcessorThatNeverInitializes.Factory()) + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Events(_factory) .Logging(testLogging); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs index abe28cfe..54b42ca6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientListenersTest.cs @@ -9,22 +9,20 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientListenersTest : BaseTest { + // Tests for data source status listeners are in LdClientDataSourceStatusTests. + public LdClientListenersTest(ITestOutputHelper testOutput) : base(testOutput) { } [Fact] public void ClientSendsFlagValueChangeEvents() { - var user = User.WithKey("user-key"); var testData = TestData.DataSource(); - var config = TestUtil.TestConfig("mobile-key") - .DataSource(testData) - .Logging(testLogging) - .Build(); + var config = BasicConfig().DataSource(testData).Build(); var flagKey = "flagkey"; testData.Update(testData.Flag(flagKey).Variation(true)); - using (var client = TestUtil.CreateClient(config, user)) + using (var client = TestUtil.CreateClient(config, BasicUser)) { var eventSink1 = new EventSink(); var eventSink2 = new EventSink(); @@ -49,5 +47,29 @@ public void ClientSendsFlagValueChangeEvents() eventSink2.ExpectNoValue(); } } + + [Fact] + public void EventSenderIsClientInstance() + { + // We're only checking one kind of events here (FlagValueChanged), but since the SDK uses the + // same TaskExecutor instance for all event dispatches and the sender is configured in + // that object, the sender should be the same for all events. + + var flagKey = "flagKey"; + var testData = TestData.DataSource(); + testData.Update(testData.Flag(flagKey).Variation(true)); + var config = BasicConfig().DataSource(testData).Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var receivedSender = new EventSink(); + client.FlagTracker.FlagValueChanged += (s, e) => receivedSender.Enqueue(s); + + testData.Update(testData.Flag(flagKey).Variation(false)); + + var sender = receivedSender.ExpectValue(); + Assert.Same(client, sender); + } + } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 387b6964..28de97e0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -397,7 +397,7 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() var flags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); storage.Init(simpleUser, DataModelSerialization.SerializeAll(flags)); var config = TestUtil.TestConfig() - .Persistence(new SinglePersistentDataStoreFactory(storage)) + .Persistence(storage.AsSingletonFactory()) .Offline(true) .Logging(testLogging) .Build(); @@ -414,7 +414,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() var initialFlags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); var config = TestUtil.TestConfig() .DataSource(MockPollingProcessor.Factory(TestUtil.MakeJsonData(initialFlags))) - .Persistence(new SinglePersistentDataStoreFactory(storage)) + .Persistence(storage.AsSingletonFactory()) .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) @@ -432,7 +432,7 @@ public void EventProcessorIsOnlineByDefault() { var eventProcessor = new MockEventProcessor(); var config = TestUtil.TestConfig() - .Events(new SingleEventProcessorFactory(eventProcessor)) + .Events(eventProcessor.AsSingletonFactory()) .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) @@ -448,7 +448,7 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() var eventProcessor = new MockEventProcessor(); var config = TestUtil.TestConfig() .ConnectivityStateManager(connectivityStateManager) - .Events(new SingleEventProcessorFactory(eventProcessor)) + .Events(eventProcessor.AsSingletonFactory()) .Offline(true) .Logging(testLogging) .Build(); @@ -478,7 +478,7 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() var eventProcessor = new MockEventProcessor(); var config = TestUtil.TestConfig() .ConnectivityStateManager(connectivityStateManager) - .Events(new SingleEventProcessorFactory(eventProcessor)) + .Events(eventProcessor.AsSingletonFactory()) .Logging(testLogging) .Build(); using (var client = TestUtil.CreateClient(config, simpleUser)) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 1b5686b2..8e06a750 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -13,6 +13,38 @@ namespace LaunchDarkly.Sdk.Client { + internal static class MockComponentExtensions + { + public static IDataSourceFactory AsSingletonFactory(this IDataSource instance) => + new SingleDataSourceFactory { Instance = instance }; + + public static IEventProcessorFactory AsSingletonFactory(this IEventProcessor instance) => + new SingleEventProcessorFactory { Instance = instance }; + + public static IPersistentDataStoreFactory AsSingletonFactory(this IPersistentDataStore instance) => + new SinglePersistentDataStoreFactory { Instance = instance }; + + private class SingleDataSourceFactory : IDataSourceFactory + { + public IDataSource Instance { get; set; } + public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, + User currentUser, bool inBackground) => Instance; + } + + private class SingleEventProcessorFactory : IEventProcessorFactory + { + public IEventProcessor Instance { get; set; } + public IEventProcessor CreateEventProcessor(LdClientContext context) => Instance; + } + + private class SinglePersistentDataStoreFactory : IPersistentDataStoreFactory + { + public IPersistentDataStore Instance { get; set; } + public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => Instance; + } + + } + internal class MockBackgroundModeManager : IBackgroundModeManager { public event EventHandler BackgroundModeChanged; @@ -68,13 +100,22 @@ internal class ReceivedUpsert public User User { get; set; } } + internal class ReceivedStatusUpdate + { + public DataSourceState State { get; set; } + public DataSourceStatus.ErrorInfo? Error { get; set; } + } + public readonly EventSink Actions = new EventSink(); public void Init(User user, FullDataSet data) => - Actions.Add(null, new ReceivedInit { Data = data, User = user }); + Actions.Enqueue(new ReceivedInit { Data = data, User = user }); public void Upsert(User user, string key, ItemDescriptor data) => - Actions.Add(null, new ReceivedUpsert { Key = key, Data = data, User = user }); + Actions.Enqueue(new ReceivedUpsert { Key = key, Data = data, User = user }); + + public void UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) => + Actions.Enqueue(new ReceivedStatusUpdate { State = newState, Error = newError }); public FullDataSet ExpectInit(User user) { @@ -138,18 +179,6 @@ public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) => Events.Add(e); } - internal class SingleEventProcessorFactory : IEventProcessorFactory - { - private readonly IEventProcessor _instance; - - public SingleEventProcessorFactory(IEventProcessor instance) - { - _instance = instance; - } - - public IEventProcessor CreateEventProcessor(LdClientContext context) => _instance; - } - internal class MockFeatureFlagRequestor : IFeatureFlagRequestor { private readonly string _jsonFlags; @@ -186,17 +215,15 @@ public void Init(User user, string allData) } } - internal class SinglePersistentDataStoreFactory : IPersistentDataStoreFactory + internal class CapturingDataSourceFactory : IDataSourceFactory { - private readonly IPersistentDataStore _instance; + internal IDataSourceUpdateSink UpdateSink; - public SinglePersistentDataStoreFactory(IPersistentDataStore instance) + public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, User currentUser, bool inBackground) { - _instance = instance; + UpdateSink = updateSink; + return new MockDataSourceThatNeverInitializes(); } - - public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => - _instance; } internal class MockDataSourceFactoryFromLambda : IDataSourceFactory @@ -212,11 +239,6 @@ public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSi _fn(context, updateSink, currentUser, inBackground); } - internal class SingleDataSourceFactory : MockDataSourceFactoryFromLambda - { - internal SingleDataSourceFactory(IDataSource instance) : base((c, up, u, bg) => instance) { } - } - internal class MockPollingProcessor : IDataSource { private IDataSourceUpdateSink _updateSink; @@ -296,20 +318,25 @@ public Task Start() public void Dispose() { } } - internal class MockUpdateProcessorThatNeverInitializes : IDataSource + internal class MockDataSource : IDataSource { - public static IDataSourceFactory Factory() => - new SingleDataSourceFactory(new MockUpdateProcessorThatNeverInitializes()); + public bool IsRunning => true; + public void Dispose() { } + + public bool Initialized => true; + + public Task Start() => Task.FromResult(true); + } + + internal class MockDataSourceThatNeverInitializes : IDataSource + { public bool IsRunning => false; public void Dispose() { } public bool Initialized => false; - public Task Start() - { - return new TaskCompletionSource().Task; // will never be completed - } + public Task Start() => new TaskCompletionSource().Task; // will never be completed } } From 9abae1868fbe1b987bf7303335a97dda11cdf555 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 28 Sep 2021 17:04:17 -0700 Subject: [PATCH 364/499] comments --- .../Interfaces/IDataSourceUpdateSink.cs | 5 +++++ .../Internal/DataSources/ConnectionManager.cs | 7 +++++++ src/LaunchDarkly.ClientSdk/LdClient.cs | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs index ca3dad3c..74b957dc 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -52,6 +52,11 @@ public interface IDataSourceUpdateSink /// remain at because /// is only meaningful after a successful startup. /// + /// + /// Data source implementations normally should not need to set the state to + /// , because that will happen automatically if they call + /// . + /// /// /// the data source state /// information about a new error, if any diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index e705c804..57454f32 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -247,6 +247,9 @@ private Task OpenOrCloseConnectionIfNecessary() { if (_dataSource == null && _dataSourceConstructor != null) { + // Set the state to Initializing when there's a new data source that has not yet + // started. The state will then be updated as appropriate by the data source either + // calling UpdateStatus, or Init which implies UpdateStatus(Valid). _updateSink.UpdateStatus(DataSourceState.Initializing, null); _dataSource = _dataSourceConstructor(); return _dataSource.Start() @@ -255,6 +258,10 @@ private Task OpenOrCloseConnectionIfNecessary() } else { + // Either we've been explicitly set to be offline (in which case the state is always + // SetOffline regardless of any other conditions), or we're offline because the network + // is unavailable. If either of those things changes, we'll end up calling this method + // again and the state will be updated if appropriate. _dataSource?.Dispose(); _dataSource = null; _initialized = true; diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index b8923932..95d4a8fe 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -703,6 +703,9 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh { _log.Debug("Background updating is disabled"); await _connectionManager.SetDataSourceConstructor(null, false); + // Normally the data source status is updated by ConnectionManager and/or by the + // data source itself, but in this particular case neither of those are involved, + // so we need to explicitly set the state to BackgroundDisabled. _dataSourceUpdateSink.UpdateStatus(DataSourceState.BackgroundDisabled, null); return; } From 532ec492bdbc5f5b3137fbf77c91462eb8a81bf1 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 28 Sep 2021 17:10:34 -0700 Subject: [PATCH 365/499] make build fail if XML comments are missing or invalid --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ea13a542..c3d236bc 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -28,6 +28,9 @@ true snupkg LaunchDarkly.Sdk.Client + + + 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 From 014887e4b848bc4729b2a5c809fc1f9cff6b94e5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 29 Sep 2021 11:36:23 -0700 Subject: [PATCH 366/499] doc comment fixes --- .../Integrations/TestData.cs | 2 +- .../Interfaces/DataSourceStatus.cs | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs index 4f951a88..323ed0ba 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -179,7 +179,7 @@ private void UpdateInternal(string key, FlagBuilder builder) /// /// one of the constants defined by /// an optional instance - /// + /// the same instance public TestData UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) { DataSourceImpl[] instances; diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs index 42195c7e..9a98553c 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs @@ -22,14 +22,20 @@ public struct DataSourceStatus /// /// The meaning of this depends on the current state: /// - /// For , it is the time that the SDK started initializing. - /// For , it is the time that the data source most recently entered a valid - /// state, after previously having been either or - /// . - /// For , it is the time that the data source most recently entered an - /// error state, after previously having been . - /// For , it is the time that the data source encountered an unrecoverable error - /// or that the SDK was explicitly shut down. + /// For , it is the time that the SDK started + /// initializing. + /// For , it is the time that the data source most + /// recently entered a valid state, after previously having been + /// or an invalid state such as . + /// For , it is the time that the data source + /// most recently entered an error state, after previously having been . + /// + /// For , + /// , or , it is + /// the time that the SDK switched off the data source after detecting one of those conditions. + /// + /// For , it is the time that the data source + /// encountered an unrecoverable error or that the SDK was explicitly shut down. /// /// public DateTime StateSince { get; set; } From d100f76439e169ced5f692535976082dc7d97a12 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 30 Sep 2021 12:47:28 -0700 Subject: [PATCH 367/499] doc comment fix --- src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs index 9a98553c..4083a9ff 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs @@ -35,7 +35,7 @@ public struct DataSourceStatus /// the time that the SDK switched off the data source after detecting one of those conditions. /// /// For , it is the time that the data source - /// encountered an unrecoverable error or that the SDK was explicitly shut down. + /// encountered an unrecoverable error or that the SDK was explicitly shut down. /// /// public DateTime StateSince { get; set; } From 6ce62bac58f7dfe6b4e30c8c3f3e4cff8b4990bb Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 14 Oct 2021 11:23:21 -0700 Subject: [PATCH 368/499] set default flush interval to 30 seconds on mobile platforms --- .../Integrations/EventProcessorBuilder.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 9a5bd159..05e293d8 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -36,7 +36,17 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory /// /// The default value for . /// - public static readonly TimeSpan DefaultFlushInterval = TimeSpan.FromSeconds(5); + /// + /// For Android and iOS, this is 30 seconds. For all other platforms, it is 5 seconds. The + /// difference is because the extra HTTP requests for sending events more frequently are + /// undesirable in a mobile application as opposed to a desktop application. + /// + public static readonly TimeSpan DefaultFlushInterval = +#if NETSTANDARD + TimeSpan.FromSeconds(5); +#else + TimeSpan.FromSeconds(30); +#endif internal static readonly Uri DefaultBaseUri = new Uri("https://mobile.launchdarkly.com"); From 74a4a248f960fc373f0ea001526d8f122ac131ee Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 14 Oct 2021 13:57:07 -0700 Subject: [PATCH 369/499] simpler way of configuring service base URIs (#134) --- src/LaunchDarkly.ClientSdk/Components.cs | 27 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 7 +- .../ConfigurationBuilder.cs | 41 ++- .../Integrations/EventProcessorBuilder.cs | 45 +-- .../Integrations/HttpConfigurationBuilder.cs | 7 + .../LoggingConfigurationBuilder.cs | 1 + .../Integrations/PollingDataSourceBuilder.cs | 38 +-- .../Integrations/ServiceEndpointsBuilder.cs | 284 ++++++++++++++++++ .../StreamingDataSourceBuilder.cs | 86 +----- .../Interfaces/LdClientContext.cs | 13 + .../Interfaces/ServiceEndpoints.cs | 29 ++ .../Internal/ComponentsImpl.cs | 3 +- .../Internal/Constants.cs | 8 - .../DataSources/FeatureFlagRequestor.cs | 5 +- .../DataSources/StreamingDataSource.cs | 6 +- .../Internal/StandardEndpoints.cs | 21 ++ .../LaunchDarkly.ClientSdk.csproj | 11 + .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 4 + .../Integrations/EventProcessorBuilderTest.cs | 8 - .../PollingDataSourceBuilderTest.cs | 7 - .../ServiceEndpointsBuilderTest.cs | 82 +++++ .../StreamingDataSourceBuilderTest.cs | 16 - .../LDClientEndToEndTests.cs | 20 +- .../LdClientServiceEndpointsTests.cs | 195 ++++++++++++ 24 files changed, 772 insertions(+), 192 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/ServiceEndpoints.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index ffa3c1f6..c422fc71 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -163,7 +163,7 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => ComponentsImpl.NullPersistentDataStoreFactory.Instance; /// - /// Returns a configurable factory for using polling mode to get feature flag data. + /// Returns a configurable factory for using only polling mode to get feature flag data. /// /// /// @@ -229,6 +229,31 @@ public static PollingDataSourceBuilder PollingDataSource() => /// public static EventProcessorBuilder SendEvents() => new EventProcessorBuilder(); + /// + /// Returns a builder for configuring custom service URIs. + /// + /// + /// + /// Passing this to , + /// after setting any desired properties on the builder, applies this configuration to the SDK. + /// + /// + /// Most applications will never need to use this method. The main use case is when connecting + /// to a LaunchDarkly + /// Relay Proxy instance. For more information, see . + /// + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://my-relay-hostname:80")) + /// .Build(); + /// + /// + /// a configuration builder + /// + public static ServiceEndpointsBuilder ServiceEndpoints() => new ServiceEndpointsBuilder(); + /// /// Returns a configurable factory for using streaming mode to get feature flag data. /// diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 8199533e..4d338c75 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -72,7 +72,6 @@ public sealed class Configuration /// public bool EvaluationReasons { get; } - /// /// A factory object that creates an implementation of , responsible /// for sending analytics events. @@ -108,6 +107,11 @@ public sealed class Configuration /// public IPersistentDataStoreFactory PersistentDataStoreFactory { get; } + /// + /// Defines the base service URIs used by SDK components. + /// + public ServiceEndpoints ServiceEndpoints { get; } + /// /// Creates a configuration with all parameters set to the default. /// @@ -168,6 +172,7 @@ internal Configuration(ConfigurationBuilder builder) MobileKey = builder._mobileKey; Offline = builder._offline; PersistentDataStoreFactory = builder._persistentDataStoreFactory; + ServiceEndpoints = (builder._serviceEndpointsBuilder ?? Components.ServiceEndpoints()).Build(); BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 1f7c042e..ca32daf9 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -43,6 +43,7 @@ public sealed class ConfigurationBuilder internal string _mobileKey; internal bool _offline = false; internal IPersistentDataStoreFactory _persistentDataStoreFactory = null; + internal ServiceEndpointsBuilder _serviceEndpointsBuilder = null; // Internal properties only settable for testing internal IBackgroundModeManager _backgroundModeManager; @@ -66,6 +67,7 @@ internal ConfigurationBuilder(Configuration copyFrom) _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; _persistentDataStoreFactory = copyFrom.PersistentDataStoreFactory; + _serviceEndpointsBuilder = new ServiceEndpointsBuilder(copyFrom.ServiceEndpoints); } /// @@ -112,6 +114,11 @@ public ConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) /// . See those methods for details on how /// to configure them. /// + /// + /// This overwrites any previous options set with . + /// If you want to set multiple options, set them on the same + /// or . + /// /// /// the factory object /// the same builder @@ -163,9 +170,15 @@ public ConfigurationBuilder EvaluationReasons(bool evaluationReasons) /// Sets the implementation of the component that processes analytics events. /// /// + /// /// The default is , but you may choose to set it to a customized /// , a custom implementation (for instance, a test fixture), or /// disable events with . + /// + /// + /// This overwrites any previous options set with . + /// If you want to set multiple options, set them on the same . + /// /// /// a builder/factory object for event configuration /// the same builder @@ -180,6 +193,10 @@ public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) /// . The builder has methods for setting /// individual HTTP-related properties. /// + /// + /// This overwrites any previous options set with . + /// If you want to set multiple options, set them on the same . + /// /// a builder for HTTP configuration /// the same builder public ConfigurationBuilder Http(HttpConfigurationBuilder httpConfigurationBuilder) @@ -212,7 +229,7 @@ public ConfigurationBuilder Logging(ILogAdapter logAdapter) => Logging(Components.Logging(logAdapter)); /// - /// Sets the SDK's logging configuration, using a configuration builder obtained from. + /// Sets the SDK's logging configuration, using a configuration builder obtained from /// . /// /// @@ -225,6 +242,10 @@ public ConfigurationBuilder Logging(ILogAdapter logAdapter) => /// For more about how logging works in the SDK, see the LaunchDarkly /// feature guide. /// + /// + /// This overwrites any previous options set with . + /// If you want to set multiple options, set them on the same . + /// /// /// /// var config = Configuration.Builder("my-sdk-key") @@ -292,6 +313,24 @@ public ConfigurationBuilder Persistence(IPersistentDataStoreFactory persistentDa return this; } + /// + /// Sets the SDK's service URIs, using a configuration builder obtained from + /// . + /// + /// + /// This overwrites any previous options set with . + /// If you want to set multiple options, set them on the same . + /// + /// the subconfiguration builder object + /// the main configuration builder + /// + /// + public ConfigurationBuilder ServiceEndpoints(ServiceEndpointsBuilder serviceEndpointsBuilder) + { + _serviceEndpointsBuilder = serviceEndpointsBuilder; + return this; + } + // The following properties are internal and settable only for testing. internal ConfigurationBuilder BackgroundModeManager(IBackgroundModeManager backgroundModeManager) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 05e293d8..4de4669d 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -48,10 +48,7 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory TimeSpan.FromSeconds(30); #endif - internal static readonly Uri DefaultBaseUri = new Uri("https://mobile.launchdarkly.com"); - internal bool _allAttributesPrivate = false; - internal Uri _baseUri = null; internal int _capacity = DefaultCapacity; internal TimeSpan _flushInterval = DefaultFlushInterval; internal bool _inlineUsersInEvents = false; @@ -74,33 +71,6 @@ public EventProcessorBuilder AllAttributesPrivate(bool allAttributesPrivate) return this; } - /// - /// Sets a custom base URI for the events service. - /// - /// - /// You will only need to change this value in the following cases: - /// - /// - /// - /// You are using the Relay Proxy. - /// Set BaseUri to the base URI of the Relay Proxy instance. - /// - /// - /// - /// - /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. - /// - /// - /// - /// - /// the base URI of the events service; null to use the default - /// the builder - public EventProcessorBuilder BaseUri(Uri baseUri) - { - _baseUri = baseUri; - return this; - } - /// /// Sets the capacity of the events buffer. /// @@ -146,6 +116,12 @@ public EventProcessorBuilder FlushInterval(TimeSpan flushInterval) return this; } + internal EventProcessorBuilder FlushIntervalNoMinimum(TimeSpan flushInterval) + { + _flushInterval = flushInterval; + return this; + } + /// /// Sets whether to include full user details in every analytics event. /// @@ -210,13 +186,18 @@ public EventProcessorBuilder PrivateAttributeNames(params string[] attributes) /// public IEventProcessor CreateEventProcessor(LdClientContext context) { - var uri = _baseUri ?? DefaultBaseUri; + var baseUri = ServiceEndpointsBuilder.SelectBaseUri( + context.ServiceEndpoints.EventsBaseUri, + StandardEndpoints.DefaultEventsBaseUri, + "Events", + context.BaseLogger + ); var eventsConfig = new EventsConfiguration { AllAttributesPrivate = _allAttributesPrivate, EventCapacity = _capacity, EventFlushInterval = _flushInterval, - EventsUri = uri.AddPath(Constants.EVENTS_PATH), + EventsUri = baseUri.AddPath(StandardEndpoints.AnalyticsEventsPostRequestPath), //DiagnosticUri = uri.AddPath("diagnostic"), // no diagnostic events yet InlineUsersInEvents = _inlineUsersInEvents, PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index f791bbe8..9630fdb9 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -127,8 +127,15 @@ public HttpConfigurationBuilder MessageHandler(HttpMessageHandler messageHandler /// Sets an HTTP proxy for making connections to LaunchDarkly. /// /// + /// /// This is ignored if you have specified a custom message handler with , /// since proxy behavior is implemented by the message handler. + /// + /// + /// Note that this is not the same as the LaunchDarkly + /// Relay Proxy, which would be set with + /// . + /// /// /// /// diff --git a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs index 910eca9a..241ea6e6 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs @@ -99,6 +99,7 @@ public LoggingConfigurationBuilder() { } /// a subsystem of LD and a category of DataSource. /// /// + /// the same builder public LoggingConfigurationBuilder BaseLoggerName(string baseLoggerName) { _baseLoggerName = baseLoggerName; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 093f9291..092d0f94 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -34,15 +34,12 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// public sealed class PollingDataSourceBuilder : IDataSourceFactory { - internal static readonly Uri DefaultBaseUri = new Uri("https://clientsdk.launchdarkly.com"); - /// /// The default value for : 5 minutes. /// public static readonly TimeSpan DefaultPollInterval = TimeSpan.FromMinutes(5); internal TimeSpan _backgroundPollInterval = Configuration.DefaultBackgroundPollInterval; - internal Uri _baseUri = null; internal TimeSpan _pollInterval = DefaultPollInterval; /// @@ -62,33 +59,6 @@ public PollingDataSourceBuilder BackgroundPollInterval(TimeSpan backgroundPollIn return this; } - /// - /// Sets a custom base URI for the polling service. - /// - /// - /// You will only need to change this value in the following cases: - /// - /// - /// - /// You are using the Relay Proxy. - /// Set BaseUri to the base URI of the Relay Proxy instance. - /// - /// - /// - /// - /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. - /// - /// - /// - /// - /// the base URI of the polling service; null to use the default - /// the builder - public PollingDataSourceBuilder BaseUri(Uri baseUri) - { - _baseUri = baseUri ?? DefaultBaseUri; - return this; - } - /// /// Sets the interval at which the SDK will poll for feature flag updates. /// @@ -125,10 +95,16 @@ bool inBackground { context.BaseLogger.Warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); } + var baseUri = ServiceEndpointsBuilder.SelectBaseUri( + context.ServiceEndpoints.PollingBaseUri, + StandardEndpoints.DefaultPollingBaseUri, + "Polling", + context.BaseLogger + ); var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); var requestor = new FeatureFlagRequestor( - _baseUri ?? DefaultBaseUri, + baseUri, currentUser, context.EvaluationReasons, context.Http, diff --git a/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs new file mode 100644 index 00000000..374e65ce --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs @@ -0,0 +1,284 @@ +using System; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring the SDK's service URIs. + /// + /// + /// + /// If you want to set non-default values for any of these properties, create a builder with + /// , change its properties with the methods of this class, and pass it + /// to . + /// + /// + /// The default behavior, if you do not change any of these properties, is that the SDK will connect + /// to the standard endpoints in the LaunchDarkly production service. There are several use cases for + /// changing these properties: + /// + /// + /// + /// You are using the LaunchDarkly + /// Relay Proxy. In this case, set to the base URI of the Relay Proxy + /// instance. Note that this is not the same as a regular HTTP proxy, which would be set with + /// . + /// + /// + /// You are connecting to a private instance of LaunchDarkly, rather than the standard production + /// services. In this case, there will be custom base URIs for each service, so you must set + /// , , and . + /// + /// + /// You are connecting to a test fixture that simulates the service endpoints. In this case, you + /// may set the base URIs to whatever you want, although the SDK will still set the URI paths to + /// the expected paths for LaunchDarkly services. + /// + /// + /// + /// Each of the setter methods can be called with either a or an equivalent + /// string. Passing a string that is not a valid URI will cause an immediate + /// + /// + /// + /// If you are using a private instance and you set some of the base URIs, but not all of them, + /// the SDK will log an error and may not work properly. The only exception is if you have explicitly + /// disabled the SDK's use of one of the services: for instance, if you have disabled analytics + /// events with , you do not have to set . + /// + /// + /// + /// + /// // Example of specifying a Relay Proxy instance + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints() + /// .RelayProxy("http://my-relay-hostname:8080")) + /// .Build(); + /// + /// // Example of specifying a private LaunchDarkly instance + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints() + /// .Streaming("https://stream.mycompany.launchdarkly.com") + /// .Polling("https://app.mycompany.launchdarkly.com") + /// .Events("https://events.mycompany.launchdarkly.com")) + /// .Build(); + /// + /// + public class ServiceEndpointsBuilder + { + private Uri _streamingBaseUri = null; + private Uri _pollingBaseUri = null; + private Uri _eventsBaseUri = null; + + internal ServiceEndpointsBuilder() { } + + internal ServiceEndpointsBuilder(ServiceEndpoints copyFrom) + { + _streamingBaseUri = copyFrom.StreamingBaseUri; + _pollingBaseUri = copyFrom.PollingBaseUri; + _eventsBaseUri = copyFrom.EventsBaseUri; + } + + /// + /// Sets a custom base URI for the events service. + /// + /// + /// You should only call this method if you are using a private instance or a test fixture + /// (see ). If you are using the LaunchDarkly Relay Proxy, + /// call instead. + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints() + /// .Streaming("https://stream.mycompany.launchdarkly.com") + /// .Polling("https://app.mycompany.launchdarkly.com") + /// .Events("https://events.mycompany.launchdarkly.com")) + /// .Build(); + /// + /// the base URI of the events service; null to use the default + /// the builder + /// + public ServiceEndpointsBuilder Events(Uri eventsBaseUri) + { + _eventsBaseUri = eventsBaseUri; + return this; + } + + /// + /// Equivalent to , specifying the URI as a string. + /// + /// the base URI of the events service, or + /// to reset to the default + /// the same builder + /// if the string is not null and is not a valid URI + /// + public ServiceEndpointsBuilder Events(string eventsBaseUri) => + Events(new Uri(eventsBaseUri)); + + /// + /// Sets a custom base URI for the polling service. + /// + /// + /// You should only call this method if you are using a private instance or a test fixture + /// (see ). If you are using the LaunchDarkly Relay Proxy, + /// call instead. + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints() + /// .Streaming("https://stream.mycompany.launchdarkly.com") + /// .Polling("https://app.mycompany.launchdarkly.com") + /// .Events("https://events.mycompany.launchdarkly.com")) + /// .Build(); + /// + /// the base URI of the polling service; null to use the default + /// the builder + /// + public ServiceEndpointsBuilder Polling(Uri pollingBaseUri) + { + _pollingBaseUri = pollingBaseUri; + return this; + } + + /// + /// Equivalent to , specifying the URI as a string. + /// + /// the base URI of the polling service, or + /// to reset to the default + /// the same builder + /// if the string is not null and is not a valid URI + /// + public ServiceEndpointsBuilder Polling(string pollingBaseUri) => + Polling(new Uri(pollingBaseUri)); + + /// + /// Specifies a single base URI for a Relay Proxy instance. + /// + /// + /// + /// When using the LaunchDarkly Relay Proxy, + /// the SDK only needs to know the single base URI of the Relay Proxy, which will provide all of the + /// proxied service endpoints. + /// + /// + /// Note that this is not the same as a regular HTTP proxy, which would be set with + /// . + /// + /// + /// + /// + /// var relayUri = new Uri("http://my-relay-hostname:8080"); + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints().RelayProxy(relayUri)) + /// .Build(); + /// + /// + /// the Relay Proxy base URI, or + /// to reset to default endpoints + /// the builder + /// + public ServiceEndpointsBuilder RelayProxy(Uri relayProxyBaseUri) + { + _streamingBaseUri = relayProxyBaseUri; + _pollingBaseUri = relayProxyBaseUri; + _eventsBaseUri = relayProxyBaseUri; + return this; + } + + /// + /// Equivalent to , specifying the URI as a string. + /// + /// the Relay Proxy base URI, or + /// to reset to default endpoints + /// the same builder + /// if the string is not null and is not a valid URI + /// + public ServiceEndpointsBuilder RelayProxy(string relayProxyBaseUri) => + RelayProxy(new Uri(relayProxyBaseUri)); + + /// + /// Sets a custom base URI for the streaming service. + /// + /// + /// + /// You should only call this method if you are using a private instance or a test fixture + /// (see ). If you are using the LaunchDarkly Relay Proxy, + /// call instead. + /// + /// + /// If you set a custom Streaming URI, you must also set a custom Polling URI. + /// Even when in streaming mode, the SDK may sometimes need to make a request to the polling + /// service. + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .ServiceEndpoints(Components.ServiceEndpoints() + /// .Streaming("https://stream.mycompany.launchdarkly.com") + /// .Polling("https://app.mycompany.launchdarkly.com") + /// .Events("https://events.mycompany.launchdarkly.com")) + /// .Build(); + /// + /// the base URI of the streaming service; null to use the default + /// the builder + /// + public ServiceEndpointsBuilder Streaming(Uri streamingBaseUri) + { + _streamingBaseUri = streamingBaseUri; + return this; + } + + /// + /// Equivalent to , specifying the URI as a string. + /// + /// the base URI of the streaming service, or + /// to reset to the default + /// the same builder + /// if the string is not null and is not a valid URI + /// + public ServiceEndpointsBuilder Streaming(string streamingBaseUri) => + Streaming(new Uri(streamingBaseUri)); + + /// + /// Called internally by the SDK to create a configuration instance. Applications do not need + /// to call this method. + /// + /// the configuration object + public ServiceEndpoints Build() + { + // The logic here is based on the assumption that if *any* custom URIs have been set, + // then we do not want to use default values for any that were not set, so we will leave + // those null. That way, if we decide later on (in other component factories, such as + // EventProcessorBuilder) that we are actually interested in one of these values, and we + // see that it is null, we can assume that there was a configuration mistake and log an + // error. + if (_streamingBaseUri is null && _pollingBaseUri is null && _eventsBaseUri is null) + { + return new ServiceEndpoints( + StandardEndpoints.DefaultStreamingBaseUri, + StandardEndpoints.DefaultPollingBaseUri, + StandardEndpoints.DefaultEventsBaseUri + ); + } + return new ServiceEndpoints(_streamingBaseUri, _pollingBaseUri, _eventsBaseUri); + } + + internal static Uri SelectBaseUri( + Uri serviceEndpointsValue, + Uri defaultValue, + string description, + Logger logger + ) + { + if (serviceEndpointsValue != null) + { + return serviceEndpointsValue; + } + logger.Error("You have set custom ServiceEndpoints without specifying the {0} base URI; connections may not work properly", + description); + return defaultValue; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index de29c60e..ccb68385 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -30,17 +30,13 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// public sealed class StreamingDataSourceBuilder : IDataSourceFactory { - internal static readonly Uri DefaultBaseUri = new Uri("https://clientstream.launchdarkly.com"); - /// /// The default value for : 1000 milliseconds. /// public static readonly TimeSpan DefaultInitialReconnectDelay = TimeSpan.FromSeconds(1); internal TimeSpan _backgroundPollInterval = Configuration.DefaultBackgroundPollInterval; - internal Uri _baseUri = null; internal TimeSpan _initialReconnectDelay = DefaultInitialReconnectDelay; - internal Uri _pollingBaseUri = null; internal StreamingDataSource.EventSourceCreator _eventSourceCreator = null; // used only in testing @@ -67,33 +63,6 @@ internal StreamingDataSourceBuilder BackgroundPollingIntervalWithoutMinimum(Time return this; } - /// - /// Sets a custom base URI for the streaming service. - /// - /// - /// You will only need to change this value in the following cases: - /// - /// - /// - /// You are using the Relay Proxy. - /// Set BaseUri to the base URI of the Relay Proxy instance. - /// - /// - /// - /// - /// You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service. - /// - /// - /// - /// - /// the base URI of the streaming service; null to use the default - /// the builder - public StreamingDataSourceBuilder BaseUri(Uri baseUri) - { - _baseUri = baseUri; - return this; - } - /// /// Sets the initial reconnect delay for the streaming connection. /// @@ -115,34 +84,6 @@ public StreamingDataSourceBuilder InitialReconnectDelay(TimeSpan initialReconnec return this; } - /// - /// Sets a custom base URI for the polling service. - /// - /// - /// - /// The SDK may need the polling service even if you are using streaming mode, under certain - /// circumstances: if the application has been put in the background on a mobile device, or if - /// the streaming endpoint cannot deliver an update itself and instead tells the SDK to re-poll - /// to get the update. - /// - /// - /// You will only need to change this value if you are connecting to a test server or a - /// nonstandard endpoint for the LaunchDarkly service. - /// - /// - /// If you are using the Relay Proxy. - /// you only need to set ; it will automatically default to using the - /// same value for PollingBaseUri. - /// - /// - /// the base URI of the polling service; null to use the default - /// the builder - public StreamingDataSourceBuilder PollingBaseUri(Uri pollingBaseUri) - { - _pollingBaseUri = pollingBaseUri; - return this; - } - internal StreamingDataSourceBuilder EventSourceCreator(StreamingDataSource.EventSourceCreator fn) { _eventSourceCreator = fn; @@ -157,26 +98,23 @@ public IDataSource CreateDataSource( bool inBackground ) { - var baseUri = _baseUri ?? DefaultBaseUri; - Uri pollingBaseUri; - if (_pollingBaseUri is null) - { - // If they specified a nonstandard BaseUri but did *not* specify PollingBaseUri, - // we assume it's a Relay Proxy instance and we set both to the same. - pollingBaseUri = (_baseUri is null || _baseUri == DefaultBaseUri) ? - PollingDataSourceBuilder.DefaultBaseUri : - _baseUri; - } - else - { - pollingBaseUri = _pollingBaseUri; - } + var baseUri = ServiceEndpointsBuilder.SelectBaseUri( + context.ServiceEndpoints.StreamingBaseUri, + StandardEndpoints.DefaultStreamingBaseUri, + "Streaming", + context.BaseLogger + ); + var pollingBaseUri = ServiceEndpointsBuilder.SelectBaseUri( + context.ServiceEndpoints.PollingBaseUri, + StandardEndpoints.DefaultPollingBaseUri, + "Polling", + context.BaseLogger + ); if (inBackground) { // When in the background, always use polling instead of streaming return new PollingDataSourceBuilder() - .BaseUri(pollingBaseUri) .BackgroundPollInterval(_backgroundPollInterval) .CreateDataSource(context, updateSink, currentUser, true); } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index c927fa00..df594877 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -25,6 +25,11 @@ public sealed class LdClientContext /// public Logger BaseLogger { get; } + /// + /// Whether to enable feature flag updates when the application is running in the background. + /// + public bool EnableBackgroundUpdating { get; } + /// /// True if evaluation reasons are enabled. /// @@ -35,6 +40,11 @@ public sealed class LdClientContext /// public HttpConfiguration Http { get; } + /// + /// Defines the base service URIs used by SDK components. + /// + public ServiceEndpoints ServiceEndpoints { get; } + internal TaskExecutor TaskExecutor { get; } /// @@ -57,10 +67,13 @@ object eventSender var logAdapter = logConfig.LogAdapter ?? Logs.None; this.BaseLogger = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); + this.EnableBackgroundUpdating = configuration.EnableBackgroundUpdating; this.EvaluationReasons = configuration.EvaluationReasons; this.Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) .CreateHttpConfiguration(this.Basic); + this.ServiceEndpoints = configuration.ServiceEndpoints; + this.TaskExecutor = new TaskExecutor(eventSender, this.BaseLogger); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ServiceEndpoints.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ServiceEndpoints.cs new file mode 100644 index 00000000..bdec1098 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ServiceEndpoints.cs @@ -0,0 +1,29 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Specifies the base service URIs used by SDK components. + /// + /// + /// This class's properties are not public, since they are only read by the SDK. + /// + /// + public sealed class ServiceEndpoints + { + internal Uri StreamingBaseUri { get; } + internal Uri PollingBaseUri { get; } + internal Uri EventsBaseUri { get; } + + internal ServiceEndpoints( + Uri streamingBaseUri, + Uri pollingBaseUri, + Uri eventsBaseUri + ) + { + StreamingBaseUri = streamingBaseUri; + PollingBaseUri = pollingBaseUri; + EventsBaseUri = eventsBaseUri; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index b5d6006c..b441808a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal diff --git a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs index 72952e16..2a142f93 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs @@ -6,20 +6,12 @@ internal static class Constants public const string KEY = "key"; public const string VERSION = "version"; public const string FLAGS_KEY_PREFIX = "flags:"; - public const string API_KEY = "api_key"; public const string CONTENT_TYPE = "Content-Type"; public const string APPLICATION_JSON = "application/json"; - public const string ACCEPT = "Accept"; - public const string GET = "GET"; - public const string REPORT = "REPORT"; - public const string FLAG_REQUEST_PATH_GET = "msdk/evalx/users/"; - public const string FLAG_REQUEST_PATH_REPORT = "msdk/evalx/user"; - public const string STREAM_REQUEST_PATH = "/meval/"; public const string PUT = "put"; public const string PATCH = "patch"; public const string DELETE = "delete"; public const string PING = "ping"; - public const string EVENTS_PATH = "mobile/events/bulk"; public const string UNIQUE_ID_KEY = "unique_id_key"; } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index a0e8ff86..4319df97 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -69,13 +69,14 @@ public async Task FeatureFlagsAsync() private HttpRequestMessage GetRequestMessage() { - var path = Constants.FLAG_REQUEST_PATH_GET + Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_currentUser)); + var path = StandardEndpoints.PollingRequestGetRequestPath( + Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_currentUser))); return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } private HttpRequestMessage ReportRequestMessage() { - var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(Constants.FLAG_REQUEST_PATH_REPORT)); + var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(StandardEndpoints.PollingRequestReportRequestPath)); request.Content = new StringContent(DataModelSerialization.SerializeUser(_currentUser), Encoding.UTF8, Constants.APPLICATION_JSON); return request; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index 2489bac4..e4bd0adf 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -80,7 +80,7 @@ public Task Start() _eventSource = _eventSourceCreator( _httpProperties, ReportMethod, - MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH), + MakeRequestUriWithPath(StandardEndpoints.StreamingReportRequestPath), DataModelSerialization.SerializeUser(_user) ); } @@ -89,8 +89,8 @@ public Task Start() _eventSource = _eventSourceCreator( _httpProperties, HttpMethod.Get, - MakeRequestUriWithPath(Constants.STREAM_REQUEST_PATH + - Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_user))), + MakeRequestUriWithPath(StandardEndpoints.StreamingGetRequestPath( + Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_user)))), null ); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs new file mode 100644 index 00000000..47c14762 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs @@ -0,0 +1,21 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal static class StandardEndpoints + { + internal static Uri DefaultStreamingBaseUri = new Uri("https://clientstream.launchdarkly.com"); + internal static Uri DefaultPollingBaseUri = new Uri("https://clientsdk.launchdarkly.com"); + internal static Uri DefaultEventsBaseUri = new Uri("https://mobile.launchdarkly.com"); + + internal static string StreamingGetRequestPath(string userDataBase64) => + "/meval/" + userDataBase64; + internal const string StreamingReportRequestPath = "/meval"; + + internal static string PollingRequestGetRequestPath(string userDataBase64) => + "msdk/evalx/users/" + userDataBase64; + internal const string PollingRequestReportRequestPath = "msdk/evalx/user"; + + internal const string AnalyticsEventsPostRequestPath = "mobile/events/bulk"; + } +} diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index c3d236bc..f1b2ac27 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -73,4 +73,15 @@ + + + + + + + + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 70fa13d6..abf9f436 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -43,6 +43,10 @@ public void Dispose() TestUtil.ClearClient(); } + // Returns a ConfigurationBuilder with no external data source, events disabled, and logging redirected + // to the test output. Using this as a base configuration for tests, and then overriding properties as + // needed, protects against accidental interaction with external services and also makes it easier to + // see which properties are important in a test. protected ConfigurationBuilder BasicConfig() => Configuration.Builder(BasicMobileKey) .DataSource(new MockDataSource().AsSingletonFactory()) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs index 985c691d..20bcf656 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs @@ -31,14 +31,6 @@ public void EventCapacity() prop.AssertSetIsChangedTo(-1, EventProcessorBuilder.DefaultCapacity); } - [Fact] - public void EventsUri() - { - var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); - prop.AssertDefault(null); - prop.AssertCanSet(new Uri("http://x")); - } - [Fact] public void FlushInterval() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs index ea757975..d5f59670 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/PollingDataSourceBuilderTest.cs @@ -18,13 +18,6 @@ public void BackgroundPollInterval() prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(222), Configuration.MinimumBackgroundPollInterval); } - [Fact] - public void BaseUri() - { - var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); - prop.AssertDefault(null); - prop.AssertCanSet(new Uri("http://x")); - } [Fact] public void PollInterval() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs new file mode 100644 index 00000000..6afb6676 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs @@ -0,0 +1,82 @@ +using System; +using LaunchDarkly.Sdk.Client.Internal; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + public class ServiceEndpointsBuilderTest + { + [Fact] + public void UsesAllDefaultUrisIfNoneAreOverridden() + { + var se = Components.ServiceEndpoints().Build(); + Assert.Equal(StandardEndpoints.DefaultEventsBaseUri, se.EventsBaseUri); + Assert.Equal(StandardEndpoints.DefaultPollingBaseUri, se.PollingBaseUri); + Assert.Equal(StandardEndpoints.DefaultStreamingBaseUri, se.StreamingBaseUri); + } + + [Fact] + public void CanSetAllUrisToCustomValues() + { + var eu = new Uri("http://my-events"); + var pu = new Uri("http://my-polling"); + var su = new Uri("http://my-streaming"); + var se = Components.ServiceEndpoints().Events(eu).Polling(pu).Streaming(su).Build(); + Assert.Equal(eu, se.EventsBaseUri); + Assert.Equal(pu, se.PollingBaseUri); + Assert.Equal(su, se.StreamingBaseUri); + } + + [Fact] + public void IfCustomUrisAreSetAnyUnsetOnesDefaultToNull() + { + // See ServiceEndpointsBuilder.Build() for the rationale here + var eu = new Uri("http://my-events"); + var pu = new Uri("http://my-polling"); + var su = new Uri("http://my-streaming"); + + var se1 = Components.ServiceEndpoints().Events(eu).Build(); + Assert.Equal(eu, se1.EventsBaseUri); + Assert.Null(se1.PollingBaseUri); + Assert.Null(se1.StreamingBaseUri); + + var se2 = Components.ServiceEndpoints().Polling(pu).Build(); + Assert.Null(se2.EventsBaseUri); + Assert.Equal(pu, se2.PollingBaseUri); + Assert.Null(se2.StreamingBaseUri); + + var se3 = Components.ServiceEndpoints().Streaming(su).Build(); + Assert.Null(se3.EventsBaseUri); + Assert.Null(se3.PollingBaseUri); + Assert.Equal(su, se3.StreamingBaseUri); + } + + [Fact] + public void SettingRelayProxyUriSetsAllUris() + { + var ru = new Uri("http://my-relay"); + var se = Components.ServiceEndpoints().RelayProxy(ru).Build(); + Assert.Equal(ru, se.EventsBaseUri); + Assert.Equal(ru, se.PollingBaseUri); + Assert.Equal(ru, se.StreamingBaseUri); + } + + [Fact] + public void StringSettersAreEquivalentToUriSetters() + { + var eu = "http://my-events"; + var pu = "http://my-polling"; + var su = "http://my-streaming"; + var se1 = Components.ServiceEndpoints().Events(eu).Polling(pu).Streaming(su).Build(); + Assert.Equal(new Uri(eu), se1.EventsBaseUri); + Assert.Equal(new Uri(pu), se1.PollingBaseUri); + Assert.Equal(new Uri(su), se1.StreamingBaseUri); + + var ru = "http://my-relay"; + var se2 = Components.ServiceEndpoints().RelayProxy(ru).Build(); + Assert.Equal(new Uri(ru), se2.EventsBaseUri); + Assert.Equal(new Uri(ru), se2.PollingBaseUri); + Assert.Equal(new Uri(ru), se2.StreamingBaseUri); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs index 3b07f67e..f2a9a748 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/StreamingDataSourceBuilderTest.cs @@ -18,14 +18,6 @@ public void BackgroundPollInterval() prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(222), Configuration.MinimumBackgroundPollInterval); } - [Fact] - public void BaseUri() - { - var prop = _tester.Property(b => b._baseUri, (b, v) => b.BaseUri(v)); - prop.AssertDefault(null); - prop.AssertCanSet(new Uri("http://x")); - } - [Fact] public void InitialReconnectDelay() { @@ -33,13 +25,5 @@ public void InitialReconnectDelay() prop.AssertDefault(StreamingDataSourceBuilder.DefaultInitialReconnectDelay); prop.AssertCanSet(TimeSpan.FromMilliseconds(222)); } - - [Fact] - public void PollingBaseUri() - { - var prop = _tester.Property(b => b._pollingBaseUri, (b, v) => b.PollingBaseUri(v)); - prop.AssertDefault(null); - prop.AssertCanSet(new Uri("http://x")); - } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index bbdcc358..93086deb 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -81,7 +81,8 @@ public void StreamingInitMakesPollRequestIfStreamSendsPing() using (var pollServer = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { var config = BaseConfig(b => - b.DataSource(Components.StreamingDataSource().BaseUri(streamServer.Uri).PollingBaseUri(pollServer.Uri))); + b.DataSource(Components.StreamingDataSource()) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(streamServer.Uri).Polling(pollServer.Uri))); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromSeconds(5))) { VerifyRequest(streamServer.Recorder, UpdateMode.Streaming); @@ -98,7 +99,9 @@ public void InitCanTimeOutSync() var handler = Handlers.Delay(TimeSpan.FromSeconds(2)).Then(SetupResponse(_flagData1, UpdateMode.Polling)); using (var server = HttpServer.Start(handler)) { - var config = BaseConfig(builder => builder.DataSource(Components.PollingDataSource().BaseUri(server.Uri))); + var config = BaseConfig(builder => + builder.DataSource(Components.PollingDataSource()) + .ServiceEndpoints(Components.ServiceEndpoints().Polling(server.Uri))); using (var client = TestUtil.CreateClient(config, _user, TimeSpan.FromMilliseconds(200))) { Assert.False(client.Initialized); @@ -266,8 +269,8 @@ string expectedPath { var config = Configuration.Builder(_mobileKey) .DataSource(MockPollingProcessor.Factory("{}")) - .Events(Components.SendEvents().BaseUri(new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath))) .Persistence(Components.NoPersistence) + .ServiceEndpoints(Components.ServiceEndpoints().Events(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .Build(); using (var client = TestUtil.CreateClient(config, _user)) @@ -357,7 +360,8 @@ public async Task BackgroundModeForcesPollingAsync() var config = BaseConfig(builder => builder .BackgroundModeManager(mockBackgroundModeManager) - .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollingIntervalWithoutMinimum(backgroundInterval)) + .DataSource(Components.StreamingDataSource().BackgroundPollingIntervalWithoutMinimum(backgroundInterval)) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(server.Uri).Polling(server.Uri)) ); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -405,7 +409,8 @@ public async Task BackgroundModePollingCanBeDisabledAsync() var config = BaseConfig(builder => builder .BackgroundModeManager(mockBackgroundModeManager) .EnableBackgroundUpdating(false) - .DataSource(Components.StreamingDataSource().BaseUri(server.Uri).BackgroundPollInterval(backgroundInterval)) + .DataSource(Components.StreamingDataSource().BackgroundPollInterval(backgroundInterval)) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(server.Uri).Polling(server.Uri)) ); using (var client = await TestUtil.CreateClientAsync(config, _user)) @@ -493,13 +498,14 @@ private Configuration BaseConfig(Uri serverUri, UpdateMode mode, Func { + builder.ServiceEndpoints(Components.ServiceEndpoints().Streaming(serverUri).Polling(serverUri)); if (mode.IsStreaming) { - builder.DataSource(Components.StreamingDataSource().BaseUri(serverUri)); + builder.DataSource(Components.StreamingDataSource()); } else { - builder.DataSource(Components.PollingDataSource().BaseUri(serverUri)); + builder.DataSource(Components.PollingDataSource()); } return extraConfig == null ? builder : extraConfig(builder); }); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs new file mode 100644 index 00000000..4be00a67 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client +{ + public class LdClientServiceEndpointsTests : BaseTest + { + // These tests verify that the SDK is using the expected base URIs in various configurations. + // Since we need to be able to intercept requests that would normally go to the production service + // endpoints, and we don't care about simulating realistic responses, we'll just use a simple + // HttpMessageHandler stub. + + private static readonly Uri CustomUri = new Uri("http://custom"); + + private SimpleRecordingHttpMessageHandler _stubHandler = new SimpleRecordingHttpMessageHandler(401); + + public LdClientServiceEndpointsTests(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void DefaultStreamingDataSourceBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.StreamingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(StandardEndpoints.DefaultStreamingBaseUri, BaseUriOf(req.RequestUri)); + } + } + + [Fact] + public void DefaultPollingDataSourceBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.PollingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(StandardEndpoints.DefaultPollingBaseUri, BaseUriOf(req.RequestUri)); + } + } + + [Fact] + public void DefaultEventsBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .Events(Components.SendEvents().FlushIntervalNoMinimum(TimeSpan.FromMilliseconds(10))) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(StandardEndpoints.DefaultEventsBaseUri, BaseUriOf(req.RequestUri)); + } + } + + [Fact] + public void CustomStreamingDataSourceBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.StreamingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(CustomUri).Polling(CustomUri)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(CustomUri, BaseUriOf(req.RequestUri)); + + Assert.False(logCapture.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying")); + } + } + + [Fact] + public void CustomPollingDataSourceBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.PollingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .ServiceEndpoints(Components.ServiceEndpoints().Polling(CustomUri)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(CustomUri, BaseUriOf(req.RequestUri)); + + Assert.False(logCapture.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying")); + } + } + + [Fact] + public void CustomEventsBaseUri() + { + using (var client = TestUtil.CreateClient( + BasicConfig() + .Events(Components.SendEvents().FlushIntervalNoMinimum(TimeSpan.FromMilliseconds(10))) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .ServiceEndpoints(Components.ServiceEndpoints().Events(CustomUri)) + .Build(), + BasicUser)) + { + var req = _stubHandler.Requests.ExpectValue(); + Assert.Equal(CustomUri, BaseUriOf(req.RequestUri)); + + Assert.False(logCapture.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying")); + } + } + + [Fact] + public void ErrorIsLoggedIfANecessaryUriIsNotSetWhenOtherCustomUrisAreSet() + { + var logCapture1 = Logs.Capture(); + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.StreamingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Logging(logCapture1) + .ServiceEndpoints(Components.ServiceEndpoints().Polling(CustomUri)) + .Build(), + BasicUser)) + { + Assert.True(logCapture1.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying the Streaming base URI")); + } + + var logCapture2 = Logs.Capture(); + using (var client = TestUtil.CreateClient( + BasicConfig() + .DataSource(Components.PollingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Logging(logCapture2) + .ServiceEndpoints(Components.ServiceEndpoints().Events(CustomUri)) + .Build(), + BasicUser)) + { + Assert.True(logCapture2.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying the Polling base URI")); + } + + var logCapture3 = Logs.Capture(); + using (var client = TestUtil.CreateClient( + BasicConfig() + .Events(Components.SendEvents()) + .Http(Components.HttpConfiguration().MessageHandler(_stubHandler)) + .Logging(logCapture3) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(CustomUri)) + .Build(), + BasicUser)) + { + Assert.True(logCapture3.HasMessageWithRegex(LogLevel.Error, + "You have set custom ServiceEndpoints without specifying the Events base URI")); + } + } + + private static Uri BaseUriOf(Uri uri) => + new Uri(uri.GetComponents(UriComponents.Scheme | UriComponents.HostAndPort | UriComponents.KeepDelimiter, UriFormat.Unescaped)); + + private class SimpleRecordingHttpMessageHandler : HttpMessageHandler + { + internal readonly EventSink Requests = new EventSink(); + private int _statusCode; + + public SimpleRecordingHttpMessageHandler(int statusCode) + { + _statusCode = statusCode; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Requests.Enqueue(request); + return Task.FromResult(new HttpResponseMessage((HttpStatusCode)_statusCode)); + } + } + } +} From 7453e51de84f2bdfe47e0c0fa9c8bdf8142ab06a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 20 Oct 2021 19:18:08 -0700 Subject: [PATCH 370/499] add DoubleVariation and DoubleVariationDetail --- .../Interfaces/ILdClient.cs | 28 +++++++++++-- src/LaunchDarkly.ClientSdk/LdClient.cs | 12 ++++++ .../PlatformSpecific/Logging.ios.cs | 4 +- .../ILdClientExtensionsTest.cs | 6 +++ .../LdClientEvaluationTests.cs | 42 +++++++++++++++++++ 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index 6d2af89b..936cbedc 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -174,7 +174,7 @@ public interface ILdClient : IDisposable EvaluationDetail StringVariationDetail(string key, string defaultValue); /// - /// Returns the float value of a feature flag for a given flag key. + /// Returns the single-precision floating-point value of a feature flag for a given flag key. /// /// the unique feature key for the feature flag /// the default value of the flag @@ -183,8 +183,8 @@ public interface ILdClient : IDisposable float FloatVariation(string key, float defaultValue = 0); /// - /// Returns the float value of a feature flag for a given flag key, in an object that also - /// describes the way the value was determined. + /// Returns the single-precision floating-point value of a feature flag for a given flag key, + /// in an object that also describes the way the value was determined. /// /// /// The property in the result will also be included in analytics @@ -195,6 +195,28 @@ public interface ILdClient : IDisposable /// an EvaluationDetail object EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0); + /// + /// Returns the double-precision floating-point value of a feature flag for a given flag key. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// the variation for the selected user, or defaultValue if the flag is + /// disabled in the LaunchDarkly control panel + double DoubleVariation(string key, double defaultValue = 0); + + /// + /// Returns the double-precision floating-point value of a feature flag for a given flag key, + /// in an object that also describes the way the value was determined. + /// + /// + /// The property in the result will also be included in analytics + /// events, if you are capturing detailed event data for this flag. + /// + /// the unique feature key for the feature flag + /// the default value of the flag + /// an EvaluationDetail object + EvaluationDetail DoubleVariationDetail(string key, double defaultValue = 0); + /// /// Returns the integer value of a feature flag for a given flag key. /// diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 95d4a8fe..a505c213 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -399,6 +399,18 @@ public EvaluationDetail FloatVariationDetail(string key, float defaultVal return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, _eventFactoryWithReasons); } + /// + public double DoubleVariation(string key, double defaultValue = 0) + { + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, _eventFactoryDefault).Value; + } + + /// + public EvaluationDetail DoubleVariationDetail(string key, double defaultValue = 0) + { + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, _eventFactoryWithReasons); + } + /// public int IntVariation(string key, int defaultValue = 0) { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs index 01cfc5fa..50afa20e 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.ios.cs @@ -24,7 +24,7 @@ private sealed class IOsLogAdapter : ILogAdapter private sealed class ChannelImpl : IChannel { - private readonly OSLog _log; + private readonly CoreFoundation.OSLog _log; internal ChannelImpl(string name) { @@ -39,7 +39,7 @@ internal ChannelImpl(string name) subsystem = name; category = ""; } - _log = new OSLog(subsystem, category); + _log = new CoreFoundation.OSLog(subsystem, category); } // As defined in IChannel, IsEnabled really means "is it *potentially* diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index 9e742d15..97ea8498 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -138,6 +138,12 @@ public float FloatVariation(string key, float defaultValue = 0) => public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) => throw new System.NotImplementedException(); + public double DoubleVariation(string key, double defaultValue = 0) => + throw new System.NotImplementedException(); + + public EvaluationDetail DoubleVariationDetail(string key, double defaultValue = 0) => + throw new System.NotImplementedException(); + public void Flush() { } public bool Identify(User user, System.TimeSpan maxWaitTime) => diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs index 0cd7cfa5..6ef8c7f8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs @@ -133,6 +133,48 @@ public void FloatVariationDetailReturnsValue() var expected = new EvaluationDetail(2.5f, 1, reason); Assert.Equal(expected, client.FloatVariationDetail(flagKey, 0.5f)); } + } + + [Fact] + public void DoubleVariationReturnsValue() + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(2.5d))); + using (var client = MakeClient()) + { + Assert.Equal(2.5d, client.DoubleVariation(flagKey, 0)); + } + } + + [Fact] + public void DoubleVariationCoercesIntValue() + { + _testData.Update(_testData.Flag(flagKey).Variation(LdValue.Of(2))); + using (var client = MakeClient()) + { + Assert.Equal(2.0d, client.DoubleVariation(flagKey, 0)); + } + } + + [Fact] + public void DoubleVariationReturnsDefaultForUnknownFlag() + { + using (var client = MakeClient()) + { + Assert.Equal(0.5d, client.DoubleVariation(nonexistentFlagKey, 0.5d)); + } + } + + [Fact] + public void DoubleVariationDetailReturnsValue() + { + var reason = EvaluationReason.OffReason; + var flag = new FeatureFlagBuilder().Value(LdValue.Of(2.5d)).Variation(1).Reason(reason).Build(); + _testData.Update(_testData.Flag(flagKey).PreconfiguredFlag(flag)); + using (var client = MakeClient()) + { + var expected = new EvaluationDetail(2.5d, 1, reason); + Assert.Equal(expected, client.DoubleVariationDetail(flagKey, 0.5d)); + } } [Fact] From 2116e1e5c5bf73f31d28d450ed317d5b8c246c67 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 21 Oct 2021 15:51:45 -0700 Subject: [PATCH 371/499] enable REPORT mode, except on Android --- .../Integrations/HttpConfigurationBuilder.cs | 6 +- .../DataSources/StreamingDataSourceTest.cs | 79 +++++++++++-------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 9630fdb9..a6efea25 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -212,8 +212,6 @@ public HttpConfigurationBuilder ResponseStartTimeout(TimeSpan responseStartTimeo return this; } - // NOTE: UseReport is currently internal rather than public because the REPORT verb does not - // work on every platform (ch47341) /// /// Sets whether to use the HTTP REPORT method for feature flag requests. /// @@ -232,7 +230,11 @@ public HttpConfigurationBuilder ResponseStartTimeout(TimeSpan responseStartTimeo /// /// true to enable the REPORT method /// the builder +#if !MONOANDROID + public HttpConfigurationBuilder UseReport(bool useReport) +#else internal HttpConfigurationBuilder UseReport(bool useReport) +#endif { _useReport = useReport; return this; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 48f74bba..a2b18b8c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -5,9 +5,13 @@ using LaunchDarkly.EventSource; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Json; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.TestUtil; +using static LaunchDarkly.TestHelpers.JsonAssertions; + namespace LaunchDarkly.Sdk.Client.Internal.DataSources { public class StreamingDataSourceTest : BaseTest @@ -76,38 +80,51 @@ string expectedQuery Assert.Equal(HttpMethod.Get, eventSourceFactory.ReceivedMethod); Assert.Equal(new Uri(fakeRootUri + expectedPathWithoutUser + encodedUser + expectedQuery), eventSourceFactory.ReceivedUri); + } + + // REPORT mode is known to fail in Android (ch47341) +#if !__ANDROID__ + [Theory] + [InlineData("", false, "/meval", "")] + [InlineData("", true, "/meval", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/meval", "")] + [InlineData("/basepath", true, "/basepath/meval", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/meval", "")] + [InlineData("/basepath/", true, "/basepath/meval", "?withReasons=true")] + public void RequestHasCorrectUriAndMethodAndBodyInReportMode( + string baseUriExtraPath, + bool withReasons, + string expectedPath, + string expectedQuery + ) + { + var fakeRootUri = "http://fake-stream-host"; + var fakeBaseUri = fakeRootUri + baseUriExtraPath; + this.baseUri = new Uri(fakeBaseUri); + var httpConfig = Components.HttpConfiguration().UseReport(true).CreateHttpConfiguration(SimpleContext.Basic); + using (var dataSource = new StreamingDataSource( + _updateSink, + user, + baseUri, + withReasons, + initialReconnectDelay, + mockRequestor, + httpConfig, + testLogger, + eventSourceFactory.Create() + )) + { + dataSource.Start(); + + Assert.Equal(new HttpMethod("REPORT"), eventSourceFactory.ReceivedMethod); + Assert.Equal(new Uri(fakeRootUri + expectedPath + expectedQuery), + eventSourceFactory.ReceivedUri); + Assert.NotNull(eventSourceFactory.ReceivedBody); + AssertJsonEqual(LdJsonSerialization.SerializeObject(user), + NormalizeJsonUser(LdValue.Parse(eventSourceFactory.ReceivedBody))); + } } - - // Report mode is currently disabled - ch47341 - //[Fact] - //public void StreamUriInReportModeHasNoUser() - //{ - // var config = configBuilder.UseReport(true).Build(); - // MobileStreamingProcessorStarted(); - // var props = eventSourceFactory.ReceivedProperties; - // Assert.Equal(new HttpMethod("REPORT"), props.Method); - // Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH), props.StreamUri); - //} - - //[Fact] - //public void StreamUriInReportModeHasReasonsParameterIfConfigured() - //{ - // var config = configBuilder.UseReport(true).EvaluationReasons(true).Build(); - // MobileStreamingProcessorStarted(); - // var props = eventSourceFactory.ReceivedProperties; - // Assert.Equal(new Uri(config.StreamUri, Constants.STREAM_REQUEST_PATH + "?withReasons=true"), props.StreamUri); - //} - - //[Fact] - //public async Task StreamRequestBodyInReportModeHasUser() - //{ - // configBuilder.UseReport(true); - // MobileStreamingProcessorStarted(); - // var props = eventSourceFactory.ReceivedProperties; - // var body = Assert.IsType(props.RequestBody); - // var s = await body.ReadAsStringAsync(); - // Assert.Equal(user.AsJson(), s); - //} +#endif [Fact] public void PutStoresFeatureFlags() From 46f997d335fe5a50eaff9ad392583e7c4b7b835f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 25 Oct 2021 14:17:24 -0700 Subject: [PATCH 372/499] configure TaskExecutor to call event handlers on main thread in Android/iOS --- .../Interfaces/IDataSourceStatusProvider.cs | 3 +- .../Interfaces/IFlagTracker.cs | 6 ++-- .../Interfaces/LdClientContext.cs | 6 +++- .../LaunchDarkly.ClientSdk.csproj | 6 ++-- .../AndroidSpecificTests.cs | 29 ++++++++++++++++++- .../IOsSpecificTests.cs | 29 ++++++++++++++++++- 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs index 0d07dbb8..cbe0e0f7 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs @@ -45,7 +45,8 @@ public interface IDataSourceStatusProvider /// to change. /// /// - /// Notifications will be dispatched on a background task. It is the listener's responsibility to return + /// Notifications will be dispatched either on the main thread (on mobile platforms) or in a + /// background task (on all other platforms). It is the listener's responsibility to return /// as soon as possible so as not to block subsequent notifications. /// /// diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs index 89e3261e..f249fde5 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs @@ -23,9 +23,9 @@ public interface IFlagTracker /// SDK has just received its very first set of flag values. /// /// - /// Notifications will be dispatched on a background task. It is the listener's - /// responsibility to return as soon as possible so as not to block subsequent - /// notifications. + /// Notifications will be dispatched either on the main thread (on mobile platforms) or in a + /// background task (on all other platforms). It is the listener's responsibility to return + /// as soon as possible so as not to block subsequent notifications. /// /// /// diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index df594877..82015264 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -74,7 +74,11 @@ object eventSender this.ServiceEndpoints = configuration.ServiceEndpoints; - this.TaskExecutor = new TaskExecutor(eventSender, this.BaseLogger); + this.TaskExecutor = new TaskExecutor( + eventSender, + PlatformSpecific.AsyncScheduler.ScheduleAction, + this.BaseLogger + ); } } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index f1b2ac27..b10f6b2a 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,10 +40,10 @@ - + - - + + diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index f6f22836..ff9e7255 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -1,4 +1,8 @@ -using Xunit; +using System.Threading; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.TestHelpers; +using Android.OS; +using Xunit; namespace LaunchDarkly.Sdk.Client.Android.Tests { @@ -38,5 +42,28 @@ public void CanGetUniqueUserKey() Assert.NotEqual("", user.Key); } } + + [Fact] + public void EventHandlerIsCalledOnUIThread() + { + var td = TestData.DataSource(); + var config = BasicConfig().DataSource(td).Build(); + + var captureMainThread = new EventSink(); + new Handler(Looper.MainLooper).Post(() => captureMainThread.Enqueue(Thread.CurrentThread)); + var mainThread = captureMainThread.ExpectValue(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var receivedOnThread = new EventSink(); + client.FlagTracker.FlagValueChanged += (sender, args) => + receivedOnThread.Enqueue(Thread.CurrentThread); + + td.Update(td.Flag("flagkey").Variation(true)); + + var t = receivedOnThread.ExpectValue(); + Assert.Equal(mainThread, t); + } + } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index 02df7ff1..55681ad4 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -1,4 +1,8 @@ -using Xunit; +using System.Threading; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.TestHelpers; +using Foundation; +using Xunit; namespace LaunchDarkly.Sdk.Client.iOS.Tests { @@ -37,6 +41,29 @@ public void CanGetUniqueUserKey() Assert.NotNull(user.Key); Assert.NotEqual("", user.Key); } + } + + [Fact] + public void EventHandlerIsCalledOnUIThread() + { + var td = TestData.DataSource(); + var config = BasicConfig().DataSource(td).Build(); + + var captureMainThread = new EventSink(); + NSRunLoop.Main.BeginInvokeOnMainThread(() => captureMainThread.Enqueue(Thread.CurrentThread)); + var mainThread = captureMainThread.ExpectValue(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var receivedOnThread = new EventSink(); + client.FlagTracker.FlagValueChanged += (sender, args) => + receivedOnThread.Enqueue(Thread.CurrentThread); + + td.Update(td.Flag("flagkey").Variation(true)); + + var t = receivedOnThread.ExpectValue(); + Assert.Equal(mainThread, t); + } } } } From 922046307bc97cfddafef4e55e63c3375efac7c4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 25 Oct 2021 14:25:45 -0700 Subject: [PATCH 373/499] add guard on writing to Xunit test output --- tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs index b4963cc7..c89e021c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestLogging.cs @@ -1,4 +1,5 @@ -using LaunchDarkly.Logging; +using System; +using LaunchDarkly.Logging; using Xunit.Abstractions; namespace LaunchDarkly.Sdk.Client @@ -27,6 +28,13 @@ public class TestLogging /// class constructor /// a log adapter public static ILogAdapter TestOutputAdapter(ITestOutputHelper testOutputHelper) => - Logs.ToMethod(line => testOutputHelper.WriteLine("LOG OUTPUT >> " + line)); + Logs.ToMethod(line => + { + try + { + testOutputHelper.WriteLine("LOG OUTPUT >> " + line); + } + catch (Exception) { } // WriteLine may fail if a background task tries to log something after the test has ended + }); } } From e29230e0c7177958b44e7d1077dc81a3b19d304d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 28 Oct 2021 12:52:52 -0700 Subject: [PATCH 374/499] implement diagnostic events (#137) --- src/LaunchDarkly.ClientSdk/Configuration.cs | 6 + .../ConfigurationBuilder.cs | 21 + .../Integrations/EventProcessorBuilder.cs | 93 ++++- .../Integrations/HttpConfigurationBuilder.cs | 33 +- .../Integrations/PollingDataSourceBuilder.cs | 20 +- .../Integrations/ServiceEndpointsBuilder.cs | 22 +- .../StreamingDataSourceBuilder.cs | 28 +- .../Interfaces/IDiagnosticDescription.cs | 31 ++ .../Interfaces/LdClientContext.cs | 22 +- .../DataSources/StreamingDataSource.cs | 22 ++ .../Internal/Events/ClientDiagnosticStore.cs | 86 +++++ .../Internal/Events/DiagnosticDisablerImpl.cs | 36 ++ .../Internal/StandardEndpoints.cs | 36 +- .../LaunchDarkly.ClientSdk.csproj | 15 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 19 +- .../ConfigurationTest.cs | 8 + .../Integrations/EventProcessorBuilderTest.cs | 10 + .../ServiceEndpointsBuilderTest.cs | 6 +- .../DataSources/StreamingDataSourceTest.cs | 42 +- .../LDClientEndToEndTests.cs | 29 +- .../LaunchDarkly.ClientSdk.Tests.csproj | 4 + .../LdClientDiagnosticEventTest.cs | 363 ++++++++++++++++++ .../LdClientServiceEndpointsTests.cs | 6 +- .../MockComponents.cs | 78 ++++ .../TestHttpUtils.cs | 43 +++ 25 files changed, 970 insertions(+), 109 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/Events/DiagnosticDisablerImpl.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 4d338c75..7d017224 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -52,6 +52,11 @@ public sealed class Configuration /// public IDataSourceFactory DataSourceFactory { get; } + /// + /// True if diagnostic events have been disabled. + /// + public bool DiagnosticOptOut { get; } + /// /// Whether to enable feature flag updates when the application is running in the background. /// @@ -164,6 +169,7 @@ internal Configuration(ConfigurationBuilder builder) { AutoAliasingOptOut = builder._autoAliasingOptOut; DataSourceFactory = builder._dataSourceFactory; + DiagnosticOptOut = builder._diagnosticOptOut; EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; EventProcessorFactory = builder._eventProcessorFactory; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index ca32daf9..bc79996b 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -35,6 +35,7 @@ public sealed class ConfigurationBuilder internal bool _autoAliasingOptOut = false; internal IDataSourceFactory _dataSourceFactory = null; + internal bool _diagnosticOptOut = false; internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; internal IEventProcessorFactory _eventProcessorFactory = null; @@ -59,6 +60,7 @@ internal ConfigurationBuilder(Configuration copyFrom) { _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; _dataSourceFactory = copyFrom.DataSourceFactory; + _diagnosticOptOut = copyFrom.DiagnosticOptOut; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; _eventProcessorFactory = copyFrom.EventProcessorFactory; @@ -128,6 +130,25 @@ public ConfigurationBuilder DataSource(IDataSourceFactory dataSourceFactory) return this; } + /// + /// Specifies whether true to opt out of sending diagnostic events. + /// + /// + /// Unless this is set to , the client will send some + /// diagnostics data to the LaunchDarkly servers in order to assist in the development + /// of future SDK improvements. These diagnostics consist of an initial payload + /// containing some details of SDK in use, the SDK's configuration, and the platform the + /// SDK is being run on, as well as payloads sent periodically with information on + /// irregular occurrences such as dropped events. + /// + /// to disable diagnostic events + /// the same builder + public ConfigurationBuilder DiagnosticOptOut(bool diagnosticOptOut) + { + _diagnosticOptOut = diagnosticOptOut; + return this; + } + /// /// Sets whether to enable feature flag polling when the application is in the background. /// diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 4de4669d..4c32fd0e 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; +using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; + namespace LaunchDarkly.Sdk.Client.Integrations { /// @@ -26,13 +29,18 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class EventProcessorBuilder : IEventProcessorFactory + public sealed class EventProcessorBuilder : IEventProcessorFactory, IDiagnosticDescription { /// /// The default value for . /// public const int DefaultCapacity = 100; + /// + /// The default value for . + /// + public static readonly TimeSpan DefaultDiagnosticRecordingInterval = TimeSpan.FromMinutes(15); + /// /// The default value for . /// @@ -48,8 +56,14 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory TimeSpan.FromSeconds(30); #endif + /// + /// The minimum value for : 5 minutes. + /// + public static readonly TimeSpan MinimumDiagnosticRecordingInterval = TimeSpan.FromMinutes(5); + internal bool _allAttributesPrivate = false; internal int _capacity = DefaultCapacity; + internal TimeSpan _diagnosticRecordingInterval = DefaultDiagnosticRecordingInterval; internal TimeSpan _flushInterval = DefaultFlushInterval; internal bool _inlineUsersInEvents = false; internal HashSet _privateAttributes = new HashSet(); @@ -92,6 +106,31 @@ public EventProcessorBuilder Capacity(int capacity) return this; } + /// + /// Sets the interval at which periodic diagnostic data is sent. + /// + /// + /// The default value is ; the minimum value is + /// . This property is ignored if + /// is set to . + /// + /// the diagnostics interval + /// the builder + public EventProcessorBuilder DiagnosticRecordingInterval(TimeSpan diagnosticRecordingInterval) + { + _diagnosticRecordingInterval = + diagnosticRecordingInterval < MinimumDiagnosticRecordingInterval ? + MinimumDiagnosticRecordingInterval : diagnosticRecordingInterval; + return this; + } + + // Used only in testing + internal EventProcessorBuilder DiagnosticRecordingIntervalNoMinimum(TimeSpan diagnosticRecordingInterval) + { + _diagnosticRecordingInterval = diagnosticRecordingInterval; + return this; + } + // Used only in testing internal EventProcessorBuilder EventSender(IEventSender eventSender) { @@ -186,23 +225,7 @@ public EventProcessorBuilder PrivateAttributeNames(params string[] attributes) /// public IEventProcessor CreateEventProcessor(LdClientContext context) { - var baseUri = ServiceEndpointsBuilder.SelectBaseUri( - context.ServiceEndpoints.EventsBaseUri, - StandardEndpoints.DefaultEventsBaseUri, - "Events", - context.BaseLogger - ); - var eventsConfig = new EventsConfiguration - { - AllAttributesPrivate = _allAttributesPrivate, - EventCapacity = _capacity, - EventFlushInterval = _flushInterval, - EventsUri = baseUri.AddPath(StandardEndpoints.AnalyticsEventsPostRequestPath), - //DiagnosticUri = uri.AddPath("diagnostic"), // no diagnostic events yet - InlineUsersInEvents = _inlineUsersInEvents, - PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), - RetryInterval = TimeSpan.FromSeconds(1) - }; + var eventsConfig = MakeEventsConfiguration(context, true); var logger = context.BaseLogger.SubLogger(LogNames.EventsSubLog); var eventSender = _eventSender ?? new DefaultEventSender( @@ -215,11 +238,41 @@ public IEventProcessor CreateEventProcessor(LdClientContext context) eventsConfig, eventSender, null, // no user deduplicator, because the client-side SDK doesn't send index events - null, // diagnostic store would go here, but we haven't implemented diagnostic events - null, + context.DiagnosticStore, + context.DiagnosticDisabler, logger, null )); } + + /// + public LdValue DescribeConfiguration(LdClientContext context) => + LdValue.BuildObject().WithEventProperties( + MakeEventsConfiguration(context, false), + StandardEndpoints.IsCustomUri(context.ServiceEndpoints, e => e.EventsBaseUri) + ) + .Build(); + + private EventsConfiguration MakeEventsConfiguration(LdClientContext context, bool logConfigErrors) + { + var baseUri = StandardEndpoints.SelectBaseUri( + context.ServiceEndpoints, + e => e.EventsBaseUri, + "Events", + logConfigErrors ? context.BaseLogger : Logs.None.Logger("") + ); + return new EventsConfiguration + { + AllAttributesPrivate = _allAttributesPrivate, + EventCapacity = _capacity, + EventFlushInterval = _flushInterval, + EventsUri = baseUri.AddPath(StandardEndpoints.AnalyticsEventsPostRequestPath), + DiagnosticRecordingInterval = _diagnosticRecordingInterval, + DiagnosticUri = baseUri.AddPath(StandardEndpoints.DiagnosticEventsPostRequestPath), + InlineUsersInEvents = _inlineUsersInEvents, + PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), + RetryInterval = TimeSpan.FromSeconds(1) + }; + } } } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index a6efea25..63ecfa46 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -6,6 +6,8 @@ using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Client.Interfaces; +using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; + namespace LaunchDarkly.Sdk.Client.Integrations { /// @@ -28,7 +30,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// /// - public sealed class HttpConfigurationBuilder + public sealed class HttpConfigurationBuilder : IDiagnosticDescription { /// /// The default value for : 10 seconds. @@ -263,7 +265,22 @@ public HttpConfigurationBuilder Wrapper(string wrapperName, string wrapperVersio /// /// provides the basic SDK configuration properties /// an - public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfiguration) + public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfiguration) => + new HttpConfiguration( + MakeHttpProperties(basicConfiguration), + _messageHandler, + _responseStartTimeout, + _useReport + ); + + /// + public LdValue DescribeConfiguration(LdClientContext context) => + LdValue.BuildObject() + .WithHttpProperties(MakeHttpProperties(context.Basic)) + .Add("useReport", _useReport) + .Build(); + + private HttpProperties MakeHttpProperties(BasicConfiguration basic) { Func handlerFn; if (_messageHandler is null) @@ -274,9 +291,9 @@ public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfigu { handlerFn = p => _messageHandler; } - + var httpProperties = HttpProperties.Default - .WithAuthorizationKey(basicConfiguration.MobileKey) + .WithAuthorizationKey(basic.MobileKey) .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) @@ -288,13 +305,7 @@ public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfigu { httpProperties = httpProperties.WithHeader(kv.Key, kv.Value); } - - return new HttpConfiguration( - httpProperties, - _messageHandler, - _responseStartTimeout, - _useReport - ); + return httpProperties; } } } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 092d0f94..9364ba63 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -3,6 +3,8 @@ using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; + namespace LaunchDarkly.Sdk.Client.Integrations { /// @@ -32,7 +34,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class PollingDataSourceBuilder : IDataSourceFactory + public sealed class PollingDataSourceBuilder : IDataSourceFactory, IDiagnosticDescription { /// /// The default value for : 5 minutes. @@ -95,9 +97,9 @@ bool inBackground { context.BaseLogger.Warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); } - var baseUri = ServiceEndpointsBuilder.SelectBaseUri( - context.ServiceEndpoints.PollingBaseUri, - StandardEndpoints.DefaultPollingBaseUri, + var baseUri = StandardEndpoints.SelectBaseUri( + context.ServiceEndpoints, + e => e.PollingBaseUri, "Polling", context.BaseLogger ); @@ -121,5 +123,15 @@ bool inBackground logger ); } + + /// + public LdValue DescribeConfiguration(LdClientContext context) => + LdValue.BuildObject() + .WithPollingProperties( + StandardEndpoints.IsCustomUri(context.ServiceEndpoints, e => e.PollingBaseUri), + _pollInterval + ) + .Add("backgroundPollingIntervalMillis", _backgroundPollInterval.TotalMilliseconds) + .Build(); } } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs index 374e65ce..91064d1c 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs @@ -256,29 +256,9 @@ public ServiceEndpoints Build() // error. if (_streamingBaseUri is null && _pollingBaseUri is null && _eventsBaseUri is null) { - return new ServiceEndpoints( - StandardEndpoints.DefaultStreamingBaseUri, - StandardEndpoints.DefaultPollingBaseUri, - StandardEndpoints.DefaultEventsBaseUri - ); + return StandardEndpoints.BaseUris; } return new ServiceEndpoints(_streamingBaseUri, _pollingBaseUri, _eventsBaseUri); } - - internal static Uri SelectBaseUri( - Uri serviceEndpointsValue, - Uri defaultValue, - string description, - Logger logger - ) - { - if (serviceEndpointsValue != null) - { - return serviceEndpointsValue; - } - logger.Error("You have set custom ServiceEndpoints without specifying the {0} base URI; connections may not work properly", - description); - return defaultValue; - } } } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index ccb68385..f89b40ca 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -3,6 +3,8 @@ using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; + namespace LaunchDarkly.Sdk.Client.Integrations { /// @@ -28,7 +30,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class StreamingDataSourceBuilder : IDataSourceFactory + public sealed class StreamingDataSourceBuilder : IDataSourceFactory, IDiagnosticDescription { /// /// The default value for : 1000 milliseconds. @@ -98,15 +100,15 @@ public IDataSource CreateDataSource( bool inBackground ) { - var baseUri = ServiceEndpointsBuilder.SelectBaseUri( - context.ServiceEndpoints.StreamingBaseUri, - StandardEndpoints.DefaultStreamingBaseUri, + var baseUri = StandardEndpoints.SelectBaseUri( + context.ServiceEndpoints, + e => e.StreamingBaseUri, "Streaming", context.BaseLogger ); - var pollingBaseUri = ServiceEndpointsBuilder.SelectBaseUri( - context.ServiceEndpoints.PollingBaseUri, - StandardEndpoints.DefaultPollingBaseUri, + var pollingBaseUri = StandardEndpoints.SelectBaseUri( + context.ServiceEndpoints, + e => e.PollingBaseUri, "Polling", context.BaseLogger ); @@ -137,8 +139,20 @@ bool inBackground requestor, context.Http, logger, + context.DiagnosticStore, _eventSourceCreator ); } + + /// + public LdValue DescribeConfiguration(LdClientContext context) => + LdValue.BuildObject() + .WithStreamingProperties( + StandardEndpoints.IsCustomUri(context.ServiceEndpoints, e => e.PollingBaseUri), + StandardEndpoints.IsCustomUri(context.ServiceEndpoints, e => e.StreamingBaseUri), + _initialReconnectDelay + ) + .Add("backgroundPollingIntervalMillis", _backgroundPollInterval.TotalMilliseconds) + .Build(); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs new file mode 100644 index 00000000..51ce362a --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs @@ -0,0 +1,31 @@ + +namespace LaunchDarkly.Sdk.Client.Interfaces +{ + /// + /// Optional interface for components to describe their own configuration. + /// + /// + /// + /// The SDK uses a simplified JSON representation of its configuration when recording diagnostics data. + /// Any class that implements , , + /// or may choose to contribute values to this representation, + /// although the SDK may or may not use them. + /// + /// + /// The method should return either or a + /// JSON value. For custom components, the value must be a string that describes the basic nature of + /// this component implementation (e.g. "Redis"). Built-in LaunchDarkly components may instead return a + /// JSON object containing multiple properties specific to the LaunchDarkly diagnostic schema. + /// + /// + public interface IDiagnosticDescription + { + /// + /// Called internally by the SDK to inspect the configuration. Applications do not need to call + /// this method. + /// + /// the context object that may provide relevant configuration details + /// a JSON value + LdValue DescribeConfiguration(LdClientContext context); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index 82015264..609217e5 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -1,6 +1,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Events; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -8,10 +9,16 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// Encapsulates SDK client context when creating components. /// /// + /// /// Factory interfaces like receive this class as a parameter. /// Its public properties provide information about the SDK configuration and environment. The SDK /// may also include non-public properties that are relevant only when creating one of the built-in /// component types and are not accessible to custom components. + /// + /// + /// Some properties are in instead because they are required in + /// situations where the has not been fully constructed yet. + /// /// public sealed class LdClientContext { @@ -41,10 +48,14 @@ public sealed class LdClientContext public HttpConfiguration Http { get; } /// - /// Defines the base service URIs used by SDK components. + /// The configured service base URIs. /// public ServiceEndpoints ServiceEndpoints { get; } + internal IDiagnosticDisabler DiagnosticDisabler { get; } + + internal IDiagnosticStore DiagnosticStore { get; } + internal TaskExecutor TaskExecutor { get; } /// @@ -53,11 +64,13 @@ public sealed class LdClientContext /// the SDK configuration public LdClientContext( Configuration configuration - ) : this(configuration, null) { } + ) : this(configuration, null, null, null) { } internal LdClientContext( Configuration configuration, - object eventSender + object eventSender, + IDiagnosticStore diagnosticStore, + IDiagnosticDisabler diagnosticDisabler ) { this.Basic = new BasicConfiguration(configuration.MobileKey); @@ -71,9 +84,10 @@ object eventSender this.EvaluationReasons = configuration.EvaluationReasons; this.Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) .CreateHttpConfiguration(this.Basic); - this.ServiceEndpoints = configuration.ServiceEndpoints; + this.DiagnosticStore = diagnosticStore; + this.DiagnosticDisabler = diagnosticDisabler; this.TaskExecutor = new TaskExecutor( eventSender, PlatformSpecific.AsyncScheduler.ScheduleAction, diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index e4bd0adf..b8f41b3d 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -6,7 +6,9 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Http; @@ -32,6 +34,7 @@ internal sealed class StreamingDataSource : IDataSource private readonly TimeSpan _initialReconnectDelay; private readonly IFeatureFlagRequestor _requestor; private readonly HttpProperties _httpProperties; + private readonly IDiagnosticStore _diagnosticStore; private readonly EventSourceCreator _eventSourceCreator; private readonly TaskCompletionSource _initTask; private readonly AtomicBoolean _initialized = new AtomicBoolean(false); @@ -39,6 +42,8 @@ internal sealed class StreamingDataSource : IDataSource private volatile IEventSource _eventSource; + internal DateTime _esStarted; // exposed for testing + internal delegate IEventSource EventSourceCreator( HttpProperties httpProperties, HttpMethod method, @@ -55,6 +60,7 @@ internal StreamingDataSource( IFeatureFlagRequestor requestor, HttpConfiguration httpConfig, Logger log, + IDiagnosticStore diagnosticStore, EventSourceCreator eventSourceCreator // used only in tests ) { @@ -66,6 +72,7 @@ EventSourceCreator eventSourceCreator // used only in tests this._initialReconnectDelay = initialReconnectDelay; this._requestor = requestor; this._httpProperties = httpConfig.HttpProperties; + this._diagnosticStore = diagnosticStore; this._initTask = new TaskCompletionSource(); this._log = log; this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; @@ -99,6 +106,8 @@ public Task Start() _eventSource.Error += OnError; _eventSource.Opened += OnOpen; + _esStarted = DateTime.Now; + _ = Task.Run(() => _eventSource.StartAsync()); return _initTask.Task; } @@ -127,9 +136,20 @@ private Uri MakeRequestUriWithPath(string path) return _withReasons ? uri.AddQuery("withReasons=true") : uri; } + private void RecordStreamInit(bool failed) + { + if (_diagnosticStore != null) + { + DateTime now = DateTime.Now; + _diagnosticStore.AddStreamInit(_esStarted, now - _esStarted, failed); + _esStarted = now; + } + } + private void OnOpen(object sender, EventSource.StateChangedEventArgs e) { _log.Debug("EventSource Opened"); + RecordStreamInit(false); } private void OnMessage(object sender, EventSource.MessageReceivedEventArgs e) @@ -165,6 +185,8 @@ private void OnError(object sender, EventSource.ExceptionEventArgs e) var recoverable = true; DataSourceStatus.ErrorInfo errorInfo; + RecordStreamInit(true); + if (ex is EventSourceServiceUnsuccessfulResponseException respEx) { int status = respEx.StatusCode; diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs new file mode 100644 index 00000000..28228570 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using LaunchDarkly.Sdk.Internal.Events; +using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Interfaces; + +using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; + +namespace LaunchDarkly.Sdk.Client.Internal.Events +{ + internal class ClientDiagnosticStore : DiagnosticStoreBase + { + private readonly Configuration _config; + private readonly TimeSpan _startWaitTime; + + private LdClientContext _context; + + protected override string SdkKeyOrMobileKey => _context.Basic.MobileKey; + protected override string SdkName => "dotnet-client-sdk"; + protected override IEnumerable ConfigProperties => GetConfigProperties(); + protected override string DotNetTargetFramework => GetDotNetTargetFramework(); + protected override HttpProperties HttpProperties => _context.Http.HttpProperties; + protected override Type TypeOfLdClient => typeof(LdClient); + + internal ClientDiagnosticStore(Configuration config, TimeSpan startWaitTime) + { + _config = config; + _startWaitTime = startWaitTime; + // We pass in startWaitTime separately because in the client-side SDK, it is not + // part of the configuration - it is a separate parameter to the LdClient + // constructor. That's because this parameter only matters if they use the + // synchronous method LdClient.Init(); if they use LdClient.InitAsync() instead, + // there's no such thing as a startup timeout within the SDK (in which case this + // parameter will be zero and the corresponding property in the diagnostic event + // data will be zero, since there is no meaningful value for it). + } + + internal void SetContext(LdClientContext context) + { + // This is done as a separate step, called from the LdClient constructor, because + // the DiagnosticStore object has to be created before the LdClientContext - since + // the LdClientContext includes a reference to the DiagnosticStore (for components + // like StreamingDataSource to use). + _context = context; + } + + private IEnumerable GetConfigProperties() + { + yield return LdValue.BuildObject() + .WithAutoAliasingOptOut(_config.AutoAliasingOptOut) + .WithStartWaitTime(_startWaitTime) + .Add("backgroundPollingDisabled", !_config.EnableBackgroundUpdating) + .Add("evaluationReasonsRequested", _config.EvaluationReasons) + .Build(); + + // Allow each pluggable component to describe its own relevant properties. + yield return GetComponentDescription(_config.DataSourceFactory ?? Components.StreamingDataSource()); + yield return GetComponentDescription(_config.EventProcessorFactory ?? Components.SendEvents()); + yield return GetComponentDescription(_config.HttpConfigurationBuilder ?? Components.HttpConfiguration()); + } + + private LdValue GetComponentDescription(object component) => + component is IDiagnosticDescription dd ? + dd.DescribeConfiguration(_context) : LdValue.Null; + + internal static string GetDotNetTargetFramework() + { + // Note that this is the _target framework_ that was selected at build time based on the application's + // compatibility requirements; it doesn't tell us anything about the actual OS version. We'll need to + // update this whenever we add or remove supported target frameworks in the .csproj file. +#if NETSTANDARD2_0 + return "netstandard2.0"; +#elif MONOANDROID71 + return "monoandroid7.1"; +#elif MONOANDROID80 + return "monoandroid8.0"; +#elif MONOANDROID81 + return "monoandroid8.1"; +#elif XAMARIN_IOS10 + return "xamarinios1.0"; +#else + return "unknown"; +#endif + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DiagnosticDisablerImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DiagnosticDisablerImpl.cs new file mode 100644 index 00000000..77fa82a1 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DiagnosticDisablerImpl.cs @@ -0,0 +1,36 @@ +using System; +using LaunchDarkly.Sdk.Internal.Concurrent; +using LaunchDarkly.Sdk.Internal.Events; + +namespace LaunchDarkly.Sdk.Client.Internal.Events +{ + // The IDiagnosticDisabler interface is defined by the event processor implementation + // in LaunchDarkly.InternalSdk as a hook to give the event processor a way to know + // whether periodic diagnostic events should be temporarily disabled. The event + // processor will register itself as an event handler on the DisabledChanged event. + // In the client-side SDK, we disable periodic diagnostic events whenever the app is + // in the background. (The server-side SDK doesn't have any equivalent behavior so + // we do not implement IDiagnosticDisabler there.) + + internal sealed class DiagnosticDisablerImpl : IDiagnosticDisabler + { + private readonly AtomicBoolean _disabled = new AtomicBoolean(false); + + public bool Disabled => _disabled.Get(); + + public event EventHandler DisabledChanged; + + internal void SetDisabled(bool disabled) + { + if (_disabled.GetAndSet(disabled) != disabled) + { + DisabledChanged?.Invoke(null, new DisabledChangedArgs(disabled)); + // We are not using TaskExecutor to dispatch this event because the + // event handler, if any, was not provided by the application - it is + // always our own internal logic in the LaunchDarkly.InternalSdk + // events code, which doesn't do anything time-consuming. So we are + // not calling out to unknown code and it's safe to be synchronous. + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs index 47c14762..07c2417a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs @@ -1,12 +1,16 @@ using System; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal { internal static class StandardEndpoints { - internal static Uri DefaultStreamingBaseUri = new Uri("https://clientstream.launchdarkly.com"); - internal static Uri DefaultPollingBaseUri = new Uri("https://clientsdk.launchdarkly.com"); - internal static Uri DefaultEventsBaseUri = new Uri("https://mobile.launchdarkly.com"); + internal static readonly ServiceEndpoints BaseUris = new ServiceEndpoints( + new Uri("https://clientstream.launchdarkly.com"), + new Uri("https://clientsdk.launchdarkly.com"), + new Uri("https://mobile.launchdarkly.com") + ); internal static string StreamingGetRequestPath(string userDataBase64) => "/meval/" + userDataBase64; @@ -17,5 +21,31 @@ internal static string PollingRequestGetRequestPath(string userDataBase64) => internal const string PollingRequestReportRequestPath = "msdk/evalx/user"; internal const string AnalyticsEventsPostRequestPath = "mobile/events/bulk"; + + internal const string DiagnosticEventsPostRequestPath = "mobile/events/diagnostic"; + + internal static Uri SelectBaseUri( + ServiceEndpoints configuredEndpoints, + Func uriGetter, + string description, + Logger errorLogger + ) + { + var configuredUri = uriGetter(configuredEndpoints); + if (configuredUri != null) + { + return configuredUri; + } + errorLogger.Error( + "You have set custom ServiceEndpoints without specifying the {0} base URI; connections may not work properly", + description); + return uriGetter(BaseUris); + } + + internal static bool IsCustomUri( + ServiceEndpoints configuredEndpoints, + Func uriGetter + ) => + !uriGetter(BaseUris).Equals(uriGetter(configuredEndpoints)); } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index b10f6b2a..ef94d434 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -41,8 +41,8 @@ - - + + @@ -73,15 +73,4 @@ - - - - - - - - - - - diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index a505c213..428a6e63 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -36,6 +36,7 @@ public sealed class LdClient : ILdClient readonly IDataSourceStatusProvider _dataSourceStatusProvider; readonly IDataSourceUpdateSink _dataSourceUpdateSink; readonly IDataStore _dataStore; + readonly DiagnosticDisablerImpl _diagnosticDisabler; readonly ConnectionManager _connectionManager; readonly IBackgroundModeManager _backgroundModeManager; readonly IDeviceInfo deviceInfo; @@ -120,7 +121,7 @@ public sealed class LdClient : ILdClient // without using WithConfigAnduser(config, user) LdClient() { } - LdClient(Configuration configuration, User user) + LdClient(Configuration configuration, User user, TimeSpan startWaitTime) { if (user == null) { @@ -128,10 +129,15 @@ public sealed class LdClient : ILdClient } _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); + var diagnosticStore = _config.DiagnosticOptOut ? null : + new ClientDiagnosticStore(_config, startWaitTime); + _diagnosticDisabler = _config.DiagnosticOptOut ? null : + new DiagnosticDisablerImpl(); - _context = new LdClientContext(configuration, this); + _context = new LdClientContext(configuration, this, diagnosticStore, _diagnosticDisabler); _log = _context.BaseLogger; _taskExecutor = _context.TaskExecutor; + diagnosticStore?.SetContext(_context); _log.Info("Starting LaunchDarkly Client {0}", Version); @@ -309,7 +315,7 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); } - var c = CreateInstance(config, user); + var c = CreateInstance(config, user, maxWaitTime); c.Start(maxWaitTime); return c; } @@ -330,12 +336,12 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. public static async Task InitAsync(Configuration config, User user) { - var c = CreateInstance(config, user); + var c = CreateInstance(config, user, TimeSpan.Zero); await c.StartAsync(); return c; } - static LdClient CreateInstance(Configuration configuration, User user) + static LdClient CreateInstance(Configuration configuration, User user, TimeSpan maxWaitTime) { lock (_createInstanceLock) { @@ -344,7 +350,7 @@ static LdClient CreateInstance(Configuration configuration, User user) throw new Exception("LdClient instance already exists."); } - var c = new LdClient(configuration, user); + var c = new LdClient(configuration, user, maxWaitTime); _instance = c; return c; } @@ -709,6 +715,7 @@ internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeCh return; } _log.Debug("Background mode is changing to {0}", goingIntoBackground); + _diagnosticDisabler?.SetDisabled(goingIntoBackground); if (goingIntoBackground) { if (!Config.EnableBackgroundUpdating) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 6afaa813..ab1271ef 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -47,6 +47,14 @@ public void DataSource() prop.AssertCanSet(new ComponentsImpl.NullDataSourceFactory()); } + [Fact] + public void DiagnosticOptOut() + { + var prop = _tester.Property(c => c.DiagnosticOptOut, (b, v) => b.DiagnosticOptOut(v)); + prop.AssertDefault(false); + prop.AssertCanSet(true); + } + [Fact] public void EnableBackgroundUpdating() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs index 20bcf656..de5b8f81 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs @@ -21,6 +21,16 @@ public void AllAttributesPrivate() prop.AssertCanSet(true); } + [Fact] + public void DiagnosticRecordingInterval() + { + var prop = _tester.Property(b => b._diagnosticRecordingInterval, (b, v) => b.DiagnosticRecordingInterval(v)); + prop.AssertDefault(EventProcessorBuilder.DefaultDiagnosticRecordingInterval); + prop.AssertCanSet(TimeSpan.FromMinutes(7)); + prop.AssertSetIsChangedTo(TimeSpan.FromMinutes(4), EventProcessorBuilder.MinimumDiagnosticRecordingInterval); + prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(-1), EventProcessorBuilder.MinimumDiagnosticRecordingInterval); + } + [Fact] public void EventCapacity() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs index 6afb6676..ff006907 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/ServiceEndpointsBuilderTest.cs @@ -10,9 +10,9 @@ public class ServiceEndpointsBuilderTest public void UsesAllDefaultUrisIfNoneAreOverridden() { var se = Components.ServiceEndpoints().Build(); - Assert.Equal(StandardEndpoints.DefaultEventsBaseUri, se.EventsBaseUri); - Assert.Equal(StandardEndpoints.DefaultPollingBaseUri, se.PollingBaseUri); - Assert.Equal(StandardEndpoints.DefaultStreamingBaseUri, se.StreamingBaseUri); + Assert.Equal(StandardEndpoints.BaseUris.EventsBaseUri, se.EventsBaseUri); + Assert.Equal(StandardEndpoints.BaseUris.PollingBaseUri, se.PollingBaseUri); + Assert.Equal(StandardEndpoints.BaseUris.StreamingBaseUri, se.StreamingBaseUri); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index a2b18b8c..c8c75179 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using LaunchDarkly.EventSource; using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Json; using Xunit; @@ -41,7 +42,7 @@ public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) baseUri = new Uri("http://example"); } - private StreamingDataSource MakeStartedStreamingDataSource() + private StreamingDataSource MakeStartedStreamingDataSource(IDiagnosticStore diagnosticStore = null) { var dataSource = new StreamingDataSource( _updateSink, @@ -52,6 +53,7 @@ private StreamingDataSource MakeStartedStreamingDataSource() mockRequestor, TestUtil.SimpleContext.Http, testLogger, + diagnosticStore, eventSourceFactory.Create() ); dataSource.Start(); @@ -111,6 +113,7 @@ string expectedQuery mockRequestor, httpConfig, testLogger, + null, eventSourceFactory.Create() )) { @@ -185,7 +188,33 @@ public void PingCausesPoll() var gotItem = gotData.Items.First(item => item.Key == "int-flag"); int intFlagValue = gotItem.Value.Item.Value.AsInt; Assert.Equal(15, intFlagValue); - } + } + + [Fact] + public void StreamInitDiagnosticRecordedOnOpen() + { + var mockDiagnosticStore = new MockDiagnosticStore(); + using (var sp = MakeStartedStreamingDataSource(mockDiagnosticStore)) + { + mockEventSource.RaiseOpened(new StateChangedEventArgs(ReadyState.Open)); + + var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); + Assert.False(streamInit.Failed); + } + } + + [Fact] + public void StreamInitDiagnosticRecordedOnError() + { + var mockDiagnosticStore = new MockDiagnosticStore(); + using (var sp = MakeStartedStreamingDataSource(mockDiagnosticStore)) + { + mockEventSource.RaiseError(new ExceptionEventArgs(new EventSourceServiceUnsuccessfulResponseException(400))); + + var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); + Assert.True(streamInit.Failed); + } + } string UpdatedFlag() { @@ -256,9 +285,10 @@ public Task StartAsync() public void Restart(bool withDelay) { } - public void RaiseMessageRcvd(MessageReceivedEventArgs eventArgs) - { - MessageReceived(null, eventArgs); - } + public void RaiseError(ExceptionEventArgs e) => Error(null, e); + + public void RaiseMessageRcvd(MessageReceivedEventArgs e) => MessageReceived(null, e); + + public void RaiseOpened(StateChangedEventArgs e) => Opened(null, e); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 93086deb..60507ead 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -257,12 +257,13 @@ public void IdentifyCanTimeOutSync(UpdateMode mode) } [Theory] - [InlineData("", "/mobile/events/bulk")] - [InlineData("/basepath", "/basepath/mobile/events/bulk")] - [InlineData("/basepath/", "/basepath/mobile/events/bulk")] + [InlineData("", "/mobile/events/bulk", "/mobile/events/diagnostic")] + [InlineData("/basepath", "/basepath/mobile/events/bulk", "/basepath/mobile/events/diagnostic")] + [InlineData("/basepath/", "/basepath/mobile/events/bulk", "/basepath/mobile/events/diagnostic")] public void EventsAreSentToCorrectEndpointAsync( string baseUriExtraPath, - string expectedPath + string expectedAnalyticsPath, + string expectedDiagnosticsPath ) { using (var server = HttpServer.Start(Handlers.Status(202))) @@ -276,11 +277,23 @@ string expectedPath using (var client = TestUtil.CreateClient(config, _user)) { client.Flush(); - var req = server.Recorder.RequireRequest(TimeSpan.FromSeconds(5)); + var req1 = server.Recorder.RequireRequest(TimeSpan.FromSeconds(5)); + var req2 = server.Recorder.RequireRequest(TimeSpan.FromSeconds(5)); - Assert.Equal("POST", req.Method); - Assert.Equal(expectedPath, req.Path); - Assert.Equal(LdValueType.Array, LdValue.Parse(req.Body).Type); + if (req1.Path.EndsWith("diagnostic")) + { + var temp = req1; + req1 = req2; + req2 = temp; + } + + Assert.Equal("POST", req1.Method); + Assert.Equal(expectedAnalyticsPath, req1.Path); + Assert.Equal(LdValueType.Array, LdValue.Parse(req1.Body).Type); + + Assert.Equal("POST", req2.Method); + Assert.Equal(expectedDiagnosticsPath, req2.Path); + Assert.Equal(LdValueType.Object, LdValue.Parse(req2.Body).Type); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index e2fc43af..8e97def9 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -16,8 +16,12 @@ + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs new file mode 100644 index 00000000..c3c08f1d --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -0,0 +1,363 @@ +using System; +using System.Net; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Events; +using LaunchDarkly.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +using static LaunchDarkly.Sdk.Client.TestHttpUtils; +using static LaunchDarkly.TestHelpers.JsonAssertions; +using static LaunchDarkly.TestHelpers.JsonTestValue; + +namespace LaunchDarkly.Sdk.Client +{ + public class LdClientDiagnosticEventTest : BaseTest + { + // These tests cover the basic functionality of sending diagnostic events, and + // also verify that properties set with Configuration.Builder show up correctly + // in the configuration part of the diagnostic data. The lower-level details of + // how diagnostic events are accumulated in memory and delivered are tested in + // LaunchDarkly.InternalSdk, and the details of how stream connection data is + // logged in diagnostic events is tested in StreamProcessorTest. + + internal static readonly TimeSpan testStartWaitTime = TimeSpan.FromMilliseconds(1); + + private MockEventSender _testEventSender = new MockEventSender { FilterKind = EventDataKind.DiagnosticEvent }; + + public LdClientDiagnosticEventTest(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void NoDiagnosticInitEventIsSentIfOptedOut() + { + var config = BasicConfig() + .DiagnosticOptOut(true) + .Events(Components.SendEvents().EventSender(_testEventSender)) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + _testEventSender.RequireNoPayloadSent(TimeSpan.FromMilliseconds(100)); + } + } + + [Fact] + public void DiagnosticInitEventIsSent() + { + var testWrapperName = "wrapper-name"; + var testWrapperVersion = "1.2.3"; + var expectedSdk = JsonOf(LdValue.BuildObject() + .Add("name", "dotnet-client-sdk") + .Add("version", AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) + .Add("wrapperName", testWrapperName) + .Add("wrapperVersion", testWrapperVersion) + .Build().ToJsonString()); + + var config = BasicConfig() + .Events(Components.SendEvents().EventSender(_testEventSender)) + .Http(Components.HttpConfiguration().Wrapper(testWrapperName, testWrapperVersion)) + .Build(); + + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var payload = _testEventSender.RequirePayload(); + + Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind); + Assert.Equal(1, payload.EventCount); + + var data = JsonOf(payload.Data); + AssertJsonEqual(JsonFromValue("diagnostic-init"), data.Property("kind")); + AssertJsonEqual(expectedSdk, data.Property("sdk")); + AssertJsonEqual(JsonFromValue(BasicMobileKey.Substring(BasicMobileKey.Length - 6)), + data.Property("id").Property("sdkKeySuffix")); + + data.RequiredProperty("creationDate"); + } + } + + [Fact] + public void DiagnosticPeriodicEventsAreSent() + { + var config = BasicConfig() + .Events(Components.SendEvents() + .EventSender(_testEventSender) + .DiagnosticRecordingIntervalNoMinimum(TimeSpan.FromMilliseconds(50))) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + var payload1 = _testEventSender.RequirePayload(); + + Assert.Equal(EventDataKind.DiagnosticEvent, payload1.Kind); + Assert.Equal(1, payload1.EventCount); + var data1 = LdValue.Parse(payload1.Data); + Assert.Equal("diagnostic-init", data1.Get("kind").AsString); + var timestamp1 = data1.Get("creationDate").AsLong; + Assert.NotEqual(0, timestamp1); + + var payload2 = _testEventSender.RequirePayload(); + + Assert.Equal(EventDataKind.DiagnosticEvent, payload2.Kind); + Assert.Equal(1, payload2.EventCount); + var data2 = LdValue.Parse(payload2.Data); + Assert.Equal("diagnostic", data2.Get("kind").AsString); + var timestamp2 = data2.Get("creationDate").AsLong; + Assert.InRange(timestamp2, timestamp1, timestamp1 + 1000); + + var payload3 = _testEventSender.RequirePayload(); + + Assert.Equal(EventDataKind.DiagnosticEvent, payload3.Kind); + Assert.Equal(1, payload3.EventCount); + var data3 = LdValue.Parse(payload3.Data); + Assert.Equal("diagnostic", data3.Get("kind").AsString); + var timestamp3 = data2.Get("creationDate").AsLong; + Assert.InRange(timestamp3, timestamp2, timestamp1 + 1000); + } + } + + [Fact] + public void DiagnosticPeriodicEventsAreNotSentWhenInBackground() + { + var mockBackgroundModeManager = new MockBackgroundModeManager(); + var config = BasicConfig() + .BackgroundModeManager(mockBackgroundModeManager) + .Events(Components.SendEvents() + .EventSender(_testEventSender) + .DiagnosticRecordingIntervalNoMinimum(TimeSpan.FromMilliseconds(50))) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + mockBackgroundModeManager.UpdateBackgroundMode(true); + + // We will probably still get some periodic events before this mode change is picked + // up asynchronously, but we should stop getting them soon. + Assertions.AssertEventually(TimeSpan.FromMilliseconds(400), TimeSpan.FromMilliseconds(10), + () => !_testEventSender.Calls.TryTake(out _, TimeSpan.FromMilliseconds(100))); + + mockBackgroundModeManager.UpdateBackgroundMode(false); + + _testEventSender.RequirePayload(); + } + } + + [Fact] + public void ConfigDefaults() + { + // Note that in all of the test configurations where the streaming or polling data source + // is enabled, we're setting a fake HTTP message handler so it doesn't try to do any real + // HTTP requests that would fail and (depending on timing) disrupt the test. + TestDiagnosticConfig( + c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + null, + ExpectedConfigProps.Base() + ); + } + + [Fact] + public void CustomConfigForStreaming() + { + TestDiagnosticConfig( + c => c.DataSource( + Components.StreamingDataSource() + .BackgroundPollInterval(TimeSpan.FromDays(1)) + .InitialReconnectDelay(TimeSpan.FromSeconds(2)) + ) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + null, + ExpectedConfigProps.Base() + .Set("backgroundPollingIntervalMillis", TimeSpan.FromDays(1).TotalMilliseconds) + .Set("reconnectTimeMillis", 2000) + ); + } + + [Fact] + public void CustomConfigForPolling() + { + TestDiagnosticConfig( + c => c.DataSource(Components.PollingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + null, + ExpectedConfigProps.Base().WithPollingDefaults() + ); + + TestDiagnosticConfig( + c => c.DataSource( + Components.PollingDataSource() + .PollInterval(TimeSpan.FromDays(1)) + ) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + null, + ExpectedConfigProps.Base().WithPollingDefaults() + .Set("pollingIntervalMillis", TimeSpan.FromDays(1).TotalMilliseconds) + ); + } + + [Fact] + public void CustomConfigForEvents() + { + TestDiagnosticConfig( + c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + e => e.AllAttributesPrivate(true) + .Capacity(333) + .DiagnosticRecordingInterval(TimeSpan.FromMinutes(32)) + .FlushInterval(TimeSpan.FromMilliseconds(555)) + .InlineUsersInEvents(true), + ExpectedConfigProps.Base() + .Set("allAttributesPrivate", true) + .Set("diagnosticRecordingIntervalMillis", TimeSpan.FromMinutes(32).TotalMilliseconds) + .Set("eventsCapacity", 333) + .Set("eventsFlushIntervalMillis", 555) + .Set("inlineUsersInEvents", true) + ); + } + + [Fact] + public void CustomConfigForHTTP() + { + TestDiagnosticConfig( + c => c.Http( + Components.HttpConfiguration() + .ConnectTimeout(TimeSpan.FromMilliseconds(8888)) + .ReadTimeout(TimeSpan.FromMilliseconds(9999)) + .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + .UseReport(true) + ), + null, + ExpectedConfigProps.Base() + .Set("connectTimeoutMillis", 8888) + .Set("socketTimeoutMillis", 9999) + .Set("useReport", true) + ); + + var proxyUri = new Uri("http://fake"); + var proxy = new WebProxy(proxyUri); + TestDiagnosticConfig( + c => c.Http( + Components.HttpConfiguration() + .Proxy(proxy) + .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + ), + null, + ExpectedConfigProps.Base() + .Set("usingProxy", true) + ); + + var credentials = new CredentialCache(); + credentials.Add(proxyUri, "Basic", new NetworkCredential("user", "pass")); + var proxyWithAuth = new WebProxy(proxyUri); + proxyWithAuth.Credentials = credentials; + TestDiagnosticConfig( + c => c.Http( + Components.HttpConfiguration() + .Proxy(proxyWithAuth) + .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + ), + null, + ExpectedConfigProps.Base() + .Set("usingProxy", true) + .Set("usingProxyAuthenticator", true) + ); + } + + [Fact] + public void TestConfigForServiceEndpoints() + { + TestDiagnosticConfig( + c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + null, + ExpectedConfigProps.Base() + .Set("customBaseURI", true) + .Set("customStreamURI", true) + .Set("customEventsURI", true) + ); + + TestDiagnosticConfig( + c => c.ServiceEndpoints(Components.ServiceEndpoints() + .Streaming("http://custom-streaming") + .Polling("http://custom-polling") + .Events("http://custom-events")) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + null, + ExpectedConfigProps.Base() + .Set("customBaseURI", true) + .Set("customStreamURI", true) + .Set("customEventsURI", true) + ); + + TestDiagnosticConfig( + c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) + .DataSource(Components.PollingDataSource()) + .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + null, + ExpectedConfigProps.Base() + .WithPollingDefaults() + .Set("customBaseURI", true) + .Set("customEventsURI", true) + ); + } + + private void TestDiagnosticConfig( + Func modConfig, + Func modEvents, + LdValue.ObjectBuilder expected + ) + { + var eventsBuilder = Components.SendEvents() + .EventSender(_testEventSender); + modEvents?.Invoke(eventsBuilder); + var configBuilder = BasicConfig() + .DataSource(Components.StreamingDataSource()) + .Events(eventsBuilder) + .Http(Components.HttpConfiguration().MessageHandler(new StubMessageHandler(HttpStatusCode.Unauthorized))); + modConfig?.Invoke(configBuilder); + using (var client = TestUtil.CreateClient(configBuilder.Build(), BasicUser, testStartWaitTime)) + { + var data = ExpectDiagnosticEvent(); + + AssertJsonEqual(JsonFromValue("diagnostic-init"), data.Property("kind")); + + AssertJsonEqual(JsonOf(expected.Build().ToJsonString()), data.Property("configuration")); + } + } + + private JsonTestValue ExpectDiagnosticEvent() + { + var payload = _testEventSender.RequirePayload(); + Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind); + Assert.Equal(1, payload.EventCount); + return JsonOf(payload.Data); + } + } + + static class ExpectedConfigProps + { + public static LdValue.ObjectBuilder Base() => + LdValue.BuildObject() + .Add("allAttributesPrivate", false) + .Add("autoAliasingOptOut", false) + .Add("backgroundPollingDisabled", false) + .Add("backgroundPollingIntervalMillis", Configuration.DefaultBackgroundPollInterval.TotalMilliseconds) + .Add("customBaseURI", false) + .Add("connectTimeoutMillis", HttpConfigurationBuilder.DefaultConnectTimeout.TotalMilliseconds) + .Add("customEventsURI", false) + .Add("customStreamURI", false) + .Add("diagnosticRecordingIntervalMillis", EventProcessorBuilder.DefaultDiagnosticRecordingInterval.TotalMilliseconds) + .Add("evaluationReasonsRequested", false) + .Add("eventsCapacity", EventProcessorBuilder.DefaultCapacity) + .Add("eventsFlushIntervalMillis", EventProcessorBuilder.DefaultFlushInterval.TotalMilliseconds) + .Add("inlineUsersInEvents", false) + .Add("reconnectTimeMillis", StreamingDataSourceBuilder.DefaultInitialReconnectDelay.TotalMilliseconds) + .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultReadTimeout.TotalMilliseconds) + .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds) + .Add("streamingDisabled", false) + .Add("useReport", false) + .Add("usingProxy", false) + .Add("usingProxyAuthenticator", false); + + public static LdValue.ObjectBuilder WithPollingDefaults(this LdValue.ObjectBuilder builder) => + builder.Set("pollingIntervalMillis", PollingDataSourceBuilder.DefaultPollInterval.TotalMilliseconds) + .Set("streamingDisabled", true) + .Remove("customStreamURI") + .Remove("reconnectTimeMillis"); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs index 4be00a67..a93ac6fd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientServiceEndpointsTests.cs @@ -35,7 +35,7 @@ public void DefaultStreamingDataSourceBaseUri() BasicUser)) { var req = _stubHandler.Requests.ExpectValue(); - Assert.Equal(StandardEndpoints.DefaultStreamingBaseUri, BaseUriOf(req.RequestUri)); + Assert.Equal(StandardEndpoints.BaseUris.StreamingBaseUri, BaseUriOf(req.RequestUri)); } } @@ -50,7 +50,7 @@ public void DefaultPollingDataSourceBaseUri() BasicUser)) { var req = _stubHandler.Requests.ExpectValue(); - Assert.Equal(StandardEndpoints.DefaultPollingBaseUri, BaseUriOf(req.RequestUri)); + Assert.Equal(StandardEndpoints.BaseUris.PollingBaseUri, BaseUriOf(req.RequestUri)); } } @@ -65,7 +65,7 @@ public void DefaultEventsBaseUri() BasicUser)) { var req = _stubHandler.Requests.ExpectValue(); - Assert.Equal(StandardEndpoints.DefaultEventsBaseUri, BaseUriOf(req.RequestUri)); + Assert.Equal(StandardEndpoints.BaseUris.EventsBaseUri, BaseUriOf(req.RequestUri)); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 8e06a750..b9a97e0e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; @@ -6,6 +7,7 @@ using LaunchDarkly.Sdk.Client.Internal.DataSources; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; +using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.TestHelpers; using Xunit; @@ -13,6 +15,10 @@ namespace LaunchDarkly.Sdk.Client { + // Even more so than in the server-side SDK tests, we rely on our own concrete mock + // implementations of our component interfaces rather than using Moq to create mocks + // dynamically, because runtime differences cause Moq to fail on some mobile platforms. + internal static class MockComponentExtensions { public static IDataSourceFactory AsSingletonFactory(this IDataSource instance) => @@ -152,6 +158,35 @@ public string UniqueDeviceId() } } + internal class MockDiagnosticStore : IDiagnosticStore + { + internal struct StreamInit + { + internal DateTime Timestamp; + internal TimeSpan Duration; + internal bool Failed; + } + + internal readonly EventSink StreamInits = new EventSink(); + + public DateTime DataSince => DateTime.Now; + + public DiagnosticEvent? InitEvent => null; + + public DiagnosticEvent? PersistedUnsentEvent => null; + + public void AddStreamInit(DateTime timestamp, TimeSpan duration, bool failed) => + StreamInits.Enqueue(new StreamInit { Timestamp = timestamp, Duration = duration, Failed = failed }); + + public DiagnosticEvent CreateEventAndReset() => new DiagnosticEvent(); + + public void IncrementDeduplicatedUsers() { } + + public void IncrementDroppedEvents() { } + + public void RecordEventsInBatch(long eventsInBatch) { } + } + internal class MockEventProcessor : IEventProcessor { public List Events = new List(); @@ -179,6 +214,49 @@ public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) => Events.Add(e); } + public class MockEventSender : IEventSender + { + public BlockingCollection Calls = new BlockingCollection(); + public EventDataKind? FilterKind = null; + + public void Dispose() { } + + public struct Params + { + public EventDataKind Kind; + public string Data; + public int EventCount; + } + + public Task SendEventDataAsync(EventDataKind kind, string data, int eventCount) + { + if (!FilterKind.HasValue || kind == FilterKind.Value) + { + Calls.Add(new Params { Kind = kind, Data = data, EventCount = eventCount }); + } + return Task.FromResult(new EventSenderResult(DeliveryStatus.Succeeded, null)); + } + + public Params RequirePayload() + { + Params result; + if (!Calls.TryTake(out result, TimeSpan.FromSeconds(5))) + { + throw new System.Exception("did not receive an event payload"); + } + return result; + } + + public void RequireNoPayloadSent(TimeSpan timeout) + { + Params result; + if (Calls.TryTake(out result, timeout)) + { + throw new System.Exception("received an unexpected event payload"); + } + } + } + internal class MockFeatureFlagRequestor : IFeatureFlagRequestor { private readonly string _jsonFlags; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs new file mode 100644 index 00000000..13444bdb --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs @@ -0,0 +1,43 @@ +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace LaunchDarkly.Sdk.Client +{ + internal static class TestHttpUtils + { + internal class StubMessageHandler : HttpMessageHandler + { + private readonly HttpStatusCode _status; + private readonly string _body; + private readonly string _contentType; + + internal StubMessageHandler(HttpStatusCode status) : this(status, null, null) { } + + internal StubMessageHandler(HttpStatusCode status, string body, string contentType) + { + _status = status; + _body = body; + _contentType = contentType; + } + + internal static StubMessageHandler EmptyPollingResponse() => + new StubMessageHandler(HttpStatusCode.OK, "{}", "application/json"); + + internal static StubMessageHandler EmptyStreamingResponse() => + new StubMessageHandler(HttpStatusCode.OK, "", "text/event-stream"); + + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + var resp = new HttpResponseMessage(_status); + if (_body != null) + { + resp.Content = new StringContent(_body, System.Text.Encoding.UTF8, _contentType); + } + return Task.FromResult(new HttpResponseMessage(_status)); + } + } + } +} From 78b51abaa446a1679d0ef3983d7ede6aab85fffd Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 28 Oct 2021 15:02:20 -0700 Subject: [PATCH 375/499] fix HttpMessageHandler + proxy configuration, add test coverage (#141) * fix HttpMessageHandler + proxy configuration, add test coverage * fix test package dependencies * re-fix dependencies --- .../Integrations/HttpConfigurationBuilder.cs | 2 +- .../DataSources/StreamingDataSource.cs | 2 +- .../PlatformSpecific/Http.android.cs | 13 ++- .../PlatformSpecific/Http.ios.cs | 15 ++- .../PlatformSpecific/Http.netstandard.cs | 9 +- .../PlatformSpecific/Http.shared.cs | 22 +++- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 2 +- .../LDClientEndToEndTests.cs | 89 +++++++++++---- .../LaunchDarkly.ClientSdk.Tests.csproj | 2 +- .../LdClientDiagnosticEventTest.cs | 26 +++-- .../MockResponses.cs | 29 +++++ .../ModelBuilders.cs | 9 ++ .../TestHttpUtils.cs | 108 ++++++++++++++---- .../LaunchDarkly.ClientSdk.iOS.Tests.csproj | 2 +- 14 files changed, 252 insertions(+), 78 deletions(-) create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 63ecfa46..f4fd2278 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -285,7 +285,7 @@ private HttpProperties MakeHttpProperties(BasicConfiguration basic) Func handlerFn; if (_messageHandler is null) { - handlerFn = p => PlatformSpecific.Http.CreateHttpMessageHandler(p.ConnectTimeout, p.ReadTimeout); + handlerFn = PlatformSpecific.Http.GetHttpMessageHandlerFactory(_connectTimeout, _readTimeout, _proxy); } else { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index b8f41b3d..3f47c558 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -121,7 +121,7 @@ string jsonBody { var configBuilder = EventSource.Configuration.Builder(uri) .Method(method) - .HttpMessageHandler(httpProperties.HttpMessageHandlerFactory(httpProperties)) + .HttpMessageHandler(httpProperties.NewHttpMessageHandler()) .ResponseStartTimeout(httpProperties.ConnectTimeout) .InitialRetryDelay(_initialReconnectDelay) .ReadTimeout(LaunchDarklyStreamReadTimeout) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs index ddd623cc..37953780 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs @@ -1,16 +1,23 @@ using System; +using System.Net; using System.Net.Http; +using LaunchDarkly.Sdk.Internal.Http; using Xamarin.Android.Net; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => - new AndroidClientHandler() + private static Func PlatformGetHttpMessageHandlerFactory( + TimeSpan connectTimeout, + TimeSpan readTimeout, + IWebProxy proxy + ) => + p => new AndroidClientHandler() { ConnectTimeout = connectTimeout, - ReadTimeout = readTimeout + ReadTimeout = readTimeout, + Proxy = proxy }; private static Exception PlatformTranslateHttpException(Exception e) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs index 1f2eaa96..91bdf861 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs @@ -1,12 +1,23 @@ using System; +using System.Net; using System.Net.Http; +using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => - new NSUrlSessionHandler(); + // NSUrlSessionHandler is the preferred native implementation of HttpMessageHandler on iOS. + // However, it does not support programmatically setting a proxy, so if a proxy was specified + // we must fall back to the non-native .NET implementation. + private static Func PlatformGetHttpMessageHandlerFactory( + TimeSpan connectTimeout, + TimeSpan readTimeout, + IWebProxy proxy + ) => + (proxy is null) + ? (p => (HttpMessageHandler)new NSUrlSessionHandler()) + : (Func)null; // NSUrlSessionHandler doesn't appear to throw platform-specific exceptions that we care about private static Exception PlatformTranslateHttpException(Exception e) => e; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs index c0e0bdb7..01ad76fa 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs @@ -1,14 +1,17 @@ using System; +using System.Net; using System.Net.Http; +using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class Http { - private static HttpMessageHandler PlatformCreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => + private static Func PlatformGetHttpMessageHandlerFactory( + TimeSpan connectTimeout, TimeSpan readTimeout, IWebProxy proxy) => null; - // Setting the HttpClient's message handler to null means it will use the default .NET implementation, - // which already correctly implements timeouts based on the client configuration. + // Returning null means HttpProperties will use the default .NET implementation, + // which will take care of configuring the HTTP client with timeouts/proxies. private static Exception PlatformTranslateHttpException(Exception e) => e; } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs index f4b25927..ab9dc787 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs @@ -1,5 +1,7 @@ using System; +using System.Net; using System.Net.Http; +using LaunchDarkly.Sdk.Internal.Http; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { @@ -7,15 +9,23 @@ internal static partial class Http { /// /// If our default configuration should use a specific - /// implementation, returns that implementation. + /// implementation, returns a factory for that implementation. /// /// - /// The timeouts are passed in because the Xamarin Android implementation does not actually - /// look at the configured timeouts from HttpClient. + /// The return value is a factory, rather than having this method itself be the factory, + /// because of how our shared HttpProperties class is implemented: if you pass a + /// non-null MessageHandler factory function, then it assumes you will definitely be + /// returning a fully configured handler, so we would not be able to conditionally fall + /// back to the default .NET implementation without duplicating the proxy/timeout setup + /// logic here. /// - /// an HTTP handler implementation or null - public static HttpMessageHandler CreateHttpMessageHandler(TimeSpan connectTimeout, TimeSpan readTimeout) => - PlatformCreateHttpMessageHandler(connectTimeout, readTimeout); + /// an HTTP message handler factory or null + public static Func GetHttpMessageHandlerFactory( + TimeSpan connectTimeout, + TimeSpan readTimeout, + IWebProxy proxy + ) => + PlatformGetHttpMessageHandlerFactory(connectTimeout, readTimeout, proxy); /// /// Converts any platform-specific exceptions that might be thrown by the platform-specific diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index 1fc62718..795c7556 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -67,7 +67,7 @@ - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 60507ead..219a665f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -10,6 +10,7 @@ using Xunit.Abstractions; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.MockResponses; namespace LaunchDarkly.Sdk.Client { @@ -474,26 +475,69 @@ public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) } } - [Theory] - [MemberData(nameof(PollingAndStreaming))] - public void DateLikeStringValueIsStillParsedAsString(UpdateMode mode) + [Fact] + public void HttpConfigurationIsAppliedToStreaming() { - // Newtonsoft.Json's default behavior is to transform ISO date/time strings into DateTime objects. We - // definitely don't want that. Verify that we're disabling that behavior when we parse flags. - const string dateLikeString1 = "1970-01-01T00:00:01.001Z"; - const string dateLikeString2 = "1970-01-01T00:00:01Z"; - var flagData = new DataSetBuilder() - .Add("flag1", 1, LdValue.Of(dateLikeString1), 0) - .Add("flag2", 1, LdValue.Of(dateLikeString2), 0) - .Build(); - using (var server = HttpServer.Start(SetupResponse(flagData, mode))) - { - var config = BaseConfig(server.Uri, mode); - using (var client = TestUtil.CreateClient(config, _user)) + TestHttpUtils.TestWithSpecialHttpConfigurations( + StreamWithInitialData(_flagData1), + (targetUri, httpConfig, server) => { - VerifyFlagValues(client, flagData); - } - } + var config = BasicConfig() + .DataSource(Components.StreamingDataSource()) + .Http(httpConfig) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(targetUri)) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + VerifyFlagValues(client, _flagData1); + } + }, + testLogger + ); + } + + [Fact] + public void HttpConfigurationIsAppliedToPolling() + { + TestHttpUtils.TestWithSpecialHttpConfigurations( + PollingResponse(_flagData1), + (targetUri, httpConfig, server) => + { + var config = BasicConfig() + .DataSource(Components.PollingDataSource()) + .Http(httpConfig) + .ServiceEndpoints(Components.ServiceEndpoints().Polling(targetUri)) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + VerifyFlagValues(client, _flagData1); + } + }, + testLogger + ); + } + + [Fact] + public void HttpConfigurationIsAppliedToEvents() + { + TestHttpUtils.TestWithSpecialHttpConfigurations( + EventsAcceptedResponse, + (targetUri, httpConfig, server) => + { + var config = BasicConfig() + .DiagnosticOptOut(true) + .Events(Components.SendEvents()) + .Http(httpConfig) + .ServiceEndpoints(Components.ServiceEndpoints().Events(targetUri)) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + client.Flush(); + server.Recorder.RequireRequest(); + } + }, + testLogger + ); } private Configuration BaseConfig(Func extraConfig = null) @@ -526,10 +570,8 @@ private Configuration BaseConfig(Uri serverUri, UpdateMode mode, Func mode.IsStreaming - ? Handlers.SSE.Start() - .Then(Handlers.SSE.Event("put", PollingData(data))) - .Then(Handlers.SSE.LeaveOpen()) - : Handlers.BodyJson(PollingData(data)); + ? StreamWithInitialData(data) + : PollingResponse(data); private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) { @@ -566,9 +608,6 @@ private void VerifyNoFlagValues(ILdClient client, FullDataSet flags) Assert.Equal(LdValue.Null, client.JsonVariation(e.Key, LdValue.Null)); } } - - private static string PollingData(FullDataSet flags) => - TestUtil.MakeJsonData(flags); } public class UpdateMode diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index 8e97def9..a0b2b17d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -5,7 +5,7 @@ LaunchDarkly.Sdk.Client - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs index c3c08f1d..f8213553 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -4,9 +4,11 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.TestHelpers; +using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.MockResponses; using static LaunchDarkly.Sdk.Client.TestHttpUtils; using static LaunchDarkly.TestHelpers.JsonAssertions; using static LaunchDarkly.TestHelpers.JsonTestValue; @@ -146,7 +148,7 @@ public void ConfigDefaults() // is enabled, we're setting a fake HTTP message handler so it doesn't try to do any real // HTTP requests that would fail and (depending on timing) disrupt the test. TestDiagnosticConfig( - c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + c => c.Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), null, ExpectedConfigProps.Base() ); @@ -161,7 +163,7 @@ public void CustomConfigForStreaming() .BackgroundPollInterval(TimeSpan.FromDays(1)) .InitialReconnectDelay(TimeSpan.FromSeconds(2)) ) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), null, ExpectedConfigProps.Base() .Set("backgroundPollingIntervalMillis", TimeSpan.FromDays(1).TotalMilliseconds) @@ -174,7 +176,7 @@ public void CustomConfigForPolling() { TestDiagnosticConfig( c => c.DataSource(Components.PollingDataSource()) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(PollingResponse().AsMessageHandler())), null, ExpectedConfigProps.Base().WithPollingDefaults() ); @@ -184,7 +186,7 @@ public void CustomConfigForPolling() Components.PollingDataSource() .PollInterval(TimeSpan.FromDays(1)) ) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(PollingResponse().AsMessageHandler())), null, ExpectedConfigProps.Base().WithPollingDefaults() .Set("pollingIntervalMillis", TimeSpan.FromDays(1).TotalMilliseconds) @@ -195,7 +197,7 @@ public void CustomConfigForPolling() public void CustomConfigForEvents() { TestDiagnosticConfig( - c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + c => c.Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), e => e.AllAttributesPrivate(true) .Capacity(333) .DiagnosticRecordingInterval(TimeSpan.FromMinutes(32)) @@ -218,7 +220,7 @@ public void CustomConfigForHTTP() Components.HttpConfiguration() .ConnectTimeout(TimeSpan.FromMilliseconds(8888)) .ReadTimeout(TimeSpan.FromMilliseconds(9999)) - .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + .MessageHandler(StreamWithInitialData().AsMessageHandler()) .UseReport(true) ), null, @@ -234,7 +236,7 @@ public void CustomConfigForHTTP() c => c.Http( Components.HttpConfiguration() .Proxy(proxy) - .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + .MessageHandler(StreamWithInitialData().AsMessageHandler()) ), null, ExpectedConfigProps.Base() @@ -249,7 +251,7 @@ public void CustomConfigForHTTP() c => c.Http( Components.HttpConfiguration() .Proxy(proxyWithAuth) - .MessageHandler(StubMessageHandler.EmptyStreamingResponse()) + .MessageHandler(StreamWithInitialData().AsMessageHandler()) ), null, ExpectedConfigProps.Base() @@ -263,7 +265,7 @@ public void TestConfigForServiceEndpoints() { TestDiagnosticConfig( c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), null, ExpectedConfigProps.Base() .Set("customBaseURI", true) @@ -276,7 +278,7 @@ public void TestConfigForServiceEndpoints() .Streaming("http://custom-streaming") .Polling("http://custom-polling") .Events("http://custom-events")) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), null, ExpectedConfigProps.Base() .Set("customBaseURI", true) @@ -287,7 +289,7 @@ public void TestConfigForServiceEndpoints() TestDiagnosticConfig( c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom")) .DataSource(Components.PollingDataSource()) - .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())), + .Http(Components.HttpConfiguration().MessageHandler(StreamWithInitialData().AsMessageHandler())), null, ExpectedConfigProps.Base() .WithPollingDefaults() @@ -308,7 +310,7 @@ LdValue.ObjectBuilder expected var configBuilder = BasicConfig() .DataSource(Components.StreamingDataSource()) .Events(eventsBuilder) - .Http(Components.HttpConfiguration().MessageHandler(new StubMessageHandler(HttpStatusCode.Unauthorized))); + .Http(Components.HttpConfiguration().MessageHandler(Error401Response.AsMessageHandler())); modConfig?.Invoke(configBuilder); using (var client = TestUtil.CreateClient(configBuilder.Build(), BasicUser, testStartWaitTime)) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs new file mode 100644 index 00000000..6e31b906 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs @@ -0,0 +1,29 @@ +using LaunchDarkly.TestHelpers.HttpTest; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client +{ + public static class MockResponses + { + public static Handler Error401Response => Handlers.Status(401); + + public static Handler Error503Response => Handlers.Status(503); + + public static Handler EventsAcceptedResponse => Handlers.Status(202); + + public static Handler PollingResponse(FullDataSet? data = null) => + Handlers.BodyJson((data ?? DataSetBuilder.Empty).ToJsonString()); + + public static Handler StreamWithInitialData(FullDataSet? data = null) => + Handlers.SSE.Start() + .Then(PutEvent(data)) + .Then(Handlers.SSE.LeaveOpen()); + + public static Handler PutEvent(FullDataSet? data = null) => + Handlers.SSE.Event( + "put", + (data ?? DataSetBuilder.Empty).ToJsonString() + ); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs index 71f62c91..f1e5f0f7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using LaunchDarkly.Sdk.Client.Internal; using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; @@ -93,6 +94,8 @@ internal class DataSetBuilder { private List> _items = new List>(); + public static FullDataSet Empty => new DataSetBuilder().Build(); + public DataSetBuilder Add(string key, FeatureFlag flag) { _items.Add(new KeyValuePair(key, flag.ToItemDescriptor())); @@ -110,4 +113,10 @@ public DataSetBuilder AddDeleted(string key, int version) public FullDataSet Build() => new FullDataSet(_items); } + + public static class DataSetExtensions + { + public static string ToJsonString(this FullDataSet data) => + DataModelSerialization.SerializeAll(data); + } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs index 13444bdb..7a41a2f5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs @@ -1,42 +1,106 @@ -using System.Net; +using System; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.TestHelpers.HttpTest; +using Xunit; namespace LaunchDarkly.Sdk.Client { internal static class TestHttpUtils { - internal class StubMessageHandler : HttpMessageHandler + // Used for TestWithSpecialHttpConfigurations + internal class MessageHandlerThatAddsPathSuffix : HttpClientHandler { - private readonly HttpStatusCode _status; - private readonly string _body; - private readonly string _contentType; + private readonly string _suffix; - internal StubMessageHandler(HttpStatusCode status) : this(status, null, null) { } + internal MessageHandlerThatAddsPathSuffix(string suffix) { _suffix = suffix; } - internal StubMessageHandler(HttpStatusCode status, string body, string contentType) + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) { - _status = status; - _body = body; - _contentType = contentType; + request.RequestUri = new Uri(request.RequestUri.ToString() + _suffix); + return base.SendAsync(request, cancellationToken); } + } - internal static StubMessageHandler EmptyPollingResponse() => - new StubMessageHandler(HttpStatusCode.OK, "{}", "application/json"); + // Used for TestWithSpecialHttpConfigurations + public delegate void HttpConfigurationTestAction(Uri targetUri, HttpConfigurationBuilder httpConfig, HttpServer server); - internal static StubMessageHandler EmptyStreamingResponse() => - new StubMessageHandler(HttpStatusCode.OK, "", "text/event-stream"); + /// + /// A test suite for all SDK components that support our standard HTTP configuration options. + /// + /// + /// + /// Although all of our supported HTTP behaviors are implemented in shared code, there is no + /// guarantee that all of our components are using that code, or using it correctly. So we + /// should run this test suite on each component that can be affected by HttpConfigurationBuilder + /// properties. + /// + /// + /// For each HTTP configuration variant that is expected work (e.g., using a proxy server), it + /// sets up a server that will produce whatever expected response was specified in + /// . Then it runs , + /// which should create its component with the given configuration and base URI and verify that + /// the component behaves correctly. + /// + /// + /// specifies how the target server should response + /// verifies the result of a test + /// the current TestLogger + public static void TestWithSpecialHttpConfigurations( + Handler responseHandler, + HttpConfigurationTestAction testActionShouldSucceed, + Logger log + ) + { + log.Info("*** TestHttpClientCanUseCustomMessageHandler"); + TestHttpClientCanUseCustomMessageHandler(responseHandler, testActionShouldSucceed); - protected override Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) + log.Info("*** TestHttpClientCanUseProxy"); + TestHttpClientCanUseProxy(responseHandler, testActionShouldSucceed); + } + + static void TestHttpClientCanUseCustomMessageHandler(Handler responseHandler, + HttpConfigurationTestAction testActionShouldSucceed) + { + // To verify that a custom HttpMessageHandler will really be used if provided, we + // create one that behaves normally except that it modifies the request path. + // Then we verify that the server received a request with a modified path. + + var recordAndDelegate = Handlers.Record(out var recorder).Then(responseHandler); + using (var server = HttpServer.Start(recordAndDelegate)) { - var resp = new HttpResponseMessage(_status); - if (_body != null) - { - resp.Content = new StringContent(_body, System.Text.Encoding.UTF8, _contentType); - } - return Task.FromResult(new HttpResponseMessage(_status)); + var suffix = "/modified-by-test"; + var messageHandler = new MessageHandlerThatAddsPathSuffix(suffix); + var httpConfig = Components.HttpConfiguration().MessageHandler(messageHandler); + + testActionShouldSucceed(server.Uri, httpConfig, server); + + var request = recorder.RequireRequest(); + Assert.EndsWith(suffix, request.Path); + recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100)); + } + } + + static void TestHttpClientCanUseProxy(Handler responseHandler, + HttpConfigurationTestAction testActionShouldSucceed) + { + // To verify that a web proxy will really be used if provided, we set up a proxy + // configuration pointing to our test server. It's not really a proxy server, + // but if it receives a request that was intended for some other URI (instead of + // the SDK trying to access that other URI directly), then that's a success. + + using (var server = HttpServer.Start(responseHandler)) + { + var proxy = new WebProxy(server.Uri); + var httpConfig = Components.HttpConfiguration().Proxy(proxy); + var fakeBaseUri = new Uri("http://not-a-real-host"); + + testActionShouldSucceed(fakeBaseUri, httpConfig, server); } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj index d22e6493..4697ad76 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj @@ -75,7 +75,7 @@ - + From 33ff003b62cf8bb22ee1bdcfa36edc597da09a74 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 1 Nov 2021 13:05:31 -0700 Subject: [PATCH 376/499] update MSBuild.Sdk.Extras to prevent spurious build warnings from .NET 5 tools --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 17032b75..679841e9 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "2.0.54" + "MSBuild.Sdk.Extras": "3.0.38" } } From 0bfc3d32604057174617a3008a2e5d2780938413 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 5 Nov 2021 13:57:38 -0700 Subject: [PATCH 377/499] new persistent storage implementation + max users limit (#143) --- src/LaunchDarkly.ClientSdk/Components.cs | 34 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 7 +- .../ConfigurationBuilder.cs | 38 +- .../PersistenceConfigurationBuilder.cs | 101 ++++ .../Interfaces/DataStoreTypes.cs | 2 +- .../Interfaces/IFlagTracker.cs | 8 + .../Interfaces/IPersistentDataStore.cs | 54 ++- .../Interfaces/IPersistentDataStoreFactory.cs | 6 +- src/LaunchDarkly.ClientSdk/Internal/Base64.cs | 9 + .../Internal/ComponentsImpl.cs | 26 +- .../Internal/Constants.cs | 1 - .../DataSources/DataSourceUpdateSinkImpl.cs | 14 +- .../DataStores/DefaultPersistentDataStore.cs | 29 -- .../Internal/DataStores/FlagDataManager.cs | 186 ++++++++ .../Internal/DataStores/InMemoryDataStore.cs | 60 --- .../DataStores/NullPersistentDataStore.cs | 24 + .../DataStores/PersistenceConfiguration.cs | 20 + .../DataStores/PersistentDataStoreWrapper.cs | 158 ++++--- .../Internal/DataStores/UserIndex.cs | 110 +++++ .../Internal/DefaultDeviceInfo.cs | 20 +- .../Internal/Factory.cs | 5 - .../Internal/Interfaces/IDataStore.cs | 65 --- .../Internal/UserDecorator.cs | 86 ++++ src/LaunchDarkly.ClientSdk/LdClient.cs | 93 ++-- .../ClientIdentifier.android.cs | 38 +- .../PlatformSpecific/ClientIdentifier.ios.cs | 10 +- .../ClientIdentifier.netstandard.cs | 5 +- .../ClientIdentifier.shared.cs | 40 +- .../PlatformSpecific/LocalStorage.android.cs | 43 ++ .../PlatformSpecific/LocalStorage.ios.cs | 56 +++ .../LocalStorage.netstandard.cs | 122 +++++ .../PlatformSpecific/LocalStorage.shared.cs | 17 + .../PlatformSpecific/Preferences.android.cs | 115 ----- .../PlatformSpecific/Preferences.ios.cs | 115 ----- .../Preferences.netstandard.cs | 170 ------- .../PlatformSpecific/Preferences.shared.cs | 144 ------ .../PlatformSpecific/UserMetadata.shared.cs | 9 +- .../AndroidSpecificTests.cs | 4 +- .../AssertHelpers.cs | 26 ++ .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 11 +- .../ConfigurationTest.cs | 4 +- .../DataSourceUpdateSinkImplTest.cs | 8 +- .../DataSources/PollingDataSourceTest.cs | 9 +- .../DataStores/FlagDataManagerTest.cs | 205 ++++++++ .../FlagDataManagerWithPersistenceTest.cs | 223 +++++++++ .../DataStores/InMemoryDataStoreTest.cs | 193 -------- .../PersistentDataStoreWrapperTest.cs | 145 +++--- .../DataStores/PlatformLocalStorageTest.cs | 100 ++++ .../Internal/DataStores/UserIndexTest.cs | 116 +++++ .../Internal/DefaultDeviceInfoTests.cs | 29 -- .../LDClientEndToEndTests.cs | 109 ++--- .../LdClientEvaluationTests.cs | 3 +- .../LdClientEventTests.cs | 15 +- .../LdClientTests.cs | 437 +++++++++--------- .../MockComponents.cs | 53 ++- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 11 - .../IOsSpecificTests.cs | 4 +- 57 files changed, 2104 insertions(+), 1641 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/DefaultDeviceInfoTests.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index c422fc71..a607bbca 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Internal.DataStores; namespace LaunchDarkly.Sdk.Client { @@ -151,6 +152,9 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// /// A configuration object that disables persistent storage. /// + /// + /// This is equivalent to `Persistence().MaxCachedUsers(0)`. + /// /// /// /// var config = Configuration.Builder(mobileKey) @@ -158,9 +162,33 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// .Build(); /// /// - /// - public static IPersistentDataStoreFactory NoPersistence => - ComponentsImpl.NullPersistentDataStoreFactory.Instance; + /// + /// + public static PersistenceConfigurationBuilder NoPersistence => + Persistence().Storage(NullPersistentDataStoreFactory.Instance).MaxCachedUsers(0); + + /// + /// Returns a configuration builder for the SDK's persistent storage configuration. + /// + /// + /// Passing this to , + /// after setting any desired properties on the builder, applies this configuration to the SDK. + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .Persistence( + /// Components.Persistence().MaxCachedUsers(10) + /// ) + /// .Build(); + /// + /// + /// a builder + /// + /// + /// + public static PersistenceConfigurationBuilder Persistence() => + new PersistenceConfigurationBuilder(); /// /// Returns a configurable factory for using only polling mode to get feature flag data. diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 7d017224..1af7d4e6 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -107,10 +107,9 @@ public sealed class Configuration public bool Offline { get; } /// - /// A factory object that creates an implementation of , for - /// saving flag values in persistent storage. + /// Persistent storage configuration properties for the SDK. /// - public IPersistentDataStoreFactory PersistentDataStoreFactory { get; } + public PersistenceConfigurationBuilder PersistenceConfigurationBuilder { get; } /// /// Defines the base service URIs used by SDK components. @@ -177,7 +176,7 @@ internal Configuration(ConfigurationBuilder builder) LoggingConfigurationBuilder = builder._loggingConfigurationBuilder; MobileKey = builder._mobileKey; Offline = builder._offline; - PersistentDataStoreFactory = builder._persistentDataStoreFactory; + PersistenceConfigurationBuilder = builder._persistenceConfigurationBuilder; ServiceEndpoints = (builder._serviceEndpointsBuilder ?? Components.ServiceEndpoints()).Build(); BackgroundModeManager = builder._backgroundModeManager; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index bc79996b..45207384 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -43,7 +43,7 @@ public sealed class ConfigurationBuilder internal LoggingConfigurationBuilder _loggingConfigurationBuilder = null; internal string _mobileKey; internal bool _offline = false; - internal IPersistentDataStoreFactory _persistentDataStoreFactory = null; + internal PersistenceConfigurationBuilder _persistenceConfigurationBuilder = null; internal ServiceEndpointsBuilder _serviceEndpointsBuilder = null; // Internal properties only settable for testing @@ -68,7 +68,7 @@ internal ConfigurationBuilder(Configuration copyFrom) _loggingConfigurationBuilder = copyFrom.LoggingConfigurationBuilder; _mobileKey = copyFrom.MobileKey; _offline = copyFrom.Offline; - _persistentDataStoreFactory = copyFrom.PersistentDataStoreFactory; + _persistenceConfigurationBuilder = copyFrom.PersistenceConfigurationBuilder; _serviceEndpointsBuilder = new ServiceEndpointsBuilder(copyFrom.ServiceEndpoints); } @@ -219,7 +219,7 @@ public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) /// If you want to set multiple options, set them on the same . /// /// a builder for HTTP configuration - /// the same builder + /// the top-level builder public ConfigurationBuilder Http(HttpConfigurationBuilder httpConfigurationBuilder) { _httpConfigurationBuilder = httpConfigurationBuilder; @@ -273,8 +273,8 @@ public ConfigurationBuilder Logging(ILogAdapter logAdapter) => /// .Logging(Components.Logging().Level(LogLevel.Warn))) /// .Build(); /// - /// the builder object - /// the same builder + /// a builder for logging configuration + /// the top-level builder /// /// /// @@ -311,7 +311,8 @@ public ConfigurationBuilder Offline(bool offline) } /// - /// Sets the implementation of the component that saves flag values for each user in persistent storage. + /// Sets the SDK's persistent storage configuration, using a configuration builder obtained from + /// . /// /// /// @@ -321,16 +322,27 @@ public ConfigurationBuilder Offline(bool offline) /// /// By default, the SDK uses a persistence mechanism that is specific to each platform: on Android and /// iOS it is the native preferences store, and in the .NET Standard implementation for desktop apps - /// it is the System.IO.IsolatedStorage API. You may substitute a custom implementation, or - /// pass to disable persistent storage and use only in-memory - /// storage. + /// it is the System.IO.IsolatedStorage API. You may use the builder methods to substitute a + /// custom implementation or change related parameters. + /// + /// + /// This overwrites any previous options set with this method. If you want to set multiple options, + /// set them on the same . /// /// - /// the factory object; null to use the default implementation - /// the same builder - public ConfigurationBuilder Persistence(IPersistentDataStoreFactory persistentDataStoreFactory) + /// + /// var config = Configuration.Builder("my-sdk-key") + /// .Persistence(Components.Persistence().MaxCachedUsers(10)) + /// .Build(); + /// + /// a builder for persistence configuration + /// the top-level builder + /// + /// + /// + public ConfigurationBuilder Persistence(PersistenceConfigurationBuilder persistenceConfigurationBuilder) { - _persistentDataStoreFactory = persistentDataStoreFactory; + _persistenceConfigurationBuilder = persistenceConfigurationBuilder; return this; } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs new file mode 100644 index 00000000..5ed96e3b --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs @@ -0,0 +1,101 @@ +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.DataStores; + +namespace LaunchDarkly.Sdk.Client.Integrations +{ + /// + /// Contains methods for configuring the SDK's persistent storage behavior. + /// + /// + /// + /// The persistent storage mechanism allows the SDK to immediately access the last known flag data + /// for the user, if any, if it was started offline or has not yet received data from LaunchDarkly. + /// + /// + /// By default, the SDK uses a persistence mechanism that is specific to each platform, as + /// described in . To use a custom persistence + /// implementation, or to customize related properties defined in this class, create a builder with + /// , change its properties with the methods of this class, and + /// pass it to . + /// + /// + /// + /// + /// var config = Configuration.Builder(sdkKey) + /// .Persistence( + /// Components.Persistence().MaxCachedUsers(5) + /// ) + /// .Build(); + /// + /// + public sealed class PersistenceConfigurationBuilder + { + /// + /// Default value for : 5. + /// + public const int DefaultMaxCachedUsers = 5; + + /// + /// Passing this value (or any negative number) to + /// means there is no limit on cached user data. + /// + public const int UnlimitedCachedUsers = -1; + + private IPersistentDataStoreFactory _storeFactory = null; + private int _maxCachedUsers = DefaultMaxCachedUsers; + + internal PersistenceConfigurationBuilder() { } + + /// + /// Sets the storage implementation. + /// + /// + /// By default, the SDK uses a persistence mechanism that is specific to each platform: on Android and + /// iOS it is the native preferences store, and in the .NET Standard implementation for desktop apps + /// it is the System.IO.IsolatedStorage API. You may use this method to specify a custom + /// implementation using a factory object. + /// + /// a factory for the custom storage implementation, or + /// to use the default implementation + /// the builder + public PersistenceConfigurationBuilder Storage(IPersistentDataStoreFactory persistentDataStoreFactory) + { + _storeFactory = persistentDataStoreFactory; + return this; + } + + /// + /// Sets the maximum number of users to store flag data for. + /// + /// + /// + /// A value greater than zero means that the SDK will use persistent storage to remember the last + /// known flag values for up to that number of unique user keys. If the limit is exceeded, the SDK + /// discards the data for the least recently used user. + /// + /// + /// A value of zero means that the SDK will not use persistent storage; it will only have whatever + /// flag data it has received since the current LdClient instance was started. + /// + /// + /// A value of or any other negative number means there is no + /// limit. Use this mode with caution, as it could cause the size of mobile device preferences to + /// grow indefinitely if your application uses many different user keys on the same device. + /// + /// + /// + /// + public PersistenceConfigurationBuilder MaxCachedUsers(int maxCachedUsers) + { + _maxCachedUsers = maxCachedUsers; + return this; + } + + internal PersistenceConfiguration CreatePersistenceConfiguration(LdClientContext context) => + new PersistenceConfiguration( + _storeFactory is null ? PlatformSpecific.LocalStorage.Instance : + _storeFactory.CreatePersistentDataStore(context), + _maxCachedUsers + ); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs index bb24a15d..45c12091 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs @@ -72,7 +72,7 @@ public struct FullDataSet : IEquatable /// the feature flags, indexed by key public FullDataSet(IEnumerable> items) { - Items = items is null ? ImmutableList.Create>() : + Items = items is null ? ImmutableList>.Empty : ImmutableList.CreateRange(items); } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs index f249fde5..2b68f588 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs @@ -23,6 +23,14 @@ public interface IFlagTracker /// SDK has just received its very first set of flag values. /// /// + /// Currently this event will not fire in a scenario where 1. the client is offline, 2. + /// or + /// has been called to change the current user, and 3. the SDK had previously stored flag data + /// for that user (see ) and has + /// now loaded those flags. The event will only fire if the SDK has received new flag data + /// from LaunchDarkly or from . + /// + /// /// Notifications will be dispatched either on the main thread (on mobile platforms) or in a /// background task (on all other platforms). It is the listener's responsibility to return /// as soon as possible so as not to block subsequent notifications. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs index 2ad9981e..4ea285c7 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs @@ -1,20 +1,29 @@ using System; +using System.Collections.Immutable; namespace LaunchDarkly.Sdk.Client.Interfaces { /// - /// Interface for a data store that holds feature flag data in a serialized form. + /// Interface for a data store that holds feature flag data and other SDK properties in a + /// serialized form. /// /// /// /// This interface should be used for platform-specific integrations that store data somewhere - /// other than in memory. The SDK will take care of converting between its own internal data model - /// and a serialized string form; the data store interacts only with the serialized form. The data - /// store should not make any assumptions about the format of the serialized data. + /// other than in memory. The SDK has a default implementation which uses the native preferences + /// API on mobile platforms, and the .NET IsolatedStorage API in desktop applications. You + /// only need to use this interface if you want to provide different storage behavior. /// /// - /// Unlike server-side SDKs, the persistent data store in this SDK reads or writes the data for - /// all flags at once for a given user, instead of one flag at a time. This is for two reasons: + /// Each data item is defined by a "namespace" and a "key". Both of these are non-null and non-empty + /// strings defined by the SDK. Keys are unique within a namespace. The value of the data item is + /// simply a string. The store should not make any assumptions about the allowed content of these + /// strings. + /// + /// + /// Unlike server-side SDKs, the persistent data store in this SDK treats the entire set of flags + /// for a given user as a single value which is written to the store all at once, rather than one + /// value per flag. This is for two reasons: /// /// /// The SDK assumes that the persistent store cannot be written to by any other process, @@ -30,35 +39,32 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// SDK tells it to do. /// /// - /// Implementations must be thread-safe. + /// Implementations do not need to worry about thread-safety; the SDK will ensure that it only + /// calls one store method at a time. /// /// - /// Error handling is defined as follows: if any data store operation encounters an I/O - /// error, or is otherwise unable to complete its task, it should throw an exception to make - /// the SDK aware of this. + /// Error handling is defined as follows: if any data store operation encounters an I/O error, or + /// is otherwise unable to complete its task, it should throw an exception to make the SDK aware + /// of this. The SDK will decide whether to log the exception. /// /// /// public interface IPersistentDataStore : IDisposable { /// - /// Overwrites the store's contents for a specific user with a serialized data set. + /// Attempts to retrieve a string value from the store. /// - /// - /// - /// All previous data for the user should be discarded. This should not affect stored data for - /// any other user. For efficiency, the store can assume that only the user key is significant. - /// - /// - /// the current user - /// a serialized data set represented as an opaque string - void Init(User user, string allData); + /// the namespace identifier + /// the unique key within that namespace + /// the value, or null if not found + string GetValue(string storageNamespace, string key); /// - /// Retrieves the serialized data for a specific user, if available. + /// Attempts to update or remove a string value in the store. /// - /// the current user - /// the serialized data for that user, or null if there is none - string GetAll(User user); + /// the namespace identifier + /// the unique key within that namespace + /// the new value, or null to remove the key + void SetValue(string storageNamespace, string key, string value); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs index cd335342..b6cca53c 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs @@ -1,10 +1,12 @@ - +using LaunchDarkly.Sdk.Client.Integrations; + namespace LaunchDarkly.Sdk.Client.Interfaces { /// /// Interface for a factory that creates some implementation of . /// - /// + /// + /// public interface IPersistentDataStoreFactory { /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/Base64.cs b/src/LaunchDarkly.ClientSdk/Internal/Base64.cs index 466161bb..174040a0 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Base64.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Base64.cs @@ -1,13 +1,22 @@ using System; +using System.Security.Cryptography; +using System.Text; namespace LaunchDarkly.Sdk.Client.Internal { internal static class Base64 { + private static readonly SHA256 _hasher = SHA256.Create(); + public static string UrlSafeEncode(this string plainText) { var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); return Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_'); } + + public static string Sha256Hash(string input) => + Convert.ToBase64String( + _hasher.ComputeHash(Encoding.UTF8.GetBytes(input)) + ); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index b441808a..fa58f6f0 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -1,6 +1,6 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.DataStores; namespace LaunchDarkly.Sdk.Client.Internal { @@ -28,7 +28,7 @@ public void Dispose() { } internal sealed class NullEventProcessorFactory : IEventProcessorFactory { - internal static NullEventProcessorFactory Instance = new NullEventProcessorFactory(); + internal static readonly NullEventProcessorFactory Instance = new NullEventProcessorFactory(); public IEventProcessor CreateEventProcessor(LdClientContext context) => NullEventProcessor.Instance; @@ -36,7 +36,7 @@ public IEventProcessor CreateEventProcessor(LdClientContext context) => internal sealed class NullEventProcessor : IEventProcessor { - internal static NullEventProcessor Instance = new NullEventProcessor(); + internal static readonly NullEventProcessor Instance = new NullEventProcessor(); public void Dispose() { } @@ -53,23 +53,5 @@ public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) { } public void SetOffline(bool offline) { } } - internal sealed class NullPersistentDataStoreFactory : IPersistentDataStoreFactory - { - internal static NullPersistentDataStoreFactory Instance = new NullPersistentDataStoreFactory(); - - public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => - NullPersistentDataStore.Instance; - } - - internal sealed class NullPersistentDataStore : IPersistentDataStore - { - internal static NullPersistentDataStore Instance = new NullPersistentDataStore(); - - public string GetAll(User user) => null; - - public void Init(User user, string allData) { } - - public void Dispose() { } - } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs index 2a142f93..8775a8c2 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs @@ -5,7 +5,6 @@ internal static class Constants { public const string KEY = "key"; public const string VERSION = "version"; - public const string FLAGS_KEY_PREFIX = "flags:"; public const string CONTENT_TYPE = "Content-Type"; public const string APPLICATION_JSON = "application/json"; public const string PUT = "put"; diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index 8ec1e483..d39db681 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Concurrent; @@ -15,12 +15,12 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink { - private readonly IDataStore _dataStore; + private readonly FlagDataManager _dataStore; private readonly object _lastValuesLock = new object(); private readonly TaskExecutor _taskExecutor; private volatile ImmutableDictionary> _lastValues = - ImmutableDictionary.Create>(); + ImmutableDictionary>.Empty; private readonly StateMonitor _status; internal DataSourceStatus CurrentStatus => _status.Current; @@ -29,7 +29,7 @@ internal sealed class DataSourceUpdateSinkImpl : IDataSourceUpdateSink internal event EventHandler StatusChanged; internal DataSourceUpdateSinkImpl( - IDataStore dataStore, + FlagDataManager dataStore, bool isConfiguredOffline, TaskExecutor taskExecutor, Logger log @@ -48,7 +48,7 @@ Logger log public void Init(User user, FullDataSet data) { - _dataStore.Init(user, data); + _dataStore.Init(user, data, true); ImmutableDictionary oldValues, newValues; lock (_lastValuesLock) @@ -107,7 +107,7 @@ public void Init(User user, FullDataSet data) public void Upsert(User user, string key, ItemDescriptor data) { - var updated = _dataStore.Upsert(user, key, data); + var updated = _dataStore.Upsert(key, data); if (!updated) { return; @@ -120,7 +120,7 @@ public void Upsert(User user, string key, ItemDescriptor data) if (oldValues is null) { // didn't have any flags for this user - var initValues = ImmutableDictionary.Create(); + var initValues = ImmutableDictionary.Empty; if (data.Item != null) { initValues = initValues.SetItem(key, data.Item); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs deleted file mode 100644 index 09a19e3d..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/DefaultPersistentDataStore.cs +++ /dev/null @@ -1,29 +0,0 @@ -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class DefaultPersistentDataStore : IPersistentDataStore - { - private readonly Logger _log; - - internal DefaultPersistentDataStore(Logger log) - { - _log = log; - } - - public void Init(User user, string allData) => - PlatformSpecific.Preferences.Set(Constants.FLAGS_KEY_PREFIX + user.Key, allData, _log); - - string IPersistentDataStore.GetAll(User user) => - PlatformSpecific.Preferences.Get(Constants.FLAGS_KEY_PREFIX + user.Key, null, _log); - - public void Dispose() { } - } - - internal sealed class DefaultPersistentDataStoreFactory : IPersistentDataStoreFactory - { - public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => - new DefaultPersistentDataStore(context.BaseLogger.SubLogger(LogNames.DataStoreSubLog)); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs new file mode 100644 index 00000000..9aef9a58 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + /// + /// The component that maintains the state of last known flag values, and manages + /// persistent storage if enabled. + /// + /// + /// + /// The state of the consists of all the + /// s for a specific user, plus optionally persistent + /// storage for some number of other users. + /// + /// + /// This is not a pluggable component - there can only be one implementation. The only + /// piece of behavior that is platform-dependent and customizable is the implementation + /// of persistent storage, which is represented by the + /// interface. + /// + /// + internal sealed class FlagDataManager : IDisposable + { + private readonly int _maxCachedUsers; + private readonly PersistentDataStoreWrapper _persistentStore; + private readonly object _writerLock = new object(); + private readonly Logger _log; + + private volatile ImmutableDictionary _flags = + ImmutableDictionary.Empty; + private volatile UserIndex _storeIndex = null; + private volatile string _currentUserId = null; + + public PersistentDataStoreWrapper PersistentStore => _persistentStore; + + public FlagDataManager( + string mobileKey, + PersistenceConfiguration persistenceConfiguration, + Logger log + ) + { + _log = log; + + if (persistenceConfiguration is null || persistenceConfiguration.MaxCachedUsers == 0 + || persistenceConfiguration.PersistentDataStore is NullPersistentDataStore) + { + _persistentStore = null; + _maxCachedUsers = 0; + } + else + { + _persistentStore = new PersistentDataStoreWrapper( + persistenceConfiguration.PersistentDataStore, + mobileKey, + log + ); + _maxCachedUsers = persistenceConfiguration.MaxCachedUsers; + _storeIndex = _persistentStore.GetIndex(); + } + } + + /// + /// Attempts to retrieve cached data for the specified user, if any. This does not + /// affect the current user/flags state. + /// + /// a user + /// that user's data from the persistent store, or null if none + public FullDataSet? GetCachedData(User user) => + _persistentStore is null ? null : _persistentStore.GetUserData(UserIdFor(user)); + + /// + /// Replaces the current flag data and updates the current-user state, optionally + /// updating persistent storage as well. + /// + /// the user who should become the current user + /// the full flag data + /// true to also update the flag data in + /// persistent storage (if persistent storage is enabled) + public void Init(User user, FullDataSet data, bool updatePersistentStorage) + { + var newFlags = data.Items.ToImmutableDictionary(); + IEnumerable removedUserIds = null; + var userId = UserIdFor(user); + var updatedIndex = _storeIndex; + + lock (_writerLock) + { + _flags = newFlags; + + if (_storeIndex != null) + { + updatedIndex = _storeIndex.UpdateTimestamp(userId, UnixMillisecondTime.Now) + .Prune(_maxCachedUsers, out removedUserIds); + _storeIndex = updatedIndex; + } + + _currentUserId = userId; + } + + if (_persistentStore != null) + { + try + { + if (removedUserIds != null) + { + foreach (var oldId in removedUserIds) + { + _persistentStore.RemoveUserData(oldId); + } + } + if (updatePersistentStorage) + { + _persistentStore.SetUserData(userId, data); + } + _persistentStore.SetIndex(updatedIndex); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to write to persistent store", e); + } + } + } + + /// + /// Attempts to get a flag by key from the current flags. This always uses the + /// in-memory cache, not persistent storage. + /// + /// the flag key + /// the flag descriptor, or null if not found + public ItemDescriptor? Get(string key) => + _flags.TryGetValue(key, out var item) ? item : (ItemDescriptor?)null; + + /// + /// Returns all current flags. This always uses the in-memory cache, not + /// persistent storage. + /// + /// the data set + public FullDataSet? GetAll() => + new FullDataSet(_flags); + + /// + /// Attempts to update or insert a flag. + /// + /// + /// This implements the usual versioning logic for updates: the update only succeeds if + /// data.Version is greater than the version of any current data for the same key. + /// If successful, and if persistent storage is enabled, it also updates persistent storage. + /// Therefore implementations do not need to implement + /// their own version checking. + /// + /// the flag key + /// the updated flag data, or a tombstone for a deleted flag + /// true if the update was done; false if it was not done due to a too-low + /// version number + public bool Upsert(string key, ItemDescriptor data) + { + var updatedFlags = _flags; + string userId = null; + + lock (_writerLock) + { + if (_flags.TryGetValue(key, out var oldItem) && oldItem.Version >= data.Version) + { + return false; + } + updatedFlags = _flags.SetItem(key, data); + _flags = updatedFlags; + userId = _currentUserId; + } + + _persistentStore?.SetUserData(userId, new FullDataSet(updatedFlags)); + return true; + } + + public void Dispose() => _persistentStore?.Dispose(); + + internal static string UserIdFor(User user) => Base64.Sha256Hash(user.Key); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs deleted file mode 100644 index 6ceb48c7..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/InMemoryDataStore.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Immutable; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; - -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - internal sealed class InMemoryDataStore : IDataStore - { - private readonly object _writerLock = new object(); - private volatile ImmutableDictionary> _data = - ImmutableDictionary>.Empty; - - public void Preload(User user) { } - - public void Init(User user, FullDataSet allData) - { - var newFlags = allData.Items.ToImmutableDictionary(); - lock (_writerLock) - { - _data = _data.SetItem(user.Key, newFlags); - } - } - - public ItemDescriptor? Get(User user, string key) - { - if (_data.TryGetValue(user.Key, out var flags)) - { - return flags.TryGetValue(key, out var item) ? item : (ItemDescriptor?)null; - } - return null; - } - - public FullDataSet? GetAll(User user) => - _data.TryGetValue(user.Key, out var flags) ? new FullDataSet(flags) : (FullDataSet?)null; - - public bool Upsert(User user, string key, ItemDescriptor item) - { - string userKey = user.Key; - lock (_writerLock) - { - if (_data.TryGetValue(userKey, out var flags)) - { - if (!flags.TryGetValue(key, out var oldItem) || oldItem.Version < item.Version) - { - var newFlags = flags.SetItem(key, item); - _data = _data.SetItem(userKey, newFlags); - return true; - } - return false; - } - var allFlags = ImmutableDictionary.Create().SetItem(key, item); - _data = _data.SetItem(userKey, allFlags); - return true; - } - } - - public void Dispose() { } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs new file mode 100644 index 00000000..d5d644ef --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs @@ -0,0 +1,24 @@ +using System.Collections.Immutable; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + internal sealed class NullPersistentDataStoreFactory : IPersistentDataStoreFactory + { + internal static readonly NullPersistentDataStoreFactory Instance = new NullPersistentDataStoreFactory(); + + public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => + NullPersistentDataStore.Instance; + } + + internal sealed class NullPersistentDataStore : IPersistentDataStore + { + internal static readonly NullPersistentDataStore Instance = new NullPersistentDataStore(); + + public string GetValue(string storageNamespace, string key) => null; + + public void SetValue(string storageNamespace, string key, string value) { } + + public void Dispose() { } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs new file mode 100644 index 00000000..571f4b07 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs @@ -0,0 +1,20 @@ +using System; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + internal sealed class PersistenceConfiguration + { + public IPersistentDataStore PersistentDataStore { get; } + public int MaxCachedUsers { get; } + + internal PersistenceConfiguration( + IPersistentDataStore persistentDataStore, + int maxCachedUsers + ) + { + PersistentDataStore = persistentDataStore; + MaxCachedUsers = maxCachedUsers; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index 2882db2b..f0f42a18 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -1,116 +1,132 @@ using System; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { - // Internal implementation of combining an in-memory store with a persistent store. - // - // For details on the format of the serialized data we store, see DataModelSerialization. - - internal sealed class PersistentDataStoreWrapper : IDataStore + /// + /// A facade over some implementation of , which adds + /// behavior that should be the same for all implementations, such as the specific data + /// keys we use, the logging of errors, and how data is serialized and deserialized. This + /// allows FlagDataManager (and other parts of the SDK that may need to access persistent + /// storage) to be written in a clearer way without embedding many implementation details. + /// + internal sealed class PersistentDataStoreWrapper : IDisposable { - private readonly InMemoryDataStore _inMemoryStore; + private const string NamespacePrefix = "LaunchDarkly"; + private const string GlobalAnonUserKey = "anonUser"; + private const string EnvironmentMetadataKey = "index"; + private const string EnvironmentUserDataKeyPrefix = "flags:"; + private readonly IPersistentDataStore _persistentStore; - private readonly object _writerLock = new object(); + private readonly string _globalNamespace; + private readonly string _environmentNamespace; + private readonly Logger _log; + private readonly object _storeLock = new object(); + private readonly AtomicBoolean _loggedStorageError = new AtomicBoolean(false); public PersistentDataStoreWrapper( - InMemoryDataStore inMemoryStore, IPersistentDataStore persistentStore, + string mobileKey, Logger log ) { - _inMemoryStore = inMemoryStore; _persistentStore = persistentStore; _log = log; + + _globalNamespace = NamespacePrefix; + _environmentNamespace = NamespacePrefix + ":" + Base64.Sha256Hash(mobileKey); } - public void Preload(User user) + public FullDataSet? GetUserData(string userId) { - lock (_writerLock) + var serializedData = HandleErrorsAndLock(() => _persistentStore.GetValue(_environmentNamespace, KeyForUserId(userId))); + if (serializedData is null) { - if (_inMemoryStore.GetAll(user) is null) - { - string serializedData = null; - try - { - serializedData = _persistentStore.GetAll(user); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Failed to read from persistent store", e); - } - if (serializedData is null) - { - return; - } - try - { - _inMemoryStore.Init(user, DataModelSerialization.DeserializeAll(serializedData)); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Failed to deserialize data from persistent store", e); - } - } + return null; + } + try + { + return DataModelSerialization.DeserializeAll(serializedData); + } + catch (Exception e) + { + LogHelpers.LogException(_log, "Failed to deserialize data from persistent store", e); + return null; } } - public void Init(User user, FullDataSet allData) + public void SetUserData(string userId, FullDataSet data) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForUserId(userId), + DataModelSerialization.SerializeAll(data))); + + public void RemoveUserData(string userId) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForUserId(userId), null)); + + public UserIndex GetIndex() { - lock (_writerLock) + string data = HandleErrorsAndLock(() => _persistentStore.GetValue(_environmentNamespace, EnvironmentMetadataKey)); + if (data is null) { - _inMemoryStore.Init(user, allData); - try - { - _persistentStore.Init(user, DataModelSerialization.SerializeAll(allData)); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Failed to write to persistent store", e); - } + return new UserIndex(); + } + try + { + return UserIndex.Deserialize(data); + } + catch (Exception) + { + _log.Warn("Discarding invalid data from persistent store index"); + return new UserIndex(); } } - public ItemDescriptor? Get(User user, string key) => - _inMemoryStore.Get(user, key); + public void SetIndex(UserIndex index) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, EnvironmentMetadataKey, index.Serialize())); + + public string GetAnonymousUserKey() => + HandleErrorsAndLock(() => _persistentStore.GetValue(_globalNamespace, GlobalAnonUserKey)); + + public void SetAnonymousUserKey(string value) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_globalNamespace, GlobalAnonUserKey, value)); + + public void Dispose() => + _persistentStore.Dispose(); + + private static string KeyForUserId(string userId) => EnvironmentUserDataKeyPrefix + userId; - public FullDataSet? GetAll(User user) => - _inMemoryStore.GetAll(user); + private void MaybeLogStoreError(Exception e) + { + if (!_loggedStorageError.GetAndSet(true)) + { + LogHelpers.LogException(_log, "Failure in persistent data store", e); + } + } - public bool Upsert(User user, string key, ItemDescriptor data) + private T HandleErrorsAndLock(Func action) { - lock (_writerLock) + try { - if (_inMemoryStore.Upsert(user, key, data)) + lock (_storeLock) { - var allData = _inMemoryStore.GetAll(user); - if (allData != null) - { - try - { - _persistentStore.Init(user, DataModelSerialization.SerializeAll(allData.Value)); - } - catch (Exception e) - { - LogHelpers.LogException(_log, "Failed to write to persistent store", e); - } - } - return true; + return action(); } - return false; + } + catch (Exception e) + { + MaybeLogStoreError(e); + return default(T); } } - public void Dispose() + private void HandleErrorsAndLock(Action action) { - _inMemoryStore.Dispose(); - _persistentStore.Dispose(); + _ = HandleErrorsAndLock(() => { action(); return true; }); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs new file mode 100644 index 00000000..36eeabbb --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using LaunchDarkly.JsonStream; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + /// + /// Used internally to track which users have flag data in the persistent store. + /// + internal class UserIndex + { + internal ImmutableList Data { get; } + + internal struct IndexEntry + { + public string UserId { get; set; } + public UnixMillisecondTime Timestamp { get; set; } + } + + internal UserIndex(ImmutableList data = null) + { + Data = data ?? ImmutableList.Empty; + } + + public UserIndex UpdateTimestamp(string userId, UnixMillisecondTime timestamp) + { + var builder = ImmutableList.CreateBuilder(); + builder.AddRange(Data.Where(e => e.UserId != userId)); + builder.Add(new IndexEntry { UserId = userId, Timestamp = timestamp }); + return new UserIndex(builder.ToImmutable()); + } + + public UserIndex Prune(int maxUsersToRetain, out IEnumerable removedUserIds) + { + if (Data.Count <= maxUsersToRetain) + { + removedUserIds = ImmutableList.Empty; + return this; + } + // The data will normally already be in ascending timestamp order, in which case this Sort + // won't do anything, but this is just in case unsorted data somehow got persisted. + var sorted = Data.Sort((e1, e2) => e1.Timestamp.CompareTo(e2.Timestamp)); + var numDrop = Data.Count - maxUsersToRetain; + removedUserIds = ImmutableList.CreateRange(sorted.Take(numDrop).Select(e => e.UserId)); + return new UserIndex(ImmutableList.CreateRange(sorted.Skip(numDrop))); + } + + /// + /// Returns a JSON representation of the user index. + /// + /// the JSON representation + public string Serialize() + { + var w = JWriter.New(); + using (var aw0 = w.Array()) + { + foreach (var e in Data) + { + using (var aw1 = aw0.Array()) + { + aw1.String(e.UserId); + aw1.Long(e.Timestamp.Value); + } + } + } + return w.GetString(); + } + + /// + /// Parses the user index from a JSON representation. If the JSON string is null or + /// empty, it returns an empty user index. + /// + /// the JSON representation + /// the parsed data + /// if the JSON is malformed + public static UserIndex Deserialize(string json) + { + if (string.IsNullOrEmpty(json)) + { + return new UserIndex(); + } + var builder = ImmutableList.CreateBuilder(); + try + { + var r = JReader.FromString(json); + for (var ar0 = r.Array(); ar0.Next(ref r);) + { + var ar1 = r.Array(); + if (ar1.Next(ref r)) + { + var userId = r.String(); + if (ar1.Next(ref r)) + { + var timeMillis = r.Long(); + builder.Add(new IndexEntry { UserId = userId, Timestamp = UnixMillisecondTime.OfMillis(timeMillis) }); + ar1.Next(ref r); + } + } + } + } + catch (Exception e) + { + throw new FormatException("invalid stored user index", e); + } + return new UserIndex(builder.ToImmutable()); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs b/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs index e806da41..bcc0d6ec 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs @@ -1,21 +1,13 @@ -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.PlatformSpecific; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal { - // This just delegates to the conditionally-compiled code in LaunchDarkly.Sdk.Client.PlatformSpecific. - // The only reason it is a pluggable component is for unit tests; we don't currently expose IDeviceInfo. + // This delegates to the conditionally-compiled code in LaunchDarkly.Sdk.Client.PlatformSpecific + // to get the device identifier. The only reason it is a pluggable component is for unit tests; + // we don't currently expose IDeviceInfo. + internal sealed class DefaultDeviceInfo : IDeviceInfo { - private readonly Logger _log; - - internal DefaultDeviceInfo(Logger log) - { - _log = log; - } - - public string UniqueDeviceId() => - ClientIdentifier.GetOrCreateClientId(_log); + public string UniqueDeviceId() => PlatformSpecific.ClientIdentifier.Value; } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs index e90f160e..3d82e1c7 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Factory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Factory.cs @@ -10,10 +10,5 @@ internal static IConnectivityStateManager CreateConnectivityStateManager(Configu { return configuration.ConnectivityStateManager ?? new DefaultConnectivityStateManager(); } - - internal static IDeviceInfo CreateDeviceInfo(Configuration configuration, Logger log) - { - return configuration.DeviceInfo ?? new DefaultDeviceInfo(log); - } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs deleted file mode 100644 index 58ef59ab..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDataStore.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.DataStores; - -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; - -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - /// - /// Internal interface for the top-level component that reads and writes flag data. - /// - /// - /// This is non-public because applications should not need to customize storage at - /// this level; they can customize the piece. - /// The standard implementations of IDataStore are - /// and . - /// - internal interface IDataStore : IDisposable - { - /// - /// Tells the data store that we are changing the user properties and it should - /// load any previously persisted flag data for the new user into memory. The - /// store does not maintain a "current user" state, since each method has a user - /// parameter, but Preload allows us to load a whole data set at once - /// rather than doing so on individual cache misses. - /// - /// the new user properties - void Preload(User user); - - /// - /// Overwrites the store's contents for a specific user with a serialized data set. - /// - /// the current user - /// the data set - void Init(User user, FullDataSet allData); - - /// - /// Retrieves an individual flag item for a specific user, if available. - /// - /// the current user - /// the flag key - /// an containing either the flag data or a - /// deleted item tombstone, or if the flag key is unknown - ItemDescriptor? Get(User user, string key); - - /// - /// Retrieves all items for a specific user, if available. - /// - /// the current user - /// a containing all flags for the user, or - /// if the user key is unknown - FullDataSet? GetAll(User user); - - /// - /// Updates or inserts an item. For updates, the object will only be updated if the - /// existing version is less than the new version. - /// - /// the current user - /// the flag key - /// the new flag data or deleted item tombstone - /// true if the item was updated; false if it was not updated because the - /// store contains an equal or greater version - bool Upsert(User user, string key, ItemDescriptor data); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs new file mode 100644 index 00000000..d994c312 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs @@ -0,0 +1,86 @@ +using System; +using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Internal.Interfaces; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal class UserDecorator + { + private readonly IDeviceInfo _deviceInfo; + private readonly PersistentDataStoreWrapper _store; + private readonly string _deviceName; + private readonly string _osName; + + private string _cachedAnonUserKey = null; + private object _anonUserKeyLock = new object(); + + public UserDecorator( + IDeviceInfo deviceInfo, + PersistentDataStoreWrapper store + ) + { + _deviceInfo = deviceInfo; + _store = store; + + // Store platform-specific values in static fields to avoid having to compute them repeatedly + _deviceName = PlatformSpecific.UserMetadata.DeviceName; + _osName = PlatformSpecific.UserMetadata.OSName; + } + + public User DecorateUser(User user) + { + IUserBuilder buildUser = null; + + if (_deviceName != null) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("device", _deviceName); + } + if (_osName != null) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + buildUser.Custom("os", _osName); + } + // If you pass in a user with a null or blank key, one will be assigned to them. + if (String.IsNullOrEmpty(user.Key)) + { + if (buildUser is null) + { + buildUser = User.Builder(user); + } + var anonUserKey = GetOrCreateAnonUserKey(); + buildUser.Key(anonUserKey).Anonymous(true); + } + return buildUser is null ? user : buildUser.Build(); + } + + private string GetOrCreateAnonUserKey() + { + lock (_anonUserKeyLock) + { + if (_cachedAnonUserKey != null) + { + return _cachedAnonUserKey; + } + var deviceId = _deviceInfo.UniqueDeviceId(); + if (deviceId is null) + { + deviceId = _store?.GetAnonymousUserKey(); + if (deviceId is null) + { + deviceId = Guid.NewGuid().ToString(); + _store?.SetAnonymousUserKey(deviceId); + } + } + _cachedAnonUserKey = deviceId; + return deviceId; + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 428a6e63..017bfcb8 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -35,15 +35,16 @@ public sealed class LdClient : ILdClient readonly IDataSourceFactory _dataSourceFactory; readonly IDataSourceStatusProvider _dataSourceStatusProvider; readonly IDataSourceUpdateSink _dataSourceUpdateSink; - readonly IDataStore _dataStore; + readonly FlagDataManager _dataStore; readonly DiagnosticDisablerImpl _diagnosticDisabler; readonly ConnectionManager _connectionManager; readonly IBackgroundModeManager _backgroundModeManager; - readonly IDeviceInfo deviceInfo; readonly IConnectivityStateManager _connectivityStateManager; readonly IEventProcessor _eventProcessor; readonly IFlagTracker _flagTracker; readonly TaskExecutor _taskExecutor; + readonly UserDecorator _userDecorator; + private readonly Logger _log; // Mutable client state (some state is also in the ConnectionManager) @@ -141,18 +142,28 @@ public sealed class LdClient : ILdClient _log.Info("Starting LaunchDarkly Client {0}", Version); - deviceInfo = Factory.CreateDeviceInfo(configuration, _log); - - _user = DecorateUser(user); - - var persistentStore = configuration.PersistentDataStoreFactory is null ? - new DefaultPersistentDataStore(_log.SubLogger(LogNames.DataStoreSubLog)) : - configuration.PersistentDataStoreFactory.CreatePersistentDataStore(_context); - _dataStore = new PersistentDataStoreWrapper( - new InMemoryDataStore(), - persistentStore, + var persistenceConfiguration = (configuration.PersistenceConfigurationBuilder ?? Components.Persistence()) + .CreatePersistenceConfiguration(_context); + _dataStore = new FlagDataManager( + configuration.MobileKey, + persistenceConfiguration, _log.SubLogger(LogNames.DataStoreSubLog) ); + + _userDecorator = new UserDecorator(configuration.DeviceInfo ?? new DefaultDeviceInfo(), + _dataStore.PersistentStore); + _user = _userDecorator.DecorateUser(user); + + // If we had cached data for the new user, set the current in-memory flag data state to use + // that data, so that any Variation calls made before Identify has completed will use the + // last known values. + var cachedData = _dataStore.GetCachedData(_user); + if (cachedData != null) + { + _log.Debug("Cached flag data is available for this user"); + _dataStore.Init(_user, cachedData.Value, false); // false means "don't rewrite the flags to persistent storage" + } + var dataSourceUpdateSink = new DataSourceUpdateSinkImpl( _dataStore, configuration.Offline, @@ -160,7 +171,6 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataSourceSubLog) ); _dataSourceUpdateSink = dataSourceUpdateSink; - _dataStore.Preload(_user); _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); _flagTracker = new FlagTrackerImpl(dataSourceUpdateSink); @@ -448,7 +458,7 @@ EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, EvaluationDetail errorResult(EvaluationErrorKind kind) => new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(kind)); - var flag = _dataStore.Get(User, featureKey)?.Item; + var flag = _dataStore.Get(featureKey)?.Item; if (flag == null) { if (!Initialized) @@ -508,10 +518,10 @@ private void SendEvaluationEventIfOnline(EventProcessorTypes.EvaluationEvent e) /// public IDictionary AllFlags() { - var data = _dataStore.GetAll(User); + var data = _dataStore.GetAll(); if (data is null) { - return ImmutableDictionary.Create(); + return ImmutableDictionary.Empty; } return data.Value.Items.Where(entry => entry.Value.Item != null) .ToDictionary(p => p.Key, p => p.Value.Item.Value); @@ -573,7 +583,7 @@ public async Task IdentifyAsync(User user) throw new ArgumentNullException(nameof(user)); } - User newUser = DecorateUser(user); + User newUser = _userDecorator.DecorateUser(user); User oldUser = newUser; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => @@ -582,7 +592,23 @@ public async Task IdentifyAsync(User user) _user = newUser; }); - _dataStore.Preload(newUser); + // If we had cached data for the new user, set the current in-memory flag data state to use + // that data, so that any Variation calls made before Identify has completed will use the + // last known values. If we did not have cached data, then we update the current in-memory + // state to reflect that there is no flag data, so that Variation calls done before completion + // will receive default values rather than the previous user's values. This does not modify + // any flags in persistent storage, and (currently) it does *not* trigger any FlagValueChanged + // events from FlagTracker. + var cachedData = _dataStore.GetCachedData(newUser); + if (cachedData != null) + { + _log.Debug("Identify found cached flag data for the new user"); + } + _dataStore.Init( + newUser, + cachedData ?? new DataStoreTypes.FullDataSet(null), + false // false means "don't rewrite the flags to persistent storage" + ); EventProcessorIfEnabled().RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { @@ -623,37 +649,6 @@ public void Alias(User user, User previousUser) }); } - User DecorateUser(User user) - { - IUserBuilder buildUser = null; - if (UserMetadata.DeviceName != null) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Custom("device", UserMetadata.DeviceName); - } - if (UserMetadata.OSName != null) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Custom("os", UserMetadata.OSName); - } - // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) - { - if (buildUser is null) - { - buildUser = User.Builder(user); - } - buildUser.Key(deviceInfo.UniqueDeviceId()).Anonymous(true); - } - return buildUser is null ? user : buildUser.Build(); - } - /// /// Permanently shuts down the SDK client. /// diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs index e9c404b7..13dd8158 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs @@ -1,9 +1,7 @@ -using System; -using Android.Provider; +using Android.Provider; using Android.OS; using Android.Runtime; using Java.Interop; -using LaunchDarkly.Logging; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { @@ -11,34 +9,26 @@ internal static partial class ClientIdentifier { private static JniPeerMembers buildMembers = new XAPeerMembers("android/os/Build", typeof(Build)); - private static string PlatformGetOrCreateClientId(Logger log) + public static string Value { - // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs - string serialField; - try - { - var value = buildMembers.StaticFields.GetObjectValue("SERIAL.Ljava/lang/String;"); - serialField = JNIEnv.GetString(value.Handle, JniHandleOwnership.TransferLocalRef); - } - catch - { - serialField = ""; - } - if (string.IsNullOrWhiteSpace(serialField) || serialField == Build.Unknown || serialField == "0") + get { + // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs + string serialField; try { - var context = Android.App.Application.Context; - return Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId); + var value = buildMembers.StaticFields.GetObjectValue("SERIAL.Ljava/lang/String;"); + serialField = JNIEnv.GetString(value.Handle, JniHandleOwnership.TransferLocalRef); } - catch (Exception ex) + catch { - log.Warn("Unable to get client ID: {0}", LogValues.ExceptionSummary(ex)); - return null; + serialField = ""; + } + if (string.IsNullOrWhiteSpace(serialField) || serialField == Build.Unknown || serialField == "0") + { + var context = Android.App.Application.Context; + return Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId); } - } - else - { return serialField; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs index 68124d1f..986d8c4a 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs @@ -1,14 +1,10 @@ -using LaunchDarkly.Logging; -using UIKit; +using UIKit; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class ClientIdentifier { - // For mobile platforms that really have a device ID, we delegate to Plugin.DeviceInfo to get the ID. - private static string PlatformGetOrCreateClientId(Logger log) - { - return UIDevice.CurrentDevice.IdentifierForVendor.AsString(); - } + public static string Value => + UIDevice.CurrentDevice.IdentifierForVendor.AsString(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs index a7a0d935..eae8b93d 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs @@ -5,9 +5,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class ClientIdentifier { // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. - // Instead, we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a - // user key for this (OS) user account, and if not, generate a randomized ID and cache it. - private static string PlatformGetOrCreateClientId(Logger log) => - GetOrCreateRandomizedClientId(log); + public static string Value => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs index fcaef0b1..dfe8a883 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs @@ -1,45 +1,7 @@ -using System; -using LaunchDarkly.Logging; - + namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class ClientIdentifier { - private static volatile string _id; - - private const string PreferencesAnonUserIdKey = "anonUserId"; - - public static string GetOrCreateClientId(Logger log) - { - var id = _id; - if (id is null) - { - id = PlatformGetOrCreateClientId(log); - _id = id; - } - return id; - } - - // Used only for testing, to keep previous calls to GetOrCreateRandomizedClientId from affecting test state. - // On mobile platforms this has no effect. - internal static void ClearCachedClientId(Logger log) - { - Preferences.Remove(PreferencesAnonUserIdKey, log); - } - - private static string GetOrCreateRandomizedClientId(Logger log) - { - // On non-mobile platforms, there may not be an OS-based notion of a device identifier. Instead, - // we'll do what we do in the non-mobile client-side SDKs: see if we've already cached a user key - // for this user account (OS user, that is), and if not, generate a randomized ID and cache it. - string cachedKey = Preferences.Get(PreferencesAnonUserIdKey, null, log); - if (cachedKey != null) - { - return cachedKey; - } - string guid = Guid.NewGuid().ToString(); - Preferences.Set(PreferencesAnonUserIdKey, guid, log); - return guid; - } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs new file mode 100644 index 00000000..ee586312 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs @@ -0,0 +1,43 @@ +using Android.App; +using Android.Content; +using Android.Preferences; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal sealed partial class LocalStorage : IPersistentDataStore + { + public string GetValue(string storageNamespace, string key) + { + using (var sharedPreferences = GetSharedPreferences(storageNamespace)) + { + return sharedPreferences.GetString(key, null); + } + } + + public void SetValue(string storageNamespace, string key, string value) + { + using (var sharedPreferences = GetSharedPreferences(storageNamespace)) + using (var editor = sharedPreferences.Edit()) + { + if (value is null) + { + editor.Remove(key).Commit(); + } + else + { + editor.PutString(key, value).Commit(); + } + } + } + + static ISharedPreferences GetSharedPreferences(string sharedName) + { + var context = Application.Context; + + return sharedName is null ? + PreferenceManager.GetDefaultSharedPreferences(context) : + context.GetSharedPreferences(sharedName, FileCreationMode.Private); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs new file mode 100644 index 00000000..57f79a83 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs @@ -0,0 +1,56 @@ +using Foundation; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal sealed partial class LocalStorage : IPersistentDataStore + { + private const string LaunchDarklyMainKey = "com.launchdarkly.sdk"; + + public string GetValue(string storageNamespace, string key) + { + using (var defaults = NSUserDefaults.StandardUserDefaults) + { + var mainDict = defaults.DictionaryForKey(LaunchDarklyMainKey); + if (mainDict is null) + { + return null; + } + var groupDict = mainDict.ObjectForKey(new NSString(storageNamespace)) as NSDictionary; + if (groupDict is null) + { + return null; + } + var value = groupDict.ObjectForKey(new NSString(key)); + return value?.ToString(); + } + } + + public void SetValue(string storageNamespace, string key, string value) + { + using (var defaults = NSUserDefaults.StandardUserDefaults) + { + var mainDict = defaults.DictionaryForKey(LaunchDarklyMainKey) as NSDictionary; + var newMainDict = mainDict is null ? new NSMutableDictionary() : + new NSMutableDictionary(mainDict); + + var groupKey = new NSString(storageNamespace); + var groupDict = newMainDict.ObjectForKey(groupKey) as NSDictionary; + var newGroupDict = groupDict is null ? new NSMutableDictionary() : + new NSMutableDictionary(groupDict); + + if (value is null) + { + newGroupDict.Remove(new NSString(key)); + } + else + { + newGroupDict.SetValueForKey(new NSString(value), new NSString(key)); + } + + newMainDict.SetValueForKey(newGroupDict, groupKey); + defaults.SetValueForKey(newMainDict, new NSString(LaunchDarklyMainKey)); + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs new file mode 100644 index 00000000..b9e177e8 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.IO.IsolatedStorage; +using System.Text; +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + // In .NET Standard 2.0, we use the IsolatedStorage API to store per-user data. The .NET Standard implementation + // of IsolatedStorage puts these files under ~/.local/share/IsolatedStorage followed by a subpath of obfuscated + // strings that are apparently based on the application and assembly name, so the data should be specific to both + // the OS user account and the current app. + // + // This is based on the Plugin.Settings plugin (which is what Xamarin Essentials uses for preferences), but greatly + // simplified since we only need one data type. See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs + + internal sealed partial class LocalStorage : IPersistentDataStore + { + private const string ConfigDirectoryName = "LaunchDarkly"; + + public string GetValue(string storageNamespace, string key) + { + return WithStore(store => + { + try + { + using (var stream = store.OpenFile(MakeFilePath(storageNamespace, key), FileMode.Open)) + { + using (var sr = new StreamReader(stream)) + { + return sr.ReadToEnd(); + } + } + } + // ignore exceptions that just indicate no value has been set for this namespace/key + catch (IsolatedStorageException e) when (e.InnerException is DirectoryNotFoundException) { } + catch (IsolatedStorageException e) when (e.InnerException is FileNotFoundException) { } + catch (DirectoryNotFoundException) { } + catch (FileNotFoundException) { } + return null; + }); + } + + public void SetValue(string storageNamespace, string key, string value) + { + WithStore(store => + { + var filePath = MakeFilePath(storageNamespace, key); + if (value is null) + { + try + { + store.DeleteFile(filePath); + } + catch (IsolatedStorageException) { } // file didn't exist - that's OK + } + else + { + var dirPath = MakeDirectoryPath(storageNamespace); + store.CreateDirectory(dirPath); // has no effect if directory already exists + using (var stream = store.OpenFile(filePath, FileMode.Create, FileAccess.Write)) + { + using (var sw = new StreamWriter(stream)) + { + sw.Write(value); + } + } + } + }); + } + + private T WithStore(Func callback) + { + // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. + using (var store = IsolatedStorageFile.GetUserStoreForDomain()) + { + return callback(store); + } + } + + private void WithStore(Action callback) + { + WithStore(store => + { + callback(store); + return true; + }); + } + + private static string MakeDirectoryPath(string storageNamespace) => + string.IsNullOrEmpty(storageNamespace) + ? ConfigDirectoryName + : (ConfigDirectoryName + "." + EscapeFilenameComponent(storageNamespace)); + + private static string MakeFilePath(string storageNamespace, string key) => + MakeDirectoryPath(storageNamespace) + "/" + EscapeFilenameComponent(key); + + private static string EscapeFilenameComponent(string name) + { + StringBuilder buf = null; + for (var i = 0; i < name.Length; i++) + { + var ch = name[i]; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' + || (ch == '.' && i > 0)) + { + buf?.Append(ch); + } + else + { + if (buf == null) // create StringBuilder lazily since most names will be valid + { + buf = new StringBuilder(name.Length + 30); + buf.Append(name.Substring(0, i)); + } + buf.Append('%').Append(((int)ch).ToString("X")); // hex value + } + } + return buf == null ? name : buf.ToString(); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs new file mode 100644 index 00000000..c34cdbf1 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs @@ -0,0 +1,17 @@ +using LaunchDarkly.Sdk.Client.Interfaces; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + /// + /// Platform-specific implementations of the IPersistentDataStore interface for + /// storing arbitrary string key-value pairs. On mobile devices, this is + /// implemented with the native preferences API. In .NET Standard, it is + /// implemented with the IsolatedStorage API. + /// + internal sealed partial class LocalStorage : IPersistentDataStore + { + internal static readonly LocalStorage Instance = new LocalStorage(); + + public void Dispose() { } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs deleted file mode 100644 index 8dbdaf25..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.android.cs +++ /dev/null @@ -1,115 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Globalization; -using Android.App; -using Android.Content; -using Android.Preferences; -using LaunchDarkly.Logging; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class - // to store them. Therefore, the overloads for non-string types have been removed, thereby - // reducing the amount of multi-platform implementation code that won't be used. - - internal static partial class Preferences - { - static readonly object locker = new object(); - - static bool PlatformContainsKey(string key, string sharedName, Logger log) - { - lock (locker) - { - using (var sharedPreferences = GetSharedPreferences(sharedName)) - { - return sharedPreferences.Contains(key); - } - } - } - - static void PlatformRemove(string key, string sharedName, Logger log) - { - lock (locker) - { - using (var sharedPreferences = GetSharedPreferences(sharedName)) - using (var editor = sharedPreferences.Edit()) - { - editor.Remove(key).Commit(); - } - } - } - - static void PlatformClear(string sharedName, Logger log) - { - lock (locker) - { - using (var sharedPreferences = GetSharedPreferences(sharedName)) - using (var editor = sharedPreferences.Edit()) - { - editor.Clear().Commit(); - } - } - } - - static void PlatformSet(string key, string value, string sharedName, Logger log) - { - lock (locker) - { - using (var sharedPreferences = GetSharedPreferences(sharedName)) - using (var editor = sharedPreferences.Edit()) - { - if (value == null) - { - editor.Remove(key); - } - else - { - editor.PutString(key, value); - } - editor.Apply(); - } - } - } - - static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) - { - lock (locker) - { - using (var sharedPreferences = GetSharedPreferences(sharedName)) - { - return sharedPreferences.GetString(key, defaultValue); - } - } - } - - static ISharedPreferences GetSharedPreferences(string sharedName) - { - var context = Application.Context; - - return string.IsNullOrWhiteSpace(sharedName) ? - PreferenceManager.GetDefaultSharedPreferences(context) : - context.GetSharedPreferences(sharedName, FileCreationMode.Private); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs deleted file mode 100644 index d9b6c193..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.ios.cs +++ /dev/null @@ -1,115 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Globalization; -using Foundation; -using LaunchDarkly.Logging; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class - // to store them. Therefore, the overloads for non-string types have been removed, thereby - // reducing the amount of multi-platform implementation code that won't be used. - - internal static partial class Preferences - { - static readonly object locker = new object(); - - static bool PlatformContainsKey(string key, string sharedName, Logger log) - { - lock (locker) - { - return GetUserDefaults(sharedName)[key] != null; - } - } - - static void PlatformRemove(string key, string sharedName, Logger log) - { - lock (locker) - { - using (var userDefaults = GetUserDefaults(sharedName)) - { - if (userDefaults[key] != null) - userDefaults.RemoveObject(key); - } - } - } - - static void PlatformClear(string sharedName, Logger log) - { - lock (locker) - { - using (var userDefaults = GetUserDefaults(sharedName)) - { - var items = userDefaults.ToDictionary(); - - foreach (var item in items.Keys) - { - if (item is NSString nsString) - userDefaults.RemoveObject(nsString); - } - } - } - } - - static void PlatformSet(string key, string value, string sharedName, Logger log) - { - lock (locker) - { - using (var userDefaults = GetUserDefaults(sharedName)) - { - if (value == null) - { - if (userDefaults[key] != null) - userDefaults.RemoveObject(key); - return; - } - - userDefaults.SetString(value, key); - } - } - } - - static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) - { - lock (locker) - { - using (var userDefaults = GetUserDefaults(sharedName)) - { - if (userDefaults[key] == null) - return defaultValue; - - return userDefaults.StringForKey(key); - } - } - } - - static NSUserDefaults GetUserDefaults(string sharedName) - { - if (!string.IsNullOrWhiteSpace(sharedName)) - return new NSUserDefaults(sharedName, NSUserDefaultsType.SuiteName); - else - return NSUserDefaults.StandardUserDefaults; - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs deleted file mode 100644 index 7f04d090..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.netstandard.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System; -using System.IO; -using System.IO.IsolatedStorage; -using System.Linq; -using System.Text; -using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Internal.Concurrent; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // This code is not from Xamarin Essentials, though it implements the same Preferences abstraction. - // - // In .NET Standard 2.0, we use the IsolatedStorage API to store per-user data. The .NET Standard implementation - // of IsolatedStorage puts these files under ~/.local/share/IsolatedStorage followed by a subpath of obfuscated - // strings that are apparently based on the application and assembly name, so the data should be specific to both - // the OS user account and the current app. - // - // This is based on the Plugin.Settings plugin (which is what Xamarin Essentials uses for preferences), but greatly - // simplified since we only need one data type. See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs - - internal static partial class Preferences - { - private static AtomicBoolean _loggedOSError = new AtomicBoolean(false); // AtomicBoolean is defined in LaunchDarkly.CommonSdk - - private const string ConfigDirectoryName = "LaunchDarkly"; - - static bool PlatformContainsKey(string key, string sharedName, Logger log) - { - return WithStore(store => store.FileExists(MakeFilePath(key, sharedName)), log); - } - - static void PlatformRemove(string key, string sharedName, Logger log) - { - WithStore(store => - { - try - { - store.DeleteFile(MakeFilePath(key, sharedName)); - } - catch (IsolatedStorageException) { } // file didn't exist - that's OK - }, log); - } - - static void PlatformClear(string sharedName, Logger log) - { - WithStore(store => - { - try - { - store.DeleteDirectory(MakeDirectoryPath(sharedName)); - // The directory will be recreated next time PlatformSet is called with the same sharedName. - } - catch (IsolatedStorageException) { } // directory didn't exist - that's OK - }, log); - } - - static void PlatformSet(string key, string value, string sharedName, Logger log) - { - WithStore(store => - { - var path = MakeDirectoryPath(sharedName); - store.CreateDirectory(path); // has no effect if directory already exists - using (var stream = store.OpenFile(MakeFilePath(key, sharedName), FileMode.Create, FileAccess.Write)) - { - using (var sw = new StreamWriter(stream)) - { - sw.Write(value); - } - } - }, log); - } - - static string PlatformGet(string key, string defaultValue, string sharedName, Logger log) - { - return WithStore(store => - { - try - { - using (var stream = store.OpenFile(MakeFilePath(key, sharedName), FileMode.Open)) - { - using (var sr = new StreamReader(stream)) - { - return sr.ReadToEnd(); - } - } - } - catch (DirectoryNotFoundException) { } // just return null if no preferences have ever been set - catch (FileNotFoundException) { } // just return null if this preference was never set - return null; - }, log); - } - - private static T WithStore(Func callback, Logger log) - { - try - { - // GetUserStoreForDomain returns a storage object that is specific to the current application and OS user. - var store = IsolatedStorageFile.GetUserStoreForDomain(); - return callback(store); - } - catch (Exception e) when (e is IsolatedStorageException || e is InvalidOperationException) - { - // These exceptions are ones that IsolatedStorageFile methods may throw under conditions that are - // unrelated to our code, e.g. filesystem permissions don't allow the store to be used. Since such a - // condition is unlikely to change during the application's lifetime, we only want to log it once. - // We won't log a stacktrace since it'll just point to somewhere in the standard library. - - // Note that we specifically catch IsolatedStorageException in a couple places above, when it would - // indicate a particular error condition that we want to handle differently. In all other cases it - // is unexpected and should be considered a platform/configuration issue. - - if (!_loggedOSError.GetAndSet(true)) - { - log.Warn("Persistent storage is unavailable and has been disabled ({0}: {1})", e.GetType(), e.Message); - } - return default(T); - } - } - - private static void WithStore(Action callback, Logger log) - { - WithStore(store => - { - callback(store); - return true; - }, log); - } - - private static string MakeDirectoryPath(string sharedName) - { - if (string.IsNullOrEmpty(sharedName)) - { - return ConfigDirectoryName; - } - return ConfigDirectoryName + "." + EscapeFilenameComponent(sharedName); - } - - private static string MakeFilePath(string key, string sharedName) - { - return MakeDirectoryPath(sharedName) + "/" + EscapeFilenameComponent(key); - } - - private static string EscapeFilenameComponent(string name) - { - // In actual usage for LaunchDarkly this should not be an issue, because keys are really feature flag keys - // which have a very limited character set, and we don't actually use sharedName. It's just good practice. - StringBuilder buf = null; - var badChars = Path.GetInvalidFileNameChars(); - const char escapeChar = '%'; - for (var i = 0; i < name.Length; i++) - { - var ch = name[i]; - if (badChars.Contains(ch) || ch == escapeChar) - { - if (buf == null) // create StringBuilder lazily since most names will be valid - { - buf = new StringBuilder(name.Length); - buf.Append(name.Substring(0, i)); - } - buf.Append(escapeChar).Append(((int)ch).ToString("X")); // hex value - } - else - { - buf?.Append(ch); - } - } - return buf == null ? name : buf.ToString(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs deleted file mode 100644 index 07021855..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Preferences.shared.cs +++ /dev/null @@ -1,144 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using LaunchDarkly.Logging; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // Modified for LaunchDarkly: the SDK always serializes values to strings before using this class - // to store them. Therefore, the overloads for non-string types have been removed, thereby - // reducing the amount of multi-platform implementation code that won't be used. - - internal static partial class Preferences - { - internal static string GetPrivatePreferencesSharedName(string feature) => - $"LaunchDarkly.Xamarin.{feature}"; - - // overloads - - public static bool ContainsKey(string key, Logger log) => - ContainsKey(key, null, log); - - public static void Remove(string key, Logger log) => - Remove(key, null, log); - - public static void Clear(Logger log) => - Clear(null, log); - - public static string Get(string key, string defaultValue, Logger log) => - Get(key, defaultValue, null, log); - - //public static bool Get(string key, bool defaultValue) => - // Get(key, defaultValue, null); - - //public static int Get(string key, int defaultValue) => - // Get(key, defaultValue, null); - - //public static double Get(string key, double defaultValue) => - // Get(key, defaultValue, null); - - //public static float Get(string key, float defaultValue) => - // Get(key, defaultValue, null); - - //public static long Get(string key, long defaultValue) => - // Get(key, defaultValue, null); - - public static void Set(string key, string value, Logger log) => - Set(key, value, null, log); - - //public static void Set(string key, bool value) => - // Set(key, value, null); - - //public static void Set(string key, int value) => - // Set(key, value, null); - - //public static void Set(string key, double value) => - // Set(key, value, null); - - //public static void Set(string key, float value) => - // Set(key, value, null); - - //public static void Set(string key, long value) => - // Set(key, value, null); - - // shared -> platform - - public static bool ContainsKey(string key, string sharedName, Logger log) => - PlatformContainsKey(key, sharedName, log); - - public static void Remove(string key, string sharedName, Logger log) => - PlatformRemove(key, sharedName, log); - - public static void Clear(string sharedName, Logger log) => - PlatformClear(sharedName, log); - - public static string Get(string key, string defaultValue, string sharedName, Logger log) => - PlatformGet(key, defaultValue, sharedName, log); - - //public static bool Get(string key, bool defaultValue, string sharedName) => - // PlatformGet(key, defaultValue, sharedName); - - //public static int Get(string key, int defaultValue, string sharedName) => - // PlatformGet(key, defaultValue, sharedName); - - //public static double Get(string key, double defaultValue, string sharedName) => - // PlatformGet(key, defaultValue, sharedName); - - //public static float Get(string key, float defaultValue, string sharedName) => - // PlatformGet(key, defaultValue, sharedName); - - //public static long Get(string key, long defaultValue, string sharedName) => - // PlatformGet(key, defaultValue, sharedName); - - public static void Set(string key, string value, string sharedName, Logger log) => - PlatformSet(key, value, sharedName, log); - - //public static void Set(string key, bool value, string sharedName) => - // PlatformSet(key, value, sharedName); - - //public static void Set(string key, int value, string sharedName) => - // PlatformSet(key, value, sharedName); - - //public static void Set(string key, double value, string sharedName) => - // PlatformSet(key, value, sharedName); - - //public static void Set(string key, float value, string sharedName) => - // PlatformSet(key, value, sharedName); - - //public static void Set(string key, long value, string sharedName) => - // PlatformSet(key, value, sharedName); - - // DateTime - - //public static DateTime Get(string key, DateTime defaultValue) => - // Get(key, defaultValue, null); - - //public static void Set(string key, DateTime value) => - // Set(key, value, null); - - //public static DateTime Get(string key, DateTime defaultValue, string sharedName) => - // DateTime.FromBinary(PlatformGet(key, defaultValue.ToBinary(), sharedName)); - - //public static void Set(string key, DateTime value, string sharedName) => - // PlatformSet(key, value.ToBinary(), sharedName); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs index 71af103e..fa91742b 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs @@ -5,22 +5,17 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class UserMetadata { - // These values are obtained from the platform-specific code once and then stored in static fields, - // to avoid having to recompute them many times. - private static readonly string _os = PlatformOS; - private static readonly string _device = PlatformDevice; - /// /// Returns the string that should be passed in the "device" property for all users. /// /// The value for "device", or null if none. - internal static string DeviceName => _device; + internal static string DeviceName => PlatformDevice; /// /// Returns the string that should be passed in the "os" property for all users. /// /// The value for "os", or null if none. - internal static string OSName => _os; + internal static string OSName => PlatformOS; internal static PlatformType PlatformType => PlatformPlatformType; } diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index ff9e7255..6ab62eb6 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -18,7 +18,7 @@ public void SdkReturnsAndroidPlatformType() public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.TestConfig("mobileKey").Build(); + var config = BasicConfig().Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; @@ -33,7 +33,7 @@ public void UserHasOSAndDeviceAttributesForPlatform() public void CanGetUniqueUserKey() { var anonUser = User.Builder((string)null).Anonymous(true).Build(); - var config = TestUtil.TestConfig("mobileKey") + var config = BasicConfig() .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs new file mode 100644 index 00000000..06233d80 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -0,0 +1,26 @@ +using Xunit; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.TestHelpers.JsonAssertions; + +namespace LaunchDarkly.Sdk.Client +{ + public class AssertHelpers + { + public static void DataSetsEqual(FullDataSet expected, FullDataSet actual) => + AssertJsonEqual(expected.ToJsonString(), actual.ToJsonString()); + + public static void UsersEqualExcludingAutoProperties(User expected, User actual) + { + var builder = User.Builder(expected); + foreach (var autoProp in new string[] { "device", "os" }) + { + if (!actual.GetAttribute(UserAttribute.ForName(autoProp)).IsNull) + { + builder.Custom(autoProp, actual.GetAttribute(UserAttribute.ForName(autoProp))); + } + } + Assert.Equal(builder.Build(), actual); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index abf9f436..9ff892de 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -33,11 +33,6 @@ protected BaseTest(Func adapterFn) BasicTaskExecutor = new TaskExecutor("test-sender", testLogger); } - protected void ClearCachedFlags(User user) - { - PlatformSpecific.Preferences.Clear(Constants.FLAGS_KEY_PREFIX + user.Key, testLogger); - } - public void Dispose() { TestUtil.ClearClient(); @@ -49,9 +44,13 @@ public void Dispose() // see which properties are important in a test. protected ConfigurationBuilder BasicConfig() => Configuration.Builder(BasicMobileKey) + .BackgroundModeManager(new MockBackgroundModeManager()) + .ConnectivityStateManager(new MockConnectivityStateManager(true)) .DataSource(new MockDataSource().AsSingletonFactory()) .Events(Components.NoEvents) .Logging(testLogging) - .Persistence(Components.NoPersistence); // unless we're specifically testing flag caching, this helps to prevent test state contamination + .Persistence( + Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory()) + ); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index ab1271ef..aac63f78 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -122,9 +122,9 @@ public void Offline() [Fact] public void Persistence() { - var prop = _tester.Property(c => c.PersistentDataStoreFactory, (b, v) => b.Persistence(v)); + var prop = _tester.Property(c => c.PersistenceConfigurationBuilder, (b, v) => b.Persistence(v)); prop.AssertDefault(null); - prop.AssertCanSet(Components.NoPersistence); + prop.AssertCanSet(Components.Persistence().MaxCachedUsers(2)); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 5e39ded6..02f15aae 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -10,7 +10,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { public class DataSourceUpdateSinkImplTest : BaseTest { - private readonly InMemoryDataStore _store; + private readonly FlagDataManager _store; private readonly FlagTrackerImpl _flagTracker; private readonly DataSourceUpdateSinkImpl _updateSink; private readonly User _basicUser = User.WithKey("user-key"); @@ -18,7 +18,7 @@ public class DataSourceUpdateSinkImplTest : BaseTest public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) { - _store = new InMemoryDataStore(); + _store = new FlagDataManager(BasicMobileKey, null, testLogger); _updateSink = new DataSourceUpdateSinkImpl(_store, false, BasicTaskExecutor, testLogger); _flagTracker = new FlagTrackerImpl(_updateSink); } @@ -29,7 +29,7 @@ public void InitPassesDataToStore() var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build(); _updateSink.Init(_basicUser, initData); - Assert.Equal(initData.Items, _store.GetAll(_basicUser).Value.Items); + Assert.Equal(initData.Items, _store.GetAll().Value.Items); } [Fact] @@ -43,7 +43,7 @@ public void UpsertPassesDataToStore() _updateSink.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); - Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); + Assert.Equal(flag1b.ToItemDescriptor(), _store.Get("key1")); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index da0b24ff..95f4a68b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -15,10 +15,13 @@ public class PollingDataSourceTest : BaseTest "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + "}"; - private InMemoryDataStore _store = new InMemoryDataStore(); + private FlagDataManager _store; User user; - public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } + public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) + { + _store = new FlagDataManager(BasicMobileKey, null, testLogger); + } IDataSource MakeDataSource() { @@ -47,7 +50,7 @@ public void StartWaitsUntilFlagCacheFilled() var dataSource = MakeDataSource(); var initTask = dataSource.Start(); var unused = initTask.Wait(TimeSpan.FromSeconds(1)); - var flags = _store.GetAll(user); + var flags = _store.GetAll(); Assert.Equal(3, flags.Value.Items.Count); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs new file mode 100644 index 00000000..eba99da4 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs @@ -0,0 +1,205 @@ +using Xunit; +using Xunit.Abstractions; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class FlagDataManagerTest : BaseTest + { + private readonly FlagDataManager _store; + + public FlagDataManagerTest(ITestOutputHelper testOutput) : base(testOutput) + { + _store = new FlagDataManager(BasicMobileKey, null, testLogger); + } + + [Fact] + public void GetCachedDataReturnsNullWithPersistenceDisabled() + { + Assert.Null(_store.GetCachedData(BasicUser)); + } + + [Fact] + public void PersistentStoreIsNullWithPersistenceDisabled() + { + Assert.Null(_store.PersistentStore); + } + + [Fact] + public void GetUnknownFlagWhenNotInitialized() + { + Assert.Null(_store.Get("flagkey")); + } + + [Fact] + public void GetUnknownFlagKeyAfterInitialized() + { + var initData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Build(); + _store.Init(BasicUser, initData, false); + + Assert.Null(_store.Get("flag2")); + } + + [Fact] + public void GetKnownFlag() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1) + .Build(); + _store.Init(BasicUser, initData, false); + + Assert.Equal(flag1.ToItemDescriptor(), _store.Get("flag1")); + } + + [Fact] + public void GetDeletedFlagForKnownUser() + { + var initData = new DataSetBuilder() + .AddDeleted("flag1", 200) + .Build(); + _store.Init(BasicUser, initData, false); + + Assert.Equal(new ItemDescriptor(200, null), _store.Get("flag1")); + } + + [Fact] + public void GetAllWhenNotInitialized() + { + var data = _store.GetAll(); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(DataSetBuilder.Empty, data.Value); + } + + [Fact] + public void GetAllWithEmptyFlags() + { + _store.Init(BasicUser, DataSetBuilder.Empty, false); + + var data = _store.GetAll(); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(DataSetBuilder.Empty, data.Value); + } + + [Fact] + public void GetAllReturnsFlags() + { + var initData = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Add("flag2", 2, LdValue.Of(false), 1) + .Build(); + _store.Init(BasicUser, initData, false); + + var data = _store.GetAll(); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(initData, data.Value); + } + + [Fact] + public void UpsertAddsFlag() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(false)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1) + .Build(); + _store.Init(BasicUser, initData, false); + + var updated = _store.Upsert("flag2", flag2.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag2.ToItemDescriptor(), _store.Get("flag2")); + } + + [Fact] + public void UpsertUpdatesFlag() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1a) + .Build(); + _store.Init(BasicUser, initData, false); + + var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Build(); + var updated = _store.Upsert("flag1", flag1b.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag1b.ToItemDescriptor(), _store.Get("flag1")); + } + + [Fact] + public void UpsertDeletesFlag() + { + var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1) + .Build(); + _store.Init(BasicUser, initData, false); + + var flag1Deleted = new ItemDescriptor(101, null); + var updated = _store.Upsert("flag1", flag1Deleted); + Assert.True(updated); + + Assert.Equal(flag1Deleted, _store.Get("flag1")); + } + + [Fact] + public void UpsertUndeletesFlag() + { + var initData = new DataSetBuilder() + .AddDeleted("flag1", 100) + .Build(); + _store.Init(BasicUser, initData, false); + + var flag1 = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(true)).Build(); + + var updated = _store.Upsert("flag1", flag1.ToItemDescriptor()); + Assert.True(updated); + + Assert.Equal(flag1.ToItemDescriptor(), _store.Get("flag1")); + } + + [Theory] + [InlineData(100, 100)] + [InlineData(100, 99)] + public void UpsertDoesNotUpdateFlagWithEqualOrLowerVersion(int previousVersion, int newVersion) + { + var flag1a = new FeatureFlagBuilder().Version(previousVersion).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1a) + .Build(); + + _store.Init(BasicUser, initData, false); + + var flag1b = new FeatureFlagBuilder().Version(newVersion).Value(LdValue.Of(false)).Build(); + + var updated = _store.Upsert("flag1", flag1b.ToItemDescriptor()); + Assert.False(updated); + + Assert.Equal(flag1a.ToItemDescriptor(), _store.Get("flag1")); + } + + [Theory] + [InlineData(100, 100)] + [InlineData(100, 99)] + public void UpsertDoesNotDeleteFlagWithEqualOrLowerVersion(int previousVersion, int newVersion) + { + var flag1a = new FeatureFlagBuilder().Version(previousVersion).Value(LdValue.Of(true)).Build(); + var initData = new DataSetBuilder() + .Add("flag1", flag1a) + .Build(); + + _store.Init(BasicUser, initData, false); + + var deletedDesc = new ItemDescriptor(newVersion, null); + + var updated = _store.Upsert("flag1", deletedDesc); + Assert.False(updated); + + Assert.Equal(flag1a.ToItemDescriptor(), _store.Get("flag1")); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs new file mode 100644 index 00000000..dc09a1c8 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -0,0 +1,223 @@ +using System.Collections.Immutable; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class FlagDataManagerWithPersistenceTest : BaseTest + { + private static readonly User OtherUser = User.WithKey("other-user"); + private static readonly FullDataSet DataSet1 = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Add("flag2", 2, LdValue.Of(false), 1) + .Build(); + private static readonly FullDataSet DataSet2 = new DataSetBuilder() + .Add("flag3", 1, LdValue.Of(true), 0) + .Build(); + + private readonly MockPersistentDataStore _persistentStore; + + public FlagDataManagerWithPersistenceTest(ITestOutputHelper testOutput) : base(testOutput) + { + _persistentStore = new MockPersistentDataStore(); + } + + internal FlagDataManager MakeStore(int maxCachedUsers) => + new FlagDataManager(BasicMobileKey, + new PersistenceConfiguration(_persistentStore, maxCachedUsers), testLogger); + + [Fact] + public void PersistentStoreIsNullIfMaxCachedUsersIsZero() + { + Assert.Null(MakeStore(0).PersistentStore); + } + + [Fact] + public void PersistentStoreIsNullWithNoOpStoreImplementation() + { + var store = new FlagDataManager(BasicMobileKey, + new PersistenceConfiguration(NullPersistentDataStore.Instance, 5), testLogger); + Assert.Null(store.PersistentStore); + } + + [Fact] + public void PersistentStoreIsNotNullWithValidStoreImplementationAndNonZeroUsers() + { + Assert.NotNull(MakeStore(5).PersistentStore); + } + + [Fact] + public void GetCachedDataForUnknownUser() + { + var store = MakeStore(1); + Assert.Null(store.GetCachedData(BasicUser)); + } + + [Fact] + public void GetCachedDataForKnownUser() + { + _persistentStore.SetupUserData(BasicMobileKey, BasicUser.Key, DataSet1); + _persistentStore.SetupUserData(BasicMobileKey, OtherUser.Key, DataSet2); + + var store = MakeStore(1); + + var data = store.GetCachedData(BasicUser); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(DataSet1, data.Value); + } + + [Fact] + public void InitWritesToPersistentStoreIfToldTo() + { + var store = MakeStore(1); + store.Init(BasicUser, DataSet1, true); + + var data = _persistentStore.InspectUserData(BasicMobileKey, BasicUser.Key); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(DataSet1, data.Value); + } + + [Fact] + public void InitDoesNotWriteToPersistentStoreIfToldNotTo() + { + _persistentStore.SetupUserData(BasicMobileKey, BasicUser.Key, DataSet1); + + var store = MakeStore(1); + + var data = store.GetCachedData(BasicUser); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(DataSet1, data.Value); + + // Hack the underlying store to remove the data so we can tell if it gets rewritten + _persistentStore.SetupUserData(BasicMobileKey, BasicUser.Key, DataSetBuilder.Empty); + + store.Init(BasicUser, data.Value, false); + + // Because we passed false in Init, it does not rewrite the data - this behavior is to + // avoid unnecessary writes on startup when we've just read the data from the cache. + var underlyingData = _persistentStore.InspectUserData(BasicMobileKey, BasicUser.Key); + Assert.NotNull(underlyingData); + AssertHelpers.DataSetsEqual(DataSetBuilder.Empty, underlyingData.Value); + } + + [Fact] + public void InitUpdatesIndex() + { + var store = MakeStore(2); + + store.Init(BasicUser, DataSet1, true); + store.Init(OtherUser, DataSet2, true); + + var index = _persistentStore.InspectUserIndex(BasicMobileKey); + Assert.Equal( + ImmutableList.Create(Base64.Sha256Hash(BasicUser.Key), Base64.Sha256Hash(OtherUser.Key)), + index.Data.Select(e => e.UserId).ToImmutableList() + ); + } + + [Fact] + public void InitEvictsLeastRecentUser() + { + var dataSet3 = new DataSetBuilder() + .Add("flag4", 4, LdValue.Of(false), 1) + .Build(); + var user3 = User.WithKey("third-user"); + + var store = MakeStore(2); + store.Init(BasicUser, DataSet1, true); + store.Init(OtherUser, DataSet2, true); + store.Init(user3, dataSet3, true); + + var index = _persistentStore.InspectUserIndex(BasicMobileKey); + Assert.Equal( + ImmutableList.Create(Base64.Sha256Hash(OtherUser.Key), Base64.Sha256Hash(user3.Key)), + index.Data.Select(e => e.UserId).ToImmutableList() + ); + } + + [Fact] + public void GetDoesNotReadFromPersistentStore() + { + var flag1a = new FeatureFlagBuilder().Version(1).Value(true).Build(); + var flag1b = new FeatureFlagBuilder().Version(2).Value(false).Build(); + var data1a = new DataSetBuilder().Add("flag1", flag1a).Build(); + var data1b = new DataSetBuilder().Add("flag1", flag1b).Build(); + + var store = MakeStore(1); + store.Init(BasicUser, data1a, true); + + // Hack the underlying store to change the data so we can verify it isn't being reread + _persistentStore.SetupUserData(BasicMobileKey, BasicUser.Key, data1b); + + var item = store.Get("flag1"); + Assert.Equal(flag1a.ToItemDescriptor(), item); + } + + [Fact] + public void GetAllDoesNotReadFromPersistentStore() + { + var flag1 = new FeatureFlagBuilder().Version(1).Value(true).Build(); + var flag2 = new FeatureFlagBuilder().Version(2).Value(false).Build(); + var data1 = new DataSetBuilder().Add("flag1", flag1).Build(); + var data2 = new DataSetBuilder().Add("flag2", flag2).Build(); + + var store = MakeStore(1); + store.Init(BasicUser, data1, true); + + // Hack the underlying store to change the data so we can verify it isn't being reread + _persistentStore.SetupUserData(BasicMobileKey, BasicUser.Key, data2); + + var data = store.GetAll(); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(data1, data.Value); + } + + [Fact] + public void UpsertUpdatesPersistentStore() + { + var flag1a = new FeatureFlagBuilder().Version(1).Value(true).Build(); + var flag1b = new FeatureFlagBuilder().Version(2).Value(true).Build(); + var flag2 = new FeatureFlagBuilder().Version(1).Value(false).Build(); + var data1a = new DataSetBuilder().Add("flag1", flag1a).Add("flag2", flag2).Build(); + var data1b = new DataSetBuilder().Add("flag1", flag1b).Add("flag2", flag2).Build(); + + var store = MakeStore(1); + store.Init(BasicUser, data1a, true); + + var updated = store.Upsert("flag1", flag1b.ToItemDescriptor()); + Assert.True(updated); + + var item = store.Get("flag1"); // this is reading only from memory, not the persistent store + Assert.Equal(flag1b.ToItemDescriptor(), item); + + var data = _persistentStore.InspectUserData(BasicMobileKey, BasicUser.Key); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(data1b, data.Value); + } + + [Fact] + public void UpsertDoesNotUpdatePersistentStoreIfUpdateIsUnsuccessful() + { + var flag1a = new FeatureFlagBuilder().Version(100).Value(true).Build(); + var flag1b = new FeatureFlagBuilder().Version(99).Value(true).Build(); + var flag2 = new FeatureFlagBuilder().Version(1).Value(false).Build(); + var data1a = new DataSetBuilder().Add("flag1", flag1a).Add("flag2", flag2).Build(); + + var store = MakeStore(1); + store.Init(BasicUser, data1a, true); + + var updated = store.Upsert("flag1", flag1b.ToItemDescriptor()); + Assert.False(updated); + + var item = store.Get("flag1"); // this is reading only from memory, not the persistent store + Assert.Equal(flag1a.ToItemDescriptor(), item); + + var data = _persistentStore.InspectUserData(BasicMobileKey, BasicUser.Key); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(data1a, data.Value); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs deleted file mode 100644 index fdeda517..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/InMemoryDataStoreTest.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit; - -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; - -namespace LaunchDarkly.Sdk.Client.Internal.DataStores -{ - public class InMemoryDataStoreTest - { - private readonly InMemoryDataStore _store = new InMemoryDataStore(); - private readonly User _basicUser = User.WithKey("user-key"); - private readonly User _otherUser = User.WithKey("other-key"); - - [Fact] - public void GetForUnknownUser() - { - Assert.Null(_store.Get(_basicUser, "key1")); - } - - [Fact] - public void GetUnknownFlagForKnownUser() - { - var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var initData = new DataSetBuilder() - .Add("key1", flag1) - .Build(); - _store.Init(_basicUser, initData); - - var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(false)).Build(); - var otherData = new DataSetBuilder().Add("key2", flag2).Build(); - _store.Init(_otherUser, otherData); - - Assert.Null(_store.Get(_basicUser, "key2")); - } - - [Fact] - public void GetDeletedFlagForKnownUser() - { - var initData = new DataSetBuilder() - .AddDeleted("key2", 200) - .Build(); - _store.Init(_basicUser, initData); - - Assert.Equal(new ItemDescriptor(200, null), _store.Get(_basicUser, "key2")); - } - - [Fact] - public void GetAllForUnknownUser() - { - Assert.Null(_store.GetAll(_basicUser)); - } - - [Fact] - public void GetAllForKnownUser() - { - var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); - var initData = new DataSetBuilder() - .Add("key1", flag1) - .Add("key2", flag2) - .AddDeleted("deletedKey", 300) - .Build(); - - _store.Init(_basicUser, initData); - - var otherData = new DataSetBuilder().AddDeleted("key1", 100).Build(); - _store.Init(_otherUser, otherData); - - var result = _store.GetAll(_basicUser); - Assert.NotNull(result); - Assert.Equal(initData, result.Value); - } - - [Fact] - public void UpsertAddsFlagForUnknownUser() - { - var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - - var updated = _store.Upsert(_basicUser, "key1", flag1.ToItemDescriptor()); - Assert.True(updated); - - Assert.Equal(flag1.ToItemDescriptor(), _store.Get(_basicUser, "key1")); - } - - [Fact] - public void UpsertAddsFlagForKnownUser() - { - var flag1 = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var initData = new DataSetBuilder() - .Add("key1", flag1) - .Build(); - - _store.Init(_basicUser, initData); - - var flag2 = new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Build(); - - var updated = _store.Upsert(_basicUser, "key2", flag2.ToItemDescriptor()); - Assert.True(updated); - - Assert.Equal(flag2.ToItemDescriptor(), _store.Get(_basicUser, "key2")); - } - - [Fact] - public void UpsertUpdatesFlagForKnownUser() - { - var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var expected = new DataSetBuilder() - .Add("key1", flag1a) - .Build(); - - _store.Init(_basicUser, expected); - - var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Build(); - - var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); - Assert.True(updated); - - Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); - } - - [Fact] - public void UpsertDeletesFlagForKnownUser() - { - var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var expected = new DataSetBuilder() - .Add("key1", flag1a) - .Build(); - - _store.Init(_basicUser, expected); - - var flag1Deleted = new ItemDescriptor(101, null); - - var updated = _store.Upsert(_basicUser, "key1", flag1Deleted); - Assert.True(updated); - - Assert.Equal(flag1Deleted, _store.Get(_basicUser, "key1")); - } - - [Fact] - public void UpsertUndeletesFlagForKnownUser() - { - var expected = new DataSetBuilder() - .AddDeleted("key1", 100) - .Build(); - - _store.Init(_basicUser, expected); - - var flag1b = new FeatureFlagBuilder().Version(101).Value(LdValue.Of(false)).Build(); - - var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); - Assert.True(updated); - - Assert.Equal(flag1b.ToItemDescriptor(), _store.Get(_basicUser, "key1")); - } - - [Fact] - public void UpsertDoesNotUpdateFlagForKnownUserWithLowerVersion() - { - var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var expected = new DataSetBuilder() - .Add("key1", flag1a) - .Build(); - - _store.Init(_basicUser, expected); - - var flag1b = new FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Build(); - - var updated = _store.Upsert(_basicUser, "key1", flag1b.ToItemDescriptor()); - Assert.False(updated); - - Assert.Equal(flag1a.ToItemDescriptor(), _store.Get(_basicUser, "key1")); - } - - [Fact] - public void UpsertDoesNotDeleteFlagForKnownUserWithLowerVersion() - { - var flag1a = new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Build(); - var expected = new DataSetBuilder() - .Add("key1", flag1a) - .Build(); - - _store.Init(_basicUser, expected); - - var deletedDesc = new ItemDescriptor(99, null); - - var updated = _store.Upsert(_basicUser, "key1", deletedDesc); - Assert.False(updated); - - Assert.Equal(flag1a.ToItemDescriptor(), _store.Get(_basicUser, "key1")); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs index 8b099717..1fa974dc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -1,135 +1,114 @@ using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.TestHelpers.JsonAssertions; + namespace LaunchDarkly.Sdk.Client.Internal.DataStores { public class PersistentDataStoreWrapperTest : BaseTest { - private static readonly User _basicUser = User.WithKey("user-key"); - private static readonly User _otherUser = User.WithKey("user-key"); + // This verifies non-platform-dependent behavior, such as what keys we store particular + // things under, using a mock persistent storage implementation. + + private static readonly string MobileKeyHash = Base64.Sha256Hash(BasicMobileKey); + private static readonly string ExpectedGlobalNamespace = "LaunchDarkly"; + private static readonly string ExpectedEnvironmentNamespace = "LaunchDarkly:" + MobileKeyHash; + private const string UserKey = "user-key"; + private static readonly string UserHash = Base64.Sha256Hash(UserKey); + private static readonly string ExpectedUserFlagsKey = "flags:" + UserHash; + private static readonly string ExpectedIndexKey = "index"; + private static readonly string ExpectedAnonUserKey = "anonUser"; - private readonly InMemoryDataStore _inMemoryStore; private readonly MockPersistentDataStore _persistentStore; private readonly PersistentDataStoreWrapper _wrapper; public PersistentDataStoreWrapperTest(ITestOutputHelper testOutput) : base(testOutput) { - _inMemoryStore = new InMemoryDataStore(); _persistentStore = new MockPersistentDataStore(); - _wrapper = new PersistentDataStoreWrapper(_inMemoryStore, _persistentStore, testLogger); + _wrapper = new PersistentDataStoreWrapper( + _persistentStore, + BasicMobileKey, + testLogger + ); } [Fact] - public void PreloadDoesNotInitializeStoreForUserWhoIsNotInPersistentStore() + public void GetUserDataForUnknownUser() { - _wrapper.Preload(_basicUser); - - Assert.Null(_inMemoryStore.GetAll(_basicUser)); + var data = _wrapper.GetUserData(UserKey); + Assert.Null(data); + Assert.Empty(logCapture.GetMessages()); } [Fact] - public void PreloadDoesNotOverwriteStoreIfDataIsAlreadyCached() + public void GetUserDataForKnownUserWithValidData() { - var inMemoryData = new DataSetBuilder() - .Add("flag1", 2, LdValue.Of(true), 0) - .Build(); - _inMemoryStore.Init(_basicUser, inMemoryData); - - var persistedData = new DataSetBuilder() - .Add("flag1", 1, LdValue.Of(false), 1) - .Build(); - _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); - - _wrapper.Preload(_basicUser); - - Assert.Equal(inMemoryData, _inMemoryStore.GetAll(_basicUser)); + var expectedData = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); + var serializedData = expectedData.ToJsonString(); + _persistentStore.SetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey, serializedData); + + var data = _wrapper.GetUserData(UserHash); + Assert.NotNull(data); + AssertHelpers.DataSetsEqual(expectedData, data.Value); + Assert.Empty(logCapture.GetMessages()); } [Fact] - public void PreloadGetsDataFromPersistentStoreIfNotAlreadyCached() + public void SetUserData() { - var persistedData = new DataSetBuilder() - .Add("flag1", 1, LdValue.Of(false), 1) - .Build(); - _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + var data = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); - _wrapper.Preload(_basicUser); + _wrapper.SetUserData(UserHash, data); - Assert.Equal(persistedData, _inMemoryStore.GetAll(_basicUser)); + var serializedData = _persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey); + AssertJsonEqual(data.ToJsonString(), serializedData); } [Fact] - public void InitWritesToBothMemoryAndPersistentStore() + public void RemoveUserData() { - var data = new DataSetBuilder() - .Add("flag1", 1, LdValue.Of(true), 0) - .Build(); - - _wrapper.Init(_basicUser, data); + var data = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); - Assert.Equal(data, _inMemoryStore.GetAll(_basicUser)); + _wrapper.SetUserData(UserHash, data); + Assert.NotNull(_persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey)); - Assert.Equal(data, DataModelSerialization.DeserializeAll(_persistentStore.GetAll(_basicUser))); + _wrapper.RemoveUserData(UserHash); + Assert.Null(_persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey)); } [Fact] - public void GetReadsOnlyInMemoryStore() + public void GetIndex() { - var flag1a = new FeatureFlagBuilder().Version(2).Value(true).Variation(0).Build(); - var inMemoryData = new DataSetBuilder() - .Add("flag1", flag1a) - .Build(); - _inMemoryStore.Init(_basicUser, inMemoryData); - - var flag1b = new FeatureFlagBuilder().Version(1).Value(false).Variation(1).Build(); - var persistedData = new DataSetBuilder() - .Add("flag1", flag1b) - .Build(); - _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); - - Assert.Equal(flag1a.ToItemDescriptor(), _wrapper.Get(_basicUser, "flag1")); + var expectedIndex = new UserIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); + _persistentStore.SetValue(ExpectedEnvironmentNamespace, ExpectedIndexKey, expectedIndex.Serialize()); + + var index = _wrapper.GetIndex(); + AssertJsonEqual(expectedIndex.Serialize(), index.Serialize()); } [Fact] - public void GetAllReadsOnlyInMemoryStore() + public void SetIndex() { - var inMemoryData = new DataSetBuilder() - .Add("flag1", 2, LdValue.Of(true), 0) - .Build(); - _inMemoryStore.Init(_basicUser, inMemoryData); + var index = new UserIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); - var persistedData = new DataSetBuilder() - .Add("flag1", 1, LdValue.Of(false), 1) - .Build(); - _persistentStore.Init(_basicUser, DataModelSerialization.SerializeAll(persistedData)); + _wrapper.SetIndex(index); - Assert.Equal(inMemoryData, _wrapper.GetAll(_basicUser)); + var serializedData = _persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedIndexKey); + AssertJsonEqual(index.Serialize(), serializedData); } [Fact] - public void UpsertPersistsAllDataAfterUpdatingMemory() + public void GetAnonymousUserKey() { - var flag1a = new FeatureFlagBuilder().Version(100).Value(true).Variation(0).Build(); - var flag1b = new FeatureFlagBuilder().Version(101).Value(false).Variation(1).Build(); - var flag2 = new FeatureFlagBuilder().Version(200).Value(true).Variation(0).Build(); - var initialData = new DataSetBuilder() - .Add("flag1", flag1a) - .Add("flag2", flag2) - .Build(); - - _wrapper.Init(_basicUser, initialData); - - _wrapper.Upsert(_basicUser, "flag1", flag1b.ToItemDescriptor()); - - Assert.Equal(flag1b.ToItemDescriptor(), _inMemoryStore.Get(_basicUser, "flag1")); - Assert.Equal(flag2.ToItemDescriptor(), _inMemoryStore.Get(_basicUser, "flag2")); - - var updatedData = new DataSetBuilder() - .Add("flag1", flag1b) - .Add("flag2", flag2) - .Build(); + _persistentStore.SetValue(ExpectedGlobalNamespace, ExpectedAnonUserKey, "user1"); + Assert.Equal("user1", _wrapper.GetAnonymousUserKey()); + } - Assert.Equal(updatedData, DataModelSerialization.DeserializeAll(_persistentStore.GetAll(_basicUser))); + [Fact] + public void SetAnonymousUserKey() + { + _wrapper.SetAnonymousUserKey("user1"); + Assert.Equal("user1", _persistentStore.GetValue(ExpectedGlobalNamespace, ExpectedAnonUserKey)); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs new file mode 100644 index 00000000..4bc27574 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs @@ -0,0 +1,100 @@ +using System.Linq; +using LaunchDarkly.Sdk.Client.Interfaces; +using Xunit; +using Xunit.Abstractions; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class PlatformLocalStorageTest : BaseTest + { + private const string TestNamespacePrefix = "LaunchDarkly.PlatformLocalStorageTest."; + private const string TestNamespace1 = TestNamespacePrefix + "Things1"; + private const string TestNamespace2 = TestNamespacePrefix + "Things2"; + private const string TestNamespaceThatIsNeverSet = TestNamespacePrefix + "Unused"; + private const string TestKeyThatIsNeverSet = "unused-key"; + + private static readonly IPersistentDataStore _storage = PlatformSpecific.LocalStorage.Instance; + + public PlatformLocalStorageTest(ITestOutputHelper testOutput) : base(testOutput) { } + + [Fact] + public void GetValueUnknownNamespace() + { + _storage.SetValue(TestNamespace1, "key1", "x"); + Assert.Null(_storage.GetValue(TestNamespaceThatIsNeverSet, "key1")); + } + + [Fact] + public void GetValueUnknownKey() + { + _storage.SetValue(TestNamespace1, "key1", "x"); + Assert.Null(_storage.GetValue(TestNamespace1, TestKeyThatIsNeverSet)); + } + + [Fact] + public void GetAndSetValues() + { + _storage.SetValue(TestNamespace1, "key1", "value1a"); + _storage.SetValue(TestNamespace1, "key2", "value2a"); + _storage.SetValue(TestNamespace2, "key1", "value1b"); + _storage.SetValue(TestNamespace2, "key2", "value2b"); + + Assert.Equal("value1a", _storage.GetValue(TestNamespace1, "key1")); + Assert.Equal("value2a", _storage.GetValue(TestNamespace1, "key2")); + Assert.Equal("value1b", _storage.GetValue(TestNamespace2, "key1")); + Assert.Equal("value2b", _storage.GetValue(TestNamespace2, "key2")); + } + + [Fact] + public void RemoveValues() + { + _storage.SetValue(TestNamespace1, "key1", "value1a"); + _storage.SetValue(TestNamespace1, "key2", "value2a"); + _storage.SetValue(TestNamespace2, "key1", "value1b"); + _storage.SetValue(TestNamespace2, "key2", "value2b"); + + _storage.SetValue(TestNamespace1, "key1", null); + _storage.SetValue(TestNamespace2, "key2", null); + + Assert.Null(_storage.GetValue(TestNamespace1, "key1")); + Assert.Equal("value2a", _storage.GetValue(TestNamespace1, "key2")); + Assert.Equal("value1b", _storage.GetValue(TestNamespace2, "key1")); + Assert.Null(_storage.GetValue(TestNamespace2, "key2")); + } + + [Fact] + public void RemoveUnknownKey() + { + _storage.SetValue(TestNamespace1, "key1", "x"); + _storage.SetValue(TestNamespace1, "key2", null); + + Assert.Equal("x", _storage.GetValue(TestNamespace1, "key1")); + } + + [Fact] + public void KeysWithSpecialCharacters() + { + var storageNamespace = TestNamespacePrefix + nameof(KeysWithSpecialCharacters); + var keys = new string[] + { + "$", + "/", + ".", + "?", + "key/with//slashes", + "key.with.dots", + "këy.wíth.âccents" + }; + var keysAndValues = keys.ToDictionary(key => key, key => "value-" + key); + foreach (var kv in keysAndValues) + { + testLogger.Info("*** setting {0} to {1}", kv.Key, kv.Value); + _storage.SetValue(storageNamespace, kv.Key, kv.Value); + } + foreach (var kv in keysAndValues) + { + Assert.Equal(kv.Value, _storage.GetValue(storageNamespace, kv.Key)); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs new file mode 100644 index 00000000..818c4411 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Immutable; +using Xunit; + +using static LaunchDarkly.TestHelpers.JsonAssertions; + +namespace LaunchDarkly.Sdk.Client.Internal.DataStores +{ + public class UserIndexTest + { + [Fact] + public void EmptyConstructor() + { + var ui = new UserIndex(); + Assert.NotNull(ui.Data); + Assert.Empty(ui.Data); + } + + [Fact] + public void Serialize() + { + var ui = new UserIndex() + .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) + .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); + + var json = ui.Serialize(); + var expected = @"[[""user1"",1000],[""user2"",2000]]"; + + AssertJsonEqual(expected, json); + } + + [Fact] + public void Deserialize() + { + var json = @"[[""user1"",1000],[""user2"",2000]]"; + var ui = UserIndex.Deserialize(json); + + Assert.NotNull(ui.Data); + Assert.Collection(ui.Data, + AssertEntry("user1", 1000), + AssertEntry("user2", 2000)); + } + + [Fact] + public void DeserializeMalformedJson() + { + Assert.ThrowsAny(() => + UserIndex.Deserialize("}")); + + Assert.ThrowsAny(() => + UserIndex.Deserialize("[")); + + Assert.ThrowsAny(() => + UserIndex.Deserialize("[[true,1000]]")); + + Assert.ThrowsAny(() => + UserIndex.Deserialize(@"[[""user1"",false]]")); + + Assert.ThrowsAny(() => + UserIndex.Deserialize("[3]")); + } + + [Fact] + public void UpdateTimestampForExistingUser() + { + var ui = new UserIndex() + .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) + .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); + + ui = ui.UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(2001)); + + Assert.Collection(ui.Data, + AssertEntry("user2", 2000), + AssertEntry("user1", 2001)); + } + + [Fact] + public void PruneRemovesLeastRecentUsers() + { + var ui = new UserIndex() + .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) + .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)) + .UpdateTimestamp("user3", UnixMillisecondTime.OfMillis(1111)) // deliberately out of order + .UpdateTimestamp("user4", UnixMillisecondTime.OfMillis(3000)) + .UpdateTimestamp("user5", UnixMillisecondTime.OfMillis(4000)); + + var ui1 = ui.Prune(3, out var removed); + Assert.Equal(ImmutableList.Create("user1", "user3"), removed); + Assert.Collection(ui1.Data, + AssertEntry("user2", 2000), + AssertEntry("user4", 3000), + AssertEntry("user5", 4000)); + } + + [Fact] + public void PruneWhenLimitIsNotExceeded() + { + var ui = new UserIndex() + .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) + .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); + + Assert.Same(ui, ui.Prune(3, out var removed1)); + Assert.Empty(removed1); + + Assert.Same(ui, ui.Prune(2, out var removed2)); + Assert.Empty(removed2); + } + + private Action AssertEntry(string id, int millis) => + e => + { + Assert.Equal(id, e.UserId); + Assert.Equal(UnixMillisecondTime.OfMillis(millis), e.Timestamp); + }; + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DefaultDeviceInfoTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DefaultDeviceInfoTests.cs deleted file mode 100644 index 3c8febb7..00000000 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DefaultDeviceInfoTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using LaunchDarkly.Sdk.Client.PlatformSpecific; -using Xunit; -using Xunit.Abstractions; - -namespace LaunchDarkly.Sdk.Client.Internal -{ - // The DefaultDeviceInfo functionality is also tested by LdClientEndToEndTests.InitWithKeylessAnonUserAddsKeyAndReusesIt(), - // which is a more realistic test since it uses a full client instance. However, currently LdClientEndToEndTests can't be - // run on every platform, so we'll also test the lower-level logic here. - public class DefaultDeviceInfoTests : BaseTest - { - public DefaultDeviceInfoTests(ITestOutputHelper testOutput) : base(testOutput) { } - - [Fact] - public void UniqueDeviceIdGeneratesStableValue() - { - // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, - // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. - ClientIdentifier.ClearCachedClientId(testLogger); - - var ddi = new DefaultDeviceInfo(testLogger); - var id0 = ddi.UniqueDeviceId(); - Assert.NotNull(id0); - - var id1 = ddi.UniqueDeviceId(); - Assert.Equal(id0, id1); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 219a665f..2a295b1c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -21,8 +21,6 @@ namespace LaunchDarkly.Sdk.Client // expected ways. public class LdClientEndToEndTests : BaseTest { - private const string _mobileKey = "FAKE_KEY"; - private static readonly User _user = User.WithKey("foo"); private static readonly User _otherUser = User.WithKey("bar"); @@ -145,35 +143,6 @@ public async Task InitFailsOn401Async(UpdateMode mode) } } - [Fact] - public async Task InitWithKeylessAnonUserAddsKeyAndReusesIt() - { - // Note, we don't care about polling mode vs. streaming mode for this functionality. - using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) - { - var config = BaseConfig(server.Uri, UpdateMode.Polling); - var name = "Sue"; - var anonUser = User.Builder((string)null).Name(name).Anonymous(true).Build(); - - // Note, on mobile platforms, the generated user key is the device ID and is stable; on other platforms, - // it's a GUID that is cached in local storage. Calling ClearCachedClientId() resets the latter. - ClientIdentifier.ClearCachedClientId(testLogger); - - string generatedKey = null; - using (var client = await TestUtil.CreateClientAsync(config, anonUser)) - { - Assert.NotNull(client.User.Key); - generatedKey = client.User.Key; - Assert.Equal(name, client.User.Name); - } - - using (var client = await TestUtil.CreateClientAsync(config, anonUser)) - { - Assert.Equal(generatedKey, client.User.Key); - } - } - } - [Theory] [MemberData(nameof(PollingAndStreaming))] public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) @@ -269,9 +238,9 @@ string expectedDiagnosticsPath { using (var server = HttpServer.Start(Handlers.Status(202))) { - var config = Configuration.Builder(_mobileKey) - .DataSource(MockPollingProcessor.Factory("{}")) - .Persistence(Components.NoPersistence) + var config = BasicConfig() + .DataSource(MockPollingProcessor.Factory(DataSetBuilder.Empty)) + .Events(Components.SendEvents()) .ServiceEndpoints(Components.ServiceEndpoints().Events(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath)) .Build(); @@ -302,30 +271,24 @@ string expectedDiagnosticsPath [Fact] public void OfflineClientUsesCachedFlagsSync() { + var sharedPersistenceConfig = Components.Persistence() + .Storage(new MockPersistentDataStore().AsSingletonFactory()); + // streaming vs. polling should make no difference for this using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - ClearCachedFlags(_user); - try + var config = BaseConfig(server.Uri, UpdateMode.Polling, c => c.Persistence(sharedPersistenceConfig)); + using (var client = TestUtil.CreateClient(config, _user)) { - // resetting Persistence to the default enables persistent storage - var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.Persistence(null)); - using (var client = TestUtil.CreateClient(config, _user)) - { - VerifyFlagValues(client, _flagData1); - } - - // At this point the SDK should have written the flags to persistent storage for this user key. - // We'll now start over in offline mode, and we should still see the earlier flag values. - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = TestUtil.CreateClient(offlineConfig, _user)) - { - VerifyFlagValues(client, _flagData1); - } + VerifyFlagValues(client, _flagData1); } - finally + + // At this point the SDK should have written the flags to persistent storage for this user key. + // We'll now start over in offline mode, and we should still see the earlier flag values. + var offlineConfig = BasicConfig().Offline(true).Persistence(sharedPersistenceConfig).Build(); + using (var client = TestUtil.CreateClient(offlineConfig, _user)) { - ClearCachedFlags(_user); + VerifyFlagValues(client, _flagData1); } } } @@ -333,29 +296,23 @@ public void OfflineClientUsesCachedFlagsSync() [Fact] public async Task OfflineClientUsesCachedFlagsAsync() { + var sharedPersistenceConfig = Components.Persistence() + .Storage(new MockPersistentDataStore().AsSingletonFactory()); + // streaming vs. polling should make no difference for this using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) { - ClearCachedFlags(_user); - try + var config = BaseConfig(server.Uri, UpdateMode.Polling, c => c.Persistence(sharedPersistenceConfig)); + using (var client = await TestUtil.CreateClientAsync(config, _user)) { - // resetting Persistence to the default enables persistent storage - var config = BaseConfig(server.Uri, UpdateMode.Polling, builder => builder.Persistence(null)); - using (var client = await TestUtil.CreateClientAsync(config, _user)) - { - VerifyFlagValues(client, _flagData1); - } - - // At this point the SDK should have written the flags to persistent storage for this user key. - var offlineConfig = Configuration.Builder(_mobileKey).Offline(true).Build(); - using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) - { - VerifyFlagValues(client, _flagData1); - } + VerifyFlagValues(client, _flagData1); } - finally + + // At this point the SDK should have written the flags to persistent storage for this user key. + var offlineConfig = BasicConfig().Offline(true).Persistence(sharedPersistenceConfig).Build(); + using (var client = await TestUtil.CreateClientAsync(offlineConfig, _user)) { - ClearCachedFlags(_user); + VerifyFlagValues(client, _flagData1); } } } @@ -366,8 +323,6 @@ public async Task BackgroundModeForcesPollingAsync() var mockBackgroundModeManager = new MockBackgroundModeManager(); var backgroundInterval = TimeSpan.FromMilliseconds(50); - ClearCachedFlags(_user); - using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); @@ -414,8 +369,6 @@ public async Task BackgroundModePollingCanBeDisabledAsync() var backgroundInterval = TimeSpan.FromMilliseconds(50); var hackyUpdateDelay = TimeSpan.FromMilliseconds(200); - ClearCachedFlags(_user); - using (var server = HttpServer.Start(Handlers.Switchable(out var switchable))) { switchable.Target = SetupResponse(_flagData1, UpdateMode.Streaming); @@ -461,7 +414,6 @@ public async Task OfflineClientGoesOnlineAndGetsFlagsAsync(UpdateMode mode) { using (var server = HttpServer.Start(SetupResponse(_flagData1, mode))) { - ClearCachedFlags(_user); var config = BaseConfig(server.Uri, mode, builder => builder.Offline(true)); using (var client = await TestUtil.CreateClientAsync(config, _user)) { @@ -542,12 +494,9 @@ public void HttpConfigurationIsAppliedToEvents() private Configuration BaseConfig(Func extraConfig = null) { - var builderInternal = Configuration.Builder(_mobileKey) + var builder = BasicConfig() .Events(new MockEventProcessor().AsSingletonFactory()); - builderInternal - .Logging(testLogging) - .Persistence(Components.NoPersistence); // unless we're specifically testing flag caching, this helps to prevent test state contamination - var builder = extraConfig == null ? builderInternal : extraConfig(builderInternal); + builder = extraConfig is null ? builder : extraConfig(builder); return builder.Build(); } @@ -585,7 +534,7 @@ private RequestInfo VerifyRequest(RequestRecorder recorder, UpdateMode mode) Assert.Matches(mode.FlagsPathRegex, req.Path); Assert.Equal("", req.Query); - Assert.Equal(_mobileKey, req.Headers["Authorization"]); + Assert.Equal(BasicMobileKey, req.Headers["Authorization"]); Assert.Equal("", req.Body); return req; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs index 6ef8c7f8..a4c70c64 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs @@ -7,7 +7,6 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientEvaluationTests : BaseTest { - const string appKey = "some app key"; const string flagKey = "flag-key"; const string nonexistentFlagKey = "some flag key"; static readonly User user = User.WithKey("userkey"); @@ -17,7 +16,7 @@ public class LdClientEvaluationTests : BaseTest public LdClientEvaluationTests(ITestOutputHelper testOutput) : base(testOutput) { } private LdClient MakeClient() => - LdClient.Init(TestUtil.TestConfig("mobile-key").DataSource(_testData).Build(), user, TimeSpan.FromSeconds(1)); + TestUtil.CreateClient(BasicConfig().DataSource(_testData).Build(), user, TimeSpan.FromSeconds(1)); [Fact] public void BoolVariationReturnsValue() diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 4664f0d5..d9d1319b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -22,7 +22,7 @@ public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) } private LdClient MakeClient(User u) => - LdClient.Init(TestUtil.TestConfig().DataSource(_testData).Events(_factory).Build(), + LdClient.Init(BasicConfig().DataSource(_testData).Events(_factory).Build(), u, TimeSpan.FromSeconds(1)); [Fact] @@ -142,9 +142,8 @@ public void IdentifyDoesNotSendAliasEventIfOptedOUt() User oldUser = User.Builder("anon-key").Anonymous(true).Build(); User newUser = User.WithKey("real-key"); - var config = TestUtil.TestConfig() + var config = BasicConfig() .Events(_factory) - .Logging(testLogging) .AutoAliasingOptOut(true) .Build(); @@ -275,10 +274,9 @@ public void VariationSendsFeatureEventForUnknownFlag() [Fact] public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) - .Events(_factory) - .Logging(testLogging); + .Events(_factory); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { @@ -381,10 +379,9 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() [Fact] public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) - .Events(_factory) - .Logging(testLogging); + .Events(_factory); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 28de97e0..1c6fb32c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using LaunchDarkly.Sdk.Client.Internal; using Xunit; using Xunit.Abstractions; @@ -9,90 +8,153 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest { - static readonly string appKey = "some app key"; - static readonly User simpleUser = User.WithKey("user-key"); + private static readonly User KeylessAnonUser = + User.Builder((string)null) + .Anonymous(true) + .Email("example").AsPrivateAttribute() // give it some more attributes so we can verify they are preserved + .Custom("other", 3) + .Build(); public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } - ConfigurationBuilder BaseConfig() => - TestUtil.TestConfig(appKey).Logging(testLogging); - - LdClient Client() - { - var configuration = BaseConfig().Build(); - return TestUtil.CreateClient(configuration, simpleUser); - } - [Fact] public void CannotCreateClientWithNullConfig() { - Assert.Throws(() => LdClient.Init((Configuration)null, simpleUser, TimeSpan.Zero)); + Assert.Throws(() => LdClient.Init((Configuration)null, BasicUser, TimeSpan.Zero)); } [Fact] public void CannotCreateClientWithNullUser() { - var config = BaseConfig().Build(); + var config = BasicConfig().Build(); Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); } [Fact] public void CannotCreateClientWithNegativeWaitTime() { - var config = BaseConfig().Build(); - Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.FromMilliseconds(-2))); + var config = BasicConfig().Build(); + Assert.Throws(() => LdClient.Init(config, BasicUser, TimeSpan.FromMilliseconds(-2))); } [Fact] public void CanCreateClientWithInfiniteWaitTime() { - var config = BaseConfig().Build(); - using (var client = LdClient.Init(config, simpleUser, System.Threading.Timeout.InfiniteTimeSpan)) { } + var config = BasicConfig().Build(); + using (var client = LdClient.Init(config, BasicUser, System.Threading.Timeout.InfiniteTimeSpan)) { } TestUtil.ClearClient(); } [Fact] - public async void InitPassesUserToUpdateProcessorFactory() + public async void InitPassesUserToDataSource() { - MockPollingProcessor stub = new MockPollingProcessor("{}"); - User testUser = User.WithKey("new-user"); + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(stub.AsFactory()) - .Logging(testLogging) .Build(); - using (var client = await LdClient.InitAsync(config, testUser)) + using (var client = await LdClient.InitAsync(config, BasicUser)) { var actualUser = client.User; // may have been transformed e.g. to add device/OS properties - Assert.Equal(testUser.Key, actualUser.Key); + Assert.Equal(BasicUser.Key, actualUser.Key); Assert.Equal(actualUser, stub.ReceivedUser); } - } + } + + [Fact] + public async Task InitWithKeylessAnonUserAddsKey() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var mockDeviceInfo = new MockDeviceInfo("fake-device-id"); + var config = BasicConfig().DeviceInfo(mockDeviceInfo).Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + { + Assert.Equal("fake-device-id", client.User.Key); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + } + +#if __MOBILE__ + [Fact] + public async Task InitWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() + { + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + string generatedKey = null; + using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + { + generatedKey = client.User.Key; + Assert.NotNull(generatedKey); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + + using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + { + Assert.Equal(generatedKey, client.User.Key); + } + } +#endif + +#if !__MOBILE__ + [Fact] + public async Task InitWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatforms() + { + var config = BasicConfig() + .Persistence(Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory())) + .Build(); + + string generatedKey = null; + using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + { + generatedKey = client.User.Key; + Assert.NotNull(generatedKey); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + + using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + { + Assert.Equal(generatedKey, client.User.Key); + } + + // Now use a configuration where persistence is disabled - a different key is generated + var configWithoutPersistence = BasicConfig().Persistence(Components.NoPersistence).Build(); + using (var client = await TestUtil.CreateClientAsync(configWithoutPersistence, KeylessAnonUser)) + { + Assert.NotNull(client.User.Key); + Assert.NotEqual(generatedKey, client.User.Key); + } + } +#endif [Fact] - public async void InitWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProcessorFactory() + public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() { - MockPollingProcessor stub = new MockPollingProcessor("{}"); - User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(stub.AsFactory()) - .Logging(testLogging) .Build(); - using (var client = await LdClient.InitAsync(config, anonUserIn)) + using (var client = await LdClient.InitAsync(config, KeylessAnonUser)) { - Assert.NotSame(anonUserIn, stub.ReceivedUser); - Assert.Equal(MockDeviceInfo.GeneratedId, stub.ReceivedUser.Key); - Assert.True(stub.ReceivedUser.Anonymous); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(stub.ReceivedUser.Key).Build(), + stub.ReceivedUser); } } [Fact] public void IdentifyUpdatesTheUser() { - using (var client = Client()) + using (var client = TestUtil.CreateClient(BasicConfig().Build(), BasicUser)) { var updatedUser = User.WithKey("some new key"); var success = client.Identify(updatedUser, TimeSpan.FromSeconds(1)); @@ -141,14 +203,10 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func(() => client.Identify(null, TimeSpan.Zero)); } @@ -185,7 +243,7 @@ public void IdentifyWithNullUserThrowsException() [Fact] public void IdentifyAsyncWithNullUserThrowsException() { - using (var client = Client()) + using (var client = TestUtil.CreateClient(BasicConfig().Build(), BasicUser)) { Assert.ThrowsAsync(async () => await client.IdentifyAsync(null)); // note that exceptions thrown out of an async task are always wrapped in AggregateException @@ -195,50 +253,126 @@ public void IdentifyAsyncWithNullUserThrowsException() [Fact] public async void IdentifyPassesUserToDataSource() { - MockPollingProcessor stub = new MockPollingProcessor("{}"); + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); User newUser = User.WithKey("new-user"); - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(stub.AsFactory()) - .Logging(testLogging) .Build(); - using (var client = await LdClient.InitAsync(config, simpleUser)) + using (var client = await LdClient.InitAsync(config, BasicUser)) { - var actualUser = client.User; // may have been transformed e.g. to add device/OS properties - Assert.Equal(simpleUser.Key, actualUser.Key); - Assert.Equal(actualUser, stub.ReceivedUser); + AssertHelpers.UsersEqualExcludingAutoProperties(BasicUser, client.User); + Assert.Equal(client.User, stub.ReceivedUser); await client.IdentifyAsync(newUser); - actualUser = client.User; - Assert.Equal(newUser.Key, actualUser.Key); - Assert.Equal(actualUser, stub.ReceivedUser); + AssertHelpers.UsersEqualExcludingAutoProperties(newUser, client.User); + Assert.Equal(client.User, stub.ReceivedUser); } - } + } + + [Fact] + public async Task IdentifyWithKeylessAnonUserAddsKey() + { + var mockDeviceInfo = new MockDeviceInfo("fake-device-id"); + var config = BasicConfig().DeviceInfo(mockDeviceInfo).Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(KeylessAnonUser); + + Assert.Equal("fake-device-id", client.User.Key); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + } + +#if __MOBILE__ + [Fact] + public async Task IdentifyWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() + { + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + string generatedKey = null; + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(KeylessAnonUser); + + generatedKey = client.User.Key; + Assert.NotNull(generatedKey); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + client.Identify(KeylessAnonUser, TimeSpan.FromSeconds(1)); + + Assert.Equal(generatedKey, client.User.Key); + } + } +#endif + +#if !__MOBILE__ + [Fact] + public async Task IdentifyWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatforms() + { + var config = BasicConfig() + .Persistence(Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory())) + .Build(); + + string generatedKey = null; + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(KeylessAnonUser); + + generatedKey = client.User.Key; + Assert.NotNull(generatedKey); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + } + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(KeylessAnonUser); + + Assert.Equal(generatedKey, client.User.Key); + } + + // Now use a configuration where persistence is disabled - a different key is generated + var configWithoutPersistence = BasicConfig().Persistence(Components.NoPersistence).Build(); + using (var client = await TestUtil.CreateClientAsync(configWithoutPersistence, BasicUser)) + { + await client.IdentifyAsync(KeylessAnonUser); + + Assert.NotNull(client.User.Key); + Assert.NotEqual(generatedKey, client.User.Key); + } + } +#endif [Fact] - public async void IdentifyWithAutoGeneratedAnonUserPassesGeneratedUserToUpdateProcessorFactory() + public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() { - MockPollingProcessor stub = new MockPollingProcessor("{}"); - User anonUserIn = User.Builder((String)null).Anonymous(true).Build(); + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(stub.AsFactory()) - .Logging(testLogging) + .DeviceInfo(new MockDeviceInfo()) .Build(); - using (var client = await LdClient.InitAsync(config, simpleUser)) + using (var client = await LdClient.InitAsync(config, BasicUser)) { - var actualUser = client.User; // may have been transformed e.g. to add device/OS properties - Assert.Equal(simpleUser.Key, actualUser.Key); - Assert.Equal(actualUser, stub.ReceivedUser); + await client.IdentifyAsync(KeylessAnonUser); - await client.IdentifyAsync(anonUserIn); - - Assert.NotSame(simpleUser, stub.ReceivedUser); - Assert.Equal(MockDeviceInfo.GeneratedId, stub.ReceivedUser.Key); - Assert.True(stub.ReceivedUser.Anonymous); + AssertHelpers.UsersEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), + client.User); + Assert.Equal(client.User, stub.ReceivedUser); } } @@ -247,10 +381,10 @@ public void SharedClientIsTheOnlyClientAvailable() { TestUtil.WithClientLock(() => { - var config = BaseConfig().Build(); - using (var client = LdClient.Init(config, simpleUser, TimeSpan.Zero)) + var config = BasicConfig().Build(); + using (var client = LdClient.Init(config, BasicUser, TimeSpan.Zero)) { - Assert.Throws(() => LdClient.Init(config, simpleUser, TimeSpan.Zero)); + Assert.Throws(() => LdClient.Init(config, BasicUser, TimeSpan.Zero)); } TestUtil.ClearClient(); }); @@ -262,146 +396,42 @@ public void CanCreateNewClientAfterDisposingOfSharedInstance() TestUtil.WithClientLock(() => { TestUtil.ClearClient(); - var config = TestUtil.TestConfig().Logging(testLogging).Build(); - using (var client0 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + var config = BasicConfig().Build(); + using (var client0 = LdClient.Init(config, BasicUser, TimeSpan.Zero)) { } Assert.Null(LdClient.Instance); // Dispose() is called automatically at end of "using" block - using (var client1 = LdClient.Init(config, simpleUser, TimeSpan.Zero)) { } + using (var client1 = LdClient.Init(config, BasicUser, TimeSpan.Zero)) { } }); } [Fact] - public void ConnectionChangeShouldStopUpdateProcessor() + public void ConnectionChangeShouldStopDataSource() { var mockUpdateProc = new MockPollingProcessor(null); var mockConnectivityStateManager = new MockConnectivityStateManager(true); - var config = TestUtil.TestConfig() + var config = BasicConfig() .DataSource(mockUpdateProc.AsFactory()) .ConnectivityStateManager(mockConnectivityStateManager) - .Logging(testLogging) .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + using (var client = TestUtil.CreateClient(config, BasicUser)) { mockConnectivityStateManager.Connect(false); Assert.False(mockUpdateProc.IsRunning); } } - [Fact] - public void UserWithNullKeyWillHaveUniqueKeySet() - { - var userWithNullKey = User.WithKey(null); - var uniqueId = "some-unique-key"; - var config = TestUtil.TestConfig() - .DeviceInfo(new MockDeviceInfo(uniqueId)) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, userWithNullKey)) - { - Assert.Equal(uniqueId, client.User.Key); - Assert.True(client.User.Anonymous); - } - } - - [Fact] - public void UserWithEmptyKeyWillHaveUniqueKeySet() - { - var userWithEmptyKey = User.WithKey(""); - var uniqueId = "some-unique-key"; - var config = TestUtil.TestConfig() - .DeviceInfo(new MockDeviceInfo(uniqueId)) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, userWithEmptyKey)) - { - Assert.Equal(uniqueId, client.User.Key); - Assert.True(client.User.Anonymous); - } - } - - [Fact] - public void IdentifyWithUserWithNullKeyUsesUniqueGeneratedKey() - { - var userWithNullKey = User.WithKey(null); - var uniqueId = "some-unique-key"; - var config = TestUtil.TestConfig() - .DeviceInfo(new MockDeviceInfo(uniqueId)) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - client.Identify(userWithNullKey, TimeSpan.FromSeconds(1)); - Assert.Equal(uniqueId, client.User.Key); - Assert.True(client.User.Anonymous); - } - } - - [Fact] - public void IdentifyWithUserWithEmptyKeyUsesUniqueGeneratedKey() - { - var userWithEmptyKey = User.WithKey(""); - var uniqueId = "some-unique-key"; - var config = TestUtil.TestConfig() - .DeviceInfo(new MockDeviceInfo(uniqueId)) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - client.Identify(userWithEmptyKey, TimeSpan.FromSeconds(1)); - Assert.Equal(uniqueId, client.User.Key); - Assert.True(client.User.Anonymous); - } - } - - [Fact] - public void AllOtherAttributesArePreservedWhenSubstitutingUniqueUserKey() - { - var user = User.Builder("") - .Secondary("secondary") - .IPAddress("10.0.0.1") - .Country("US") - .FirstName("John") - .LastName("Doe") - .Name("John Doe") - .Avatar("images.google.com/myAvatar") - .Email("test@example.com") - .Custom("attr", "value") - .Build(); - var uniqueId = "some-unique-key"; - var config = TestUtil.TestConfig() - .DeviceInfo(new MockDeviceInfo(uniqueId)) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) - { - client.Identify(user, TimeSpan.FromSeconds(1)); - User newUser = client.User; - Assert.NotEqual(user.Key, newUser.Key); - Assert.Equal(user.Avatar, newUser.Avatar); - Assert.Equal(user.Country, newUser.Country); - Assert.Equal(user.Email, newUser.Email); - Assert.Equal(user.FirstName, newUser.FirstName); - Assert.Equal(user.LastName, newUser.LastName); - Assert.Equal(user.Name, newUser.Name); - Assert.Equal(user.IPAddress, newUser.IPAddress); - Assert.Equal(user.Secondary, newUser.Secondary); - Assert.Equal(user.Custom["attr"], newUser.Custom["attr"]); - Assert.True(newUser.Anonymous); - } - } - [Fact] public void FlagsAreLoadedFromPersistentStorageByDefault() { var storage = new MockPersistentDataStore(); - var flags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); - storage.Init(simpleUser, DataModelSerialization.SerializeAll(flags)); - var config = TestUtil.TestConfig() - .Persistence(storage.AsSingletonFactory()) + var data = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); + var config = BasicConfig() + .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) .Offline(true) - .Logging(testLogging) - .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + .Build(); + storage.SetupUserData(config.MobileKey, BasicUser.Key, data); + + using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.Equal(100, client.IntVariation("flag", 99)); } @@ -412,18 +442,16 @@ public void FlagsAreSavedToPersistentStorageByDefault() { var storage = new MockPersistentDataStore(); var initialFlags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); - var config = TestUtil.TestConfig() - .DataSource(MockPollingProcessor.Factory(TestUtil.MakeJsonData(initialFlags))) - .Persistence(storage.AsSingletonFactory()) - .Logging(testLogging) + var config = BasicConfig() + .DataSource(MockPollingProcessor.Factory(initialFlags)) + .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + + using (var client = TestUtil.CreateClient(config, BasicUser)) { - var storedData = storage.GetAll(simpleUser); + var storedData = storage.InspectUserData(config.MobileKey, BasicUser.Key); Assert.NotNull(storedData); - var flags = DataModelSerialization.DeserializeAll(storedData); - Assert.NotEmpty(flags.Items); - Assert.Equal(100, flags.Items[0].Value.Item.Value.AsInt); + AssertHelpers.DataSetsEqual(initialFlags, storedData.Value); } } @@ -431,11 +459,10 @@ public void FlagsAreSavedToPersistentStorageByDefault() public void EventProcessorIsOnlineByDefault() { var eventProcessor = new MockEventProcessor(); - var config = TestUtil.TestConfig() + var config = BasicConfig() .Events(eventProcessor.AsSingletonFactory()) - .Logging(testLogging) .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.False(eventProcessor.Offline); } @@ -446,13 +473,12 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() { var connectivityStateManager = new MockConnectivityStateManager(true); var eventProcessor = new MockEventProcessor(); - var config = TestUtil.TestConfig() + var config = BasicConfig() .ConnectivityStateManager(connectivityStateManager) .Events(eventProcessor.AsSingletonFactory()) .Offline(true) - .Logging(testLogging) .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.True(eventProcessor.Offline); @@ -476,12 +502,11 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() { var connectivityStateManager = new MockConnectivityStateManager(false); var eventProcessor = new MockEventProcessor(); - var config = TestUtil.TestConfig() + var config = BasicConfig() .ConnectivityStateManager(connectivityStateManager) .Events(eventProcessor.AsSingletonFactory()) - .Logging(testLogging) .Build(); - using (var client = TestUtil.CreateClient(config, simpleUser)) + using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.True(eventProcessor.Offline); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index b9a97e0e..88d6d8cc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.Internal.Events; @@ -48,7 +52,6 @@ private class SinglePersistentDataStoreFactory : IPersistentDataStoreFactory public IPersistentDataStore Instance { get; set; } public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => Instance; } - } internal class MockBackgroundModeManager : IBackgroundModeManager @@ -280,17 +283,39 @@ public Task FeatureFlagsAsync() internal class MockPersistentDataStore : IPersistentDataStore { - private IDictionary map = new Dictionary(); + private Dictionary<(string, string), string> _map = new Dictionary<(string, string), string>(); public void Dispose() { } - public string GetAll(User user) => - map.TryGetValue(user.Key, out var value) ? value : null; + public string GetValue(string storageNamespace, string key) => + _map.TryGetValue((storageNamespace, key), out var value) ? value : null; - public void Init(User user, string allData) + public void SetValue(string storageNamespace, string key, string value) { - map[user.Key] = allData; + if (value is null) + { + _map.Remove((storageNamespace, key)); + } + else + { + _map[(storageNamespace, key)] = value; + } } + + public ImmutableList GetKeys(string storageNamespace) => + _map.Where(kv => kv.Key.Item1 == storageNamespace).Select(kv => kv.Value).ToImmutableList(); + + private PersistentDataStoreWrapper WithWrapper(string mobileKey) => + new PersistentDataStoreWrapper(this, mobileKey, Logs.None.Logger("")); + + internal void SetupUserData(string mobileKey, string userKey, FullDataSet data) => + WithWrapper(mobileKey).SetUserData(Base64.Sha256Hash(userKey), data); + + internal FullDataSet? InspectUserData(string mobileKey, string userKey) => + WithWrapper(mobileKey).GetUserData(Base64.Sha256Hash(userKey)); + + internal UserIndex InspectUserIndex(string mobileKey) => + WithWrapper(mobileKey).GetIndex(); } internal class CapturingDataSourceFactory : IDataSourceFactory @@ -321,22 +346,22 @@ internal class MockPollingProcessor : IDataSource { private IDataSourceUpdateSink _updateSink; private User _user; - private string _flagsJson; + private FullDataSet? _data; public User ReceivedUser => _user; - public MockPollingProcessor(string flagsJson) : this(null, null, flagsJson) { } + public MockPollingProcessor(FullDataSet? data) : this(null, null, data) { } - private MockPollingProcessor(IDataSourceUpdateSink updateSink, User user, string flagsJson) + private MockPollingProcessor(IDataSourceUpdateSink updateSink, User user, FullDataSet? data) { _updateSink = updateSink; _user = user; - _flagsJson = flagsJson; + _data = data; } - public static IDataSourceFactory Factory(string flagsJson) => + public static IDataSourceFactory Factory(FullDataSet? data) => new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => - new MockPollingProcessor(updates, user, flagsJson)); + new MockPollingProcessor(updates, user, data)); public IDataSourceFactory AsFactory() => new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => @@ -362,9 +387,9 @@ public void Dispose() public Task Start() { IsRunning = true; - if (_updateSink != null && _flagsJson != null) + if (_updateSink != null && _data != null) { - _updateSink.Init(_user, DataModelSerialization.DeserializeV1Schema(_flagsJson)); + _updateSink.Init(_user, _data.Value); } return Task.FromResult(true); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index a09736ca..af8acfe5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.JsonStream; -using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using static LaunchDarkly.Sdk.Client.DataModel; @@ -133,16 +132,6 @@ internal static string MakeJsonData(FullDataSet data) return w.GetString(); } - internal static ConfigurationBuilder TestConfig() => TestConfig("mobile-key"); - - internal static ConfigurationBuilder TestConfig(string appKey) => - Configuration.Builder(appKey) - .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .Events(Components.NoEvents) - .DataSource(TestData.DataSource()) - .Persistence(Components.NoPersistence) - .DeviceInfo(new MockDeviceInfo()); - public static string NormalizeJsonUser(LdValue json) { // It's undefined whether a user with no custom attributes will have "custom":{} or not diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index 55681ad4..97228c5c 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -18,7 +18,7 @@ public void SdkReturnsIOsPlatformType() public void UserHasOSAndDeviceAttributesForPlatform() { var baseUser = User.WithKey("key"); - var config = TestUtil.TestConfig("mobileKey").Build(); + var config = BasicConfig().Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { var user = client.User; @@ -33,7 +33,7 @@ public void UserHasOSAndDeviceAttributesForPlatform() public void CanGetUniqueUserKey() { var anonUser = User.Builder((string)null).Anonymous(true).Build(); - var config = TestUtil.TestConfig("mobileKey") + var config = BasicConfig() .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { From f4912605978f1d101dba35b073d5352a42bf2616 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 5 Nov 2021 14:05:09 -0700 Subject: [PATCH 378/499] better data source test coverage + misc bugfixes (#144) --- .../StreamingDataSourceBuilder.cs | 11 +- .../Internal/DataSources/PollingDataSource.cs | 6 +- .../DataSources/StreamingDataSource.cs | 21 +- .../LaunchDarkly.ClientSdk.csproj | 2 +- .../PlatformSpecific/Http.ios.cs | 10 +- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 3 +- .../MainActivity.cs | 7 +- .../XunitConsoleLoggingResultChannel.cs | 101 ++++ .../AssertHelpers.cs | 43 +- .../DataSources/FeatureFlagRequestorTests.cs | 5 +- .../DataSources/PollingDataSourceTest.cs | 282 ++++++++-- .../DataSources/StreamingDataSourceTest.cs | 484 ++++++++++-------- .../LaunchDarkly.ClientSdk.Tests.csproj | 2 +- .../MockComponents.cs | 10 +- .../MockResponses.cs | 34 ++ .../ModelBuilders.cs | 6 + .../TestHttpUtils.cs | 88 ++++ .../AppDelegate.cs | 7 +- .../LaunchDarkly.ClientSdk.iOS.Tests.csproj | 2 +- .../LaunchDarkly.ClientSdk.iOS.Tests/Main.cs | 2 +- .../XunitConsoleLoggingResultChannel.cs | 101 ++++ 21 files changed, 946 insertions(+), 281 deletions(-) create mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs create mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index f89b40ca..2cefb668 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -40,8 +40,6 @@ public sealed class StreamingDataSourceBuilder : IDataSourceFactory, IDiagnostic internal TimeSpan _backgroundPollInterval = Configuration.DefaultBackgroundPollInterval; internal TimeSpan _initialReconnectDelay = DefaultInitialReconnectDelay; - internal StreamingDataSource.EventSourceCreator _eventSourceCreator = null; // used only in testing - /// /// Sets the interval between feature flag updates when the application is running in the background. /// @@ -86,12 +84,6 @@ public StreamingDataSourceBuilder InitialReconnectDelay(TimeSpan initialReconnec return this; } - internal StreamingDataSourceBuilder EventSourceCreator(StreamingDataSource.EventSourceCreator fn) - { - _eventSourceCreator = fn; - return this; - } - /// public IDataSource CreateDataSource( LdClientContext context, @@ -139,8 +131,7 @@ bool inBackground requestor, context.Http, logger, - context.DiagnosticStore, - _eventSourceCreator + context.DiagnosticStore ); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index 9c184ead..21fe1ba1 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -112,7 +112,11 @@ private async Task UpdateTaskAsync() } catch (Exception ex) { - LogHelpers.LogException(_log, "Error updating features", PlatformSpecific.Http.TranslateHttpException(ex)); + Exception realEx = (ex is AggregateException ae) ? ae.Flatten() : ex; + _log.Warn("Polling for feature flag updates failed: {0}", LogValues.ExceptionSummary(realEx)); + _log.Debug(LogValues.ExceptionTrace(realEx)); + _updateSink.UpdateStatus(DataSourceState.Interrupted, + DataSourceStatus.ErrorInfo.FromException(realEx)); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index 3f47c558..0971edd7 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -6,7 +6,6 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Concurrent; @@ -35,7 +34,6 @@ internal sealed class StreamingDataSource : IDataSource private readonly IFeatureFlagRequestor _requestor; private readonly HttpProperties _httpProperties; private readonly IDiagnosticStore _diagnosticStore; - private readonly EventSourceCreator _eventSourceCreator; private readonly TaskCompletionSource _initTask; private readonly AtomicBoolean _initialized = new AtomicBoolean(false); private readonly Logger _log; @@ -44,13 +42,6 @@ internal sealed class StreamingDataSource : IDataSource internal DateTime _esStarted; // exposed for testing - internal delegate IEventSource EventSourceCreator( - HttpProperties httpProperties, - HttpMethod method, - Uri uri, - string jsonBody - ); - internal StreamingDataSource( IDataSourceUpdateSink updateSink, User user, @@ -60,8 +51,7 @@ internal StreamingDataSource( IFeatureFlagRequestor requestor, HttpConfiguration httpConfig, Logger log, - IDiagnosticStore diagnosticStore, - EventSourceCreator eventSourceCreator // used only in tests + IDiagnosticStore diagnosticStore ) { this._updateSink = updateSink; @@ -75,7 +65,6 @@ EventSourceCreator eventSourceCreator // used only in tests this._diagnosticStore = diagnosticStore; this._initTask = new TaskCompletionSource(); this._log = log; - this._eventSourceCreator = eventSourceCreator ?? CreateEventSource; } public bool Initialized => _initialized.Get(); @@ -84,7 +73,7 @@ public Task Start() { if (_useReport) { - _eventSource = _eventSourceCreator( + _eventSource = CreateEventSource( _httpProperties, ReportMethod, MakeRequestUriWithPath(StandardEndpoints.StreamingReportRequestPath), @@ -93,7 +82,7 @@ public Task Start() } else { - _eventSource = _eventSourceCreator( + _eventSource = CreateEventSource( _httpProperties, HttpMethod.Get, MakeRequestUriWithPath(StandardEndpoints.StreamingGetRequestPath( @@ -127,6 +116,10 @@ string jsonBody .ReadTimeout(LaunchDarklyStreamReadTimeout) .RequestHeaders(httpProperties.BaseHeaders.ToDictionary(kv => kv.Key, kv => kv.Value)) .Logger(_log); + if (jsonBody != null) + { + configBuilder.RequestBody(jsonBody, "application/json"); + } return new EventSource.EventSource(configBuilder.Build()); } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ef94d434..e2349c8d 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs index 91bdf861..32deb955 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs @@ -10,13 +10,21 @@ internal static partial class Http // NSUrlSessionHandler is the preferred native implementation of HttpMessageHandler on iOS. // However, it does not support programmatically setting a proxy, so if a proxy was specified // we must fall back to the non-native .NET implementation. + // + // Note that we set DisableCaching to true because we do not want iOS to handle HTTP caching + // for us: in the one area where we want to use it (polling), we keep track of the Etag + // ourselves, and if the server returns a 304 status we want to be able to see that and know + // that we don't have to update anything. If iOS did the caching for us, a 304 would be + // transparently changed to a 200 response with the cached data, and we would end up + // pointlessly re-parsing and reapplying the response. + private static Func PlatformGetHttpMessageHandlerFactory( TimeSpan connectTimeout, TimeSpan readTimeout, IWebProxy proxy ) => (proxy is null) - ? (p => (HttpMessageHandler)new NSUrlSessionHandler()) + ? (p => (HttpMessageHandler)new NSUrlSessionHandler() { DisableCaching = true }) : (Func)null; // NSUrlSessionHandler doesn't appear to throw platform-specific exceptions that we care about diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index 795c7556..a81c5e50 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -67,7 +67,7 @@ - + @@ -94,6 +94,7 @@ they're in the project directory. --> + diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs index ba672392..81c8db69 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs @@ -1,7 +1,7 @@ using System.Reflection; - using Android.App; using Android.OS; +using LaunchDarkly.Sdk.Client.Tests; using Xunit.Runners.UI; using Xunit.Sdk; @@ -11,6 +11,11 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests [Activity(Label = "LaunchDarkly.Sdk.Client.Android.Tests", MainLauncher = true)] public class MainActivity : RunnerActivity { + public MainActivity() + { + ResultChannel = new XunitConsoleLoggingResultChannel(); + } + protected override void OnCreate(Bundle bundle) { AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs new file mode 100644 index 00000000..6f57d93b --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit.Runners; + +// This class is used in both iOS and Android test projects. It is not applicable in .NET Standard. +// +// It is based on the TextWriterResultChannel provided by xunit.runner.devices, but it has better +// diagnostic output for our purposes: captured test output is included for failed tests, and any +// multi-line error messages are logged as individual lines so that our log-parsing logic won't be +// confused by lines with no log prefix. + +namespace LaunchDarkly.Sdk.Client.Tests +{ + public class XunitConsoleLoggingResultChannel : IResultChannel + { + private readonly TextWriter _writer; + private readonly object _lock = new object(); + + private int _passed, _skipped, _failed; + + public XunitConsoleLoggingResultChannel() + { + _writer = Console.Out; + } + + public Task OpenChannel(string message = null) + { + lock (_lock) + { + _failed = _passed = _skipped = 0; + _writer.WriteLine("[Runner executing:\t{0}]", message); + return Task.FromResult(true); + } + } + + public Task CloseChannel() + { + lock (_lock) + { + var total = _passed + _failed; + _writer.WriteLine("Tests run: {0} Passed: {1} Failed: {2} Skipped: {3}", total, _passed, _failed, _skipped); + return Task.FromResult(true); + } + } + + public void RecordResult(TestResultViewModel result) + { + lock (_lock) + { + switch (result.TestCase.Result) + { + case TestState.Passed: + _writer.Write("\t[PASS] "); + _passed++; + break; + case TestState.Skipped: + _writer.Write("\t[SKIPPED] "); + _skipped++; + break; + case TestState.Failed: + _writer.Write("\t[FAIL] "); + _failed++; + break; + default: + _writer.Write("\t[INFO] "); + break; + } + _writer.Write(result.TestCase.DisplayName); + + var message = result.ErrorMessage; + if (!string.IsNullOrEmpty(message)) + { + _writer.Write(" : {0}", message.Replace("\r\n", "\\r\\n")); + } + _writer.WriteLine(); + + var stacktrace = result.ErrorStackTrace; + if (!string.IsNullOrEmpty(result.ErrorStackTrace)) + { + WriteMultiLine(result.ErrorStackTrace, "\t\t"); + } + } + + if (result.HasOutput && result.TestCase.Result != TestState.Passed) + { + _writer.WriteLine(">>> test output follows:"); + WriteMultiLine(result.Output, ""); + } + } + + private void WriteMultiLine(string text, string prefix) + { + var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + _writer.WriteLine(prefix + line); + } + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs index 06233d80..93ba998d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -1,4 +1,7 @@ -using Xunit; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Json; +using Xunit; +using Xunit.Sdk; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; using static LaunchDarkly.TestHelpers.JsonAssertions; @@ -10,6 +13,17 @@ public class AssertHelpers public static void DataSetsEqual(FullDataSet expected, FullDataSet actual) => AssertJsonEqual(expected.ToJsonString(), actual.ToJsonString()); + public static void DataItemsEqual(ItemDescriptor expected, ItemDescriptor actual) + { + AssertJsonEqual(expected.Item is null ? null : expected.Item.ToJsonString(), + actual.Item is null ? null : actual.Item.ToJsonString()); + Assert.Equal(expected.Version, actual.Version); + } + + public static void UsersEqual(User expected, User actual) => + AssertJsonEqual(LdJsonSerialization.SerializeObject(expected), + LdJsonSerialization.SerializeObject(actual)); + public static void UsersEqualExcludingAutoProperties(User expected, User actual) { var builder = User.Builder(expected); @@ -20,7 +34,32 @@ public static void UsersEqualExcludingAutoProperties(User expected, User actual) builder.Custom(autoProp, actual.GetAttribute(UserAttribute.ForName(autoProp))); } } - Assert.Equal(builder.Build(), actual); + UsersEqual(builder.Build(), actual); } + + public static void LogMessageRegex(LogCapture logCapture, bool shouldHave, LogLevel level, string pattern) + { + if (logCapture.HasMessageWithRegex(level, pattern) != shouldHave) + { + ThrowLogMatchException(logCapture, shouldHave, level, pattern, true); + } + } + + public static void LogMessageText(LogCapture logCapture, bool shouldHave, LogLevel level, string text) + { + if (logCapture.HasMessageWithText(level, text) != shouldHave) + { + ThrowLogMatchException(logCapture, shouldHave, level, text, true); + } + } + + private static void ThrowLogMatchException(LogCapture logCapture, bool shouldHave, LogLevel level, string text, bool isRegex) => + throw new AssertActualExpectedException(shouldHave, !shouldHave, + string.Format("Expected log {0} the {1} \"{2}\" at level {3}\n\nActual log output follows:\n{4}", + shouldHave ? "to have" : "not to have", + isRegex ? "pattern" : "exact message", + text, + level, + logCapture.ToString())); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index af116fe7..82fe09fd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -10,7 +10,10 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources { - // End-to-end tests of this component against an embedded HTTP server. + // End-to-end tests of this component against an embedded HTTP server. This is covered + // in more detail by PollingDataSourceTest, but FeatureFlagRequestor can also be used from + // StreamingDataSource. + public class FeatureFlagRequestorTests : BaseTest { public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput) { } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 95f4a68b..1233a0e4 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -1,57 +1,277 @@ using System; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Internal.Concurrent; +using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; +using static LaunchDarkly.Sdk.Client.DataModel; +using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.MockResponses; +using static LaunchDarkly.Sdk.Client.TestHttpUtils; + namespace LaunchDarkly.Sdk.Client.Internal.DataSources { public class PollingDataSourceTest : BaseTest { - private const string flagsJson = "{" + - "\"int-flag\":{\"value\":15}," + - "\"float-flag\":{\"value\":13.5}," + - "\"string-flag\":{\"value\":\"markw@magenic.com\"}" + - "}"; + private static readonly FeatureFlag Flag = new FeatureFlagBuilder() + .Version(2).Value(true).Variation(1).Build(); + private static FullDataSet AllData => + new DataSetBuilder().Add("flag1", Flag).Build(); + private static readonly TimeSpan BriefInterval = TimeSpan.FromMilliseconds(20); + private static readonly User simpleUser = User.WithKey("me"); + private const string encodedSimpleUser = "eyJrZXkiOiJtZSJ9"; - private FlagDataManager _store; - User user; + private readonly MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); - public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) + private IDataSource MakeDataSource(Uri baseUri, User user, Action modConfig = null) { - _store = new FlagDataManager(BasicMobileKey, null, testLogger); + var builder = BasicConfig() + .DataSource(Components.PollingDataSource()) + .ServiceEndpoints(Components.ServiceEndpoints().Polling(baseUri)); + modConfig?.Invoke(builder); + var config = builder.Build(); + return config.DataSourceFactory.CreateDataSource(new LdClientContext(config), _updateSink, + user, false); } - IDataSource MakeDataSource() + public PollingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } + + [Theory] + [InlineData("", false, "/msdk/evalx/users/", "")] + [InlineData("", true, "/msdk/evalx/users/", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/msdk/evalx/users/", "")] + [InlineData("/basepath", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/msdk/evalx/users/", "")] + [InlineData("/basepath/", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] + public void PollingRequestHasCorrectUri( + string baseUriExtraPath, + bool withReasons, + string expectedPathWithoutUser, + string expectedQuery + ) { - var mockFeatureFlagRequestor = new MockFeatureFlagRequestor(flagsJson); - user = User.WithKey("user1Key"); - return new PollingDataSource( - new DataSourceUpdateSinkImpl(_store, false, BasicTaskExecutor, testLogger), - user, - mockFeatureFlagRequestor, - TimeSpan.FromSeconds(30), - TimeSpan.Zero, - BasicTaskExecutor, - testLogger - ); + using (var server = HttpServer.Start(PollingResponse(AllData))) + { + var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); + using (var dataSource = MakeDataSource(baseUri, simpleUser, + c => c.EvaluationReasons(withReasons))) + { + var task = dataSource.Start(); + + var request = server.Recorder.RequireRequest(); + Assert.Equal("GET", request.Method); + Assert.Equal(expectedPathWithoutUser + encodedSimpleUser, request.Path); + Assert.Equal(expectedQuery, request.Query); + } + } + } + + [Fact] + public void SuccessfulRequestCausesDataToBeStoredAndDataSourceInitialized() + { + using (var server = HttpServer.Start(PollingResponse(AllData))) + { + using (var dataSource = MakeDataSource(server.Uri, BasicUser)) + { + var initTask = dataSource.Start(); + + var receivedData = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(AllData, receivedData); + + Assert.True(AsyncUtils.WaitSafely(() => initTask, TimeSpan.FromSeconds(1))); + Assert.False(initTask.IsFaulted); + Assert.True(dataSource.Initialized); + } + } + } + + [Theory] + [InlineData(401)] + [InlineData(403)] + public void VerifyUnrecoverableHttpError(int errorStatus) + { + var errorCondition = ServerErrorCondition.FromStatus(errorStatus); + + WithServerErrorCondition(errorCondition, null, (uri, httpConfig, recorder) => + { + using (var dataSource = MakeDataSource(uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollInterval(BriefInterval)) + .Http(httpConfig))) + { + var initTask = dataSource.Start(); + bool completed = initTask.Wait(TimeSpan.FromSeconds(1)); + Assert.True(completed); + Assert.False(dataSource.Initialized); + + var status = _updateSink.ExpectStatusUpdate(); + errorCondition.VerifyDataSourceStatusError(status); + + recorder.RequireRequest(); + recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100)); // did not retry + + errorCondition.VerifyLogMessage(logCapture); + } + }); + } + + [Theory] + [InlineData(408)] + [InlineData(429)] + [InlineData(500)] + [InlineData(ServerErrorCondition.FakeIOException)] + public void VerifyRecoverableError(int errorStatus) + { + var errorCondition = ServerErrorCondition.FromStatus(errorStatus); + var successResponse = PollingResponse(AllData); + + // Verify that it does not immediately retry the failed request + + WithServerErrorCondition(errorCondition, successResponse, (uri, httpConfig, recorder) => + { + using (var dataSource = MakeDataSource(uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollInterval(TimeSpan.FromHours(1))) + .Http(httpConfig))) + { + dataSource.Start(); + + var status = _updateSink.ExpectStatusUpdate(); + errorCondition.VerifyDataSourceStatusError(status); + + recorder.RequireRequest(); + recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100)); + + errorCondition.VerifyLogMessage(logCapture); + } + }); + + // Verify (with a small polling interval) that it does do another request at the next interval + + WithServerErrorCondition(errorCondition, successResponse, (uri, httpConfig, recorder) => + { + using (var dataSource = MakeDataSource(uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollIntervalNoMinimum(BriefInterval)) + .Http(httpConfig))) + { + var initTask = dataSource.Start(); + bool completed = initTask.Wait(TimeSpan.FromSeconds(1)); + Assert.True(completed); + Assert.True(dataSource.Initialized); + + var status = _updateSink.ExpectStatusUpdate(); + errorCondition.VerifyDataSourceStatusError(status); + + // We don't check here for a second status update to the Valid state, because that was + // done by DataSourceUpdatesImpl when Init was called - our test fixture doesn't do it. + + recorder.RequireRequest(); + recorder.RequireRequest(); + + errorCondition.VerifyLogMessage(logCapture); + } + }); + } + + [Fact] + public void EtagIsStoredAndSentWithNextRequest() + { + var etag = @"""abc123"""; // note that etag strings must be quoted + var resp = Handlers.Header("Etag", etag).Then(PollingResponse(AllData)); + + using (var server = HttpServer.Start(resp)) + { + using (var dataSource = MakeDataSource(server.Uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollIntervalNoMinimum(BriefInterval)))) + { + dataSource.Start(); + + var req1 = server.Recorder.RequireRequest(); + var req2 = server.Recorder.RequireRequest(); + Assert.Null(req1.Headers.Get("If-None-Match")); + Assert.Equal(etag, req2.Headers.Get("If-None-Match")); + } + } } [Fact] - public void CanCreatePollingDataSource() + public void InitIsNotRepeatedIfServerReturnsNotModifiedStatus() { - Assert.NotNull(MakeDataSource()); + var etag = @"""abc123"""; // note that etag strings must be quoted + var responses = Handlers.SequentialWithLastRepeating( + Handlers.Header("Etag", etag).Then(PollingResponse(AllData)), + Handlers.Status(304) + ); + + using (var server = HttpServer.Start(responses)) + { + using (var dataSource = MakeDataSource(server.Uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollIntervalNoMinimum(BriefInterval)))) + { + dataSource.Start(); + + var receivedData = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(AllData, receivedData); + + // We've set it up above so that all requests except the first one return a 304 + // status, so the data source should *not* push a new data set with Init. + _updateSink.ExpectNoMoreActions(); + + var req1 = server.Recorder.RequireRequest(); + var req2 = server.Recorder.RequireRequest(); + var req3 = server.Recorder.RequireRequest(); + Assert.Null(req1.Headers.Get("If-None-Match")); + Assert.Equal(etag, req2.Headers.Get("If-None-Match")); + Assert.Equal(etag, req3.Headers.Get("If-None-Match")); + } + } } [Fact] - public void StartWaitsUntilFlagCacheFilled() + public void ResponseWithNewEtagUpdatesEtag() { - var dataSource = MakeDataSource(); - var initTask = dataSource.Start(); - var unused = initTask.Wait(TimeSpan.FromSeconds(1)); - var flags = _store.GetAll(); - Assert.Equal(3, flags.Value.Items.Count); + var etag1 = @"""abc123"""; // note that etag strings must be quoted + var etag2 = @"""def456"""; + var data1 = AllData; + var data2 = new DataSetBuilder().Add("flag2", new FeatureFlagBuilder().Build()).Build(); + var data3 = new DataSetBuilder().Add("flag3", new FeatureFlagBuilder().Build()).Build(); + var responses = Handlers.SequentialWithLastRepeating( + Handlers.Header("Etag", etag1).Then(PollingResponse(data1)), + Handlers.Status(304), + Handlers.Header("Etag", etag2).Then(PollingResponse(data2)), + Handlers.Status(304), + PollingResponse(data3) // no etag - even though the server will normally send one + ); + + using (var server = HttpServer.Start(responses)) + { + using (var dataSource = MakeDataSource(server.Uri, BasicUser, + c => c.DataSource(Components.PollingDataSource().PollIntervalNoMinimum(BriefInterval)))) + { + dataSource.Start(); + + var receivedData1 = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(data1, receivedData1); + + var receivedData2 = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(data2, receivedData2); + + var receivedData3 = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(data3, receivedData3); + + var req1 = server.Recorder.RequireRequest(); + var req2 = server.Recorder.RequireRequest(); + var req3 = server.Recorder.RequireRequest(); + var req4 = server.Recorder.RequireRequest(); + var req5 = server.Recorder.RequireRequest(); + var req6 = server.Recorder.RequireRequest(); + Assert.Null(req1.Headers.Get("If-None-Match")); + Assert.Equal(etag1, req2.Headers.Get("If-None-Match")); + Assert.Equal(etag1, req3.Headers.Get("If-None-Match")); + Assert.Equal(etag2, req4.Headers.Get("If-None-Match")); // etag was updated by 3rd response + Assert.Equal(etag2, req5.Headers.Get("If-None-Match")); // etag was updated by 3rd response + Assert.Null(req6.Headers.Get("If-None-Match")); // etag was cleared by 5th response + } + } } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index c8c75179..0230c23d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -1,64 +1,63 @@ using System; using System.Linq; -using System.Net.Http; using System.Threading.Tasks; -using LaunchDarkly.EventSource; -using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Events; -using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Json; +using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.TestUtil; +using static LaunchDarkly.Sdk.Client.MockResponses; +using static LaunchDarkly.Sdk.Client.TestHttpUtils; using static LaunchDarkly.TestHelpers.JsonAssertions; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { public class StreamingDataSourceTest : BaseTest - { - private const string initialFlagsJson = "{" + - "\"int-flag\":{\"value\":15,\"version\":100}," + - "\"float-flag\":{\"value\":13.5,\"version\":100}," + - "\"string-flag\":{\"value\":\"markw@magenic.com\",\"version\":100}" + - "}"; + { + private static readonly TimeSpan BriefReconnectDelay = TimeSpan.FromMilliseconds(10); - private readonly User user = User.WithKey("me"); - private const string encodedUser = "eyJrZXkiOiJtZSJ9"; + private readonly User simpleUser = User.WithKey("me"); + private const string encodedSimpleUser = "eyJrZXkiOiJtZSJ9"; - private EventSourceMock mockEventSource; - private TestEventSourceFactory eventSourceFactory; private MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); - private IFeatureFlagRequestor mockRequestor; - private Uri baseUri; - private TimeSpan initialReconnectDelay = StreamingDataSourceBuilder.DefaultInitialReconnectDelay; - private bool withReasons = false; - - public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) - { - mockEventSource = new EventSourceMock(); - eventSourceFactory = new TestEventSourceFactory(mockEventSource); - mockRequestor = new MockFeatureFlagRequestor(initialFlagsJson); - baseUri = new Uri("http://example"); - } - - private StreamingDataSource MakeStartedStreamingDataSource(IDiagnosticStore diagnosticStore = null) - { - var dataSource = new StreamingDataSource( - _updateSink, - user, - baseUri, - withReasons, - initialReconnectDelay, - mockRequestor, - TestUtil.SimpleContext.Http, - testLogger, - diagnosticStore, - eventSourceFactory.Create() - ); - dataSource.Start(); - return dataSource; - } + + public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } + + private IDataSource MakeDataSource(Uri baseUri, User user, Action modConfig = null) + { + var builder = BasicConfig() + .DataSource(Components.StreamingDataSource().InitialReconnectDelay(BriefReconnectDelay)) + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(baseUri).Polling(baseUri)); + modConfig?.Invoke(builder); + var config = builder.Build(); + return config.DataSourceFactory.CreateDataSource(new LdClientContext(config), _updateSink, + user, false); + } + + private IDataSource MakeDataSourceWithDiagnostics(Uri baseUri, User user, IDiagnosticStore diagnosticStore) + { + var config = BasicConfig() + .ServiceEndpoints(Components.ServiceEndpoints().Streaming(baseUri).Polling(baseUri)) + .Build(); + var context = new LdClientContext(config, null, diagnosticStore, null); + return Components.StreamingDataSource().InitialReconnectDelay(BriefReconnectDelay) + .CreateDataSource(context, _updateSink, user, false); + } + + private void WithDataSourceAndServer(Handler responseHandler, Action action) + { + using (var server = HttpServer.Start(AllowOnlyStreamRequests(responseHandler))) + { + using (var dataSource = MakeDataSource(server.Uri, BasicUser)) + { + var initTask = dataSource.Start(); + action(dataSource, server, initTask); + } + } + } [Theory] [InlineData("", false, "/meval/", "")] @@ -74,14 +73,19 @@ public void RequestHasCorrectUriAndMethodInGetMode( string expectedQuery ) { - var fakeRootUri = "http://fake-stream-host"; - var fakeBaseUri = fakeRootUri + baseUriExtraPath; - this.baseUri = new Uri(fakeBaseUri); - this.withReasons = withReasons; - MakeStartedStreamingDataSource(); - Assert.Equal(HttpMethod.Get, eventSourceFactory.ReceivedMethod); - Assert.Equal(new Uri(fakeRootUri + expectedPathWithoutUser + encodedUser + expectedQuery), - eventSourceFactory.ReceivedUri); + using (var server = HttpServer.Start(StreamWithEmptyData)) + { + var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); + using (var dataSource = MakeDataSource(baseUri, simpleUser, + c => c.EvaluationReasons(withReasons))) + { + dataSource.Start(); + var req = server.Recorder.RequireRequest(); + Assert.Equal(expectedPathWithoutUser + encodedSimpleUser, req.Path); + Assert.Equal(expectedQuery, req.Query); + Assert.Equal("GET", req.Method); + } + } } // REPORT mode is known to fail in Android (ch47341) @@ -100,195 +104,249 @@ public void RequestHasCorrectUriAndMethodAndBodyInReportMode( string expectedQuery ) { - var fakeRootUri = "http://fake-stream-host"; - var fakeBaseUri = fakeRootUri + baseUriExtraPath; - this.baseUri = new Uri(fakeBaseUri); - var httpConfig = Components.HttpConfiguration().UseReport(true).CreateHttpConfiguration(SimpleContext.Basic); - using (var dataSource = new StreamingDataSource( - _updateSink, - user, - baseUri, - withReasons, - initialReconnectDelay, - mockRequestor, - httpConfig, - testLogger, - null, - eventSourceFactory.Create() - )) + using (var server = HttpServer.Start(StreamWithEmptyData)) { - dataSource.Start(); - - Assert.Equal(new HttpMethod("REPORT"), eventSourceFactory.ReceivedMethod); - Assert.Equal(new Uri(fakeRootUri + expectedPath + expectedQuery), - eventSourceFactory.ReceivedUri); - Assert.NotNull(eventSourceFactory.ReceivedBody); - AssertJsonEqual(LdJsonSerialization.SerializeObject(user), - NormalizeJsonUser(LdValue.Parse(eventSourceFactory.ReceivedBody))); - } + var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); + using (var dataSource = MakeDataSource(baseUri, simpleUser, + c => c.EvaluationReasons(withReasons) + .Http(Components.HttpConfiguration().UseReport(true)))) + { + dataSource.Start(); + var req = server.Recorder.RequireRequest(); + Assert.Equal(expectedPath, req.Path); + Assert.Equal(expectedQuery, req.Query); + Assert.Equal("REPORT", req.Method); + AssertJsonEqual(LdJsonSerialization.SerializeObject(simpleUser), req.Body); + } + } } #endif [Fact] - public void PutStoresFeatureFlags() - { - MakeStartedStreamingDataSource(); - // should be empty before PUT message arrives - _updateSink.Actions.ExpectNoValue(); - - PUTMessageSentToProcessor(); - - var gotData = _updateSink.ExpectInit(user); - var gotItem = gotData.Items.First(item => item.Key == "int-flag"); - int intFlagValue = gotItem.Value.Item.Value.AsInt; - Assert.Equal(15, intFlagValue); - } - - [Fact] - public void PatchUpdatesFeatureFlag() - { - // before PATCH, fill in flags - MakeStartedStreamingDataSource(); - PUTMessageSentToProcessor(); - _updateSink.ExpectInit(user); - - //PATCH to update 1 flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("patch", UpdatedFlag(), null)); - mockEventSource.RaiseMessageRcvd(eventArgs); - - //verify flag has changed - var gotItem = _updateSink.ExpectUpsert(user, "int-flag"); - Assert.Equal(99, gotItem.Item.Value.AsInt); - } - - [Fact] - public void DeleteRemovesFeatureFlag() + public void PutCausesDataToBeStoredAndDataSourceInitialized() { - // before DELETE, fill in flags, test it's there - MakeStartedStreamingDataSource(); - PUTMessageSentToProcessor(); - _updateSink.ExpectInit(user); - - // DELETE int-flag - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("delete", DeleteFlag(), null)); - mockEventSource.RaiseMessageRcvd(eventArgs); + var data = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Build(); - // verify flag was deleted - var gotItem = _updateSink.ExpectUpsert(user, "int-flag"); - Assert.Null(gotItem.Item); - } + WithDataSourceAndServer(StreamWithInitialData(data), (dataSource, _, initTask) => + { + var receivedData = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(data, receivedData); + + Assert.True(AsyncUtils.WaitSafely(() => initTask, TimeSpan.FromSeconds(1))); + Assert.False(initTask.IsFaulted); + Assert.True(dataSource.Initialized); + }); + } + + [Fact] + public void DataSourceIsNotInitializedByDefault() + { + WithDataSourceAndServer(StreamThatStaysOpenWithNoEvents, (dataSource, _, initTask) => + { + Assert.False(dataSource.Initialized); + Assert.False(initTask.IsCompleted); + }); + } + + [Fact] + public void PatchUpdatesFlag() + { + var flag = new FeatureFlagBuilder().Version(1).Build(); + var patchEvent = PatchEvent(flag.ToJsonString("flag1")); + + WithDataSourceAndServer(StreamWithEmptyInitialDataAndThen(patchEvent), (dataSource, s, t) => + { + _updateSink.ExpectInit(BasicUser); + + var receivedItem = _updateSink.ExpectUpsert(BasicUser, "flag1"); + AssertHelpers.DataItemsEqual(flag.ToItemDescriptor(), receivedItem); + }); + } + + [Fact] + public void DeleteDeletesFlag() + { + var deleteEvent = DeleteEvent("flag1", 2); + + WithDataSourceAndServer(StreamWithEmptyInitialDataAndThen(deleteEvent), (dataSource, s, t) => + { + _updateSink.ExpectInit(BasicUser); + + var receivedItem = _updateSink.ExpectUpsert(BasicUser, "flag1"); + Assert.Null(receivedItem.Item); + Assert.Equal(2, receivedItem.Version); + }); + } [Fact] public void PingCausesPoll() { - MakeStartedStreamingDataSource(); - mockEventSource.RaiseMessageRcvd(new MessageReceivedEventArgs(new MessageEvent("ping", null, null))); + var data = new DataSetBuilder() + .Add("flag1", 1, LdValue.Of(true), 0) + .Build(); + var streamWithPing = Handlers.SSE.Start() + .Then(PingEvent) + .Then(Handlers.SSE.LeaveOpen()); + + using (var pollingServer = HttpServer.Start(PollingResponse(data))) + { + using (var streamingServer = HttpServer.Start(streamWithPing)) + { + using (var dataSource = MakeDataSource(streamingServer.Uri, BasicUser, + c => c.ServiceEndpoints(Components.ServiceEndpoints() + .Streaming(streamingServer.Uri).Polling(pollingServer.Uri)))) + { + var initTask = dataSource.Start(); + + pollingServer.Recorder.RequireRequest(); + + var receivedData = _updateSink.ExpectInit(BasicUser); + AssertHelpers.DataSetsEqual(data, receivedData); - var gotData = _updateSink.ExpectInit(user); - var gotItem = gotData.Items.First(item => item.Key == "int-flag"); - int intFlagValue = gotItem.Value.Item.Value.AsInt; - Assert.Equal(15, intFlagValue); + Assert.True(AsyncUtils.WaitSafely(() => initTask, TimeSpan.FromSeconds(1))); + Assert.False(initTask.IsFaulted); + Assert.True(dataSource.Initialized); + } + } + } } + [Theory] + [InlineData(408)] + [InlineData(429)] + [InlineData(500)] + [InlineData(503)] + [InlineData(ServerErrorCondition.FakeIOException)] + public void VerifyRecoverableHttpError(int errorStatus) + { + var errorCondition = ServerErrorCondition.FromStatus(errorStatus); + + WithServerErrorCondition(errorCondition, StreamWithEmptyData, (uri, httpConfig, recorder) => + { + using (var dataSource = MakeDataSource(uri, BasicUser, + c => c.DataSource(Components.StreamingDataSource().InitialReconnectDelay(TimeSpan.Zero)) + .Http(httpConfig))) + { + var initTask = dataSource.Start(); + + var status = _updateSink.ExpectStatusUpdate(); + errorCondition.VerifyDataSourceStatusError(status); + + // We don't check here for a second status update to the Valid state, because that was + // done by DataSourceUpdatesImpl when Init was called - our test fixture doesn't do it. + + _updateSink.ExpectInit(BasicUser); + + recorder.RequireRequest(); + recorder.RequireRequest(); + + Assert.True(AsyncUtils.WaitSafely(() => initTask, TimeSpan.FromSeconds(1))); + + errorCondition.VerifyLogMessage(logCapture); + } + }); + } + + [Theory] + [InlineData(401)] + [InlineData(403)] + public void VerifyUnrecoverableHttpError(int errorStatus) + { + var errorCondition = ServerErrorCondition.FromStatus(errorStatus); + + WithServerErrorCondition(errorCondition, StreamWithEmptyData, (uri, httpConfig, recorder) => + { + using (var dataSource = MakeDataSource(uri, BasicUser, + c => c.DataSource(Components.StreamingDataSource().InitialReconnectDelay(TimeSpan.Zero)) + .Http(httpConfig))) + { + var initTask = dataSource.Start(); + var status = _updateSink.ExpectStatusUpdate(); + errorCondition.VerifyDataSourceStatusError(status); + + _updateSink.ExpectNoMoreActions(); + + recorder.RequireRequest(); + recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100)); + + Assert.True(AsyncUtils.WaitSafely(() => initTask, TimeSpan.FromSeconds(1))); + + errorCondition.VerifyLogMessage(logCapture); + } + }); + } + [Fact] - public void StreamInitDiagnosticRecordedOnOpen() + public async void StreamInitDiagnosticRecordedOnOpen() { var mockDiagnosticStore = new MockDiagnosticStore(); - using (var sp = MakeStartedStreamingDataSource(mockDiagnosticStore)) + + using (var server = HttpServer.Start(StreamWithEmptyData)) { - mockEventSource.RaiseOpened(new StateChangedEventArgs(ReadyState.Open)); + using (var dataSource = MakeDataSourceWithDiagnostics(server.Uri, BasicUser, mockDiagnosticStore)) + { + await dataSource.Start(); - var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); - Assert.False(streamInit.Failed); + var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); + Assert.False(streamInit.Failed); + } } } [Fact] - public void StreamInitDiagnosticRecordedOnError() + public async void StreamInitDiagnosticRecordedOnError() { var mockDiagnosticStore = new MockDiagnosticStore(); - using (var sp = MakeStartedStreamingDataSource(mockDiagnosticStore)) + + using (var server = HttpServer.Start(Error401Response)) { - mockEventSource.RaiseError(new ExceptionEventArgs(new EventSourceServiceUnsuccessfulResponseException(400))); + using (var dataSource = MakeDataSourceWithDiagnostics(server.Uri, BasicUser, mockDiagnosticStore)) + { + await dataSource.Start(); - var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); - Assert.True(streamInit.Failed); + var streamInit = mockDiagnosticStore.StreamInits.ExpectValue(); + Assert.True(streamInit.Failed); + } } } - - string UpdatedFlag() - { - var updatedFlagAsJson = "{\"key\":\"int-flag\",\"version\":999,\"flagVersion\":192,\"value\":99,\"variation\":0,\"trackEvents\":false}"; - return updatedFlagAsJson; - } - - string DeleteFlag() - { - var flagToDelete = "{\"key\":\"int-flag\",\"version\":1214}"; - return flagToDelete; - } - - void PUTMessageSentToProcessor() - { - MessageReceivedEventArgs eventArgs = new MessageReceivedEventArgs(new MessageEvent("put", initialFlagsJson, null)); - mockEventSource.RaiseMessageRcvd(eventArgs); - } - } - - class TestEventSourceFactory - { - public HttpProperties ReceivedHttpProperties { get; private set; } - public HttpMethod ReceivedMethod { get; private set; } - public Uri ReceivedUri { get; private set; } - public string ReceivedBody { get; private set; } - IEventSource _eventSource; - - public TestEventSourceFactory(IEventSource eventSource) - { - _eventSource = eventSource; - } - - public StreamingDataSource.EventSourceCreator Create() - { - return (httpProperties, method, uri, jsonBody) => - { - ReceivedHttpProperties = httpProperties; - ReceivedMethod = method; - ReceivedUri = uri; - ReceivedBody = jsonBody; - return _eventSource; - }; - } - } - - class EventSourceMock : IEventSource - { - public ReadyState ReadyState => throw new NotImplementedException(); - -#pragma warning disable 0067 // unused properties - public event EventHandler Opened; - public event EventHandler Closed; - public event EventHandler MessageReceived; - public event EventHandler CommentReceived; - public event EventHandler Error; -#pragma warning restore 0067 - - public void Close() - { - - } - - public Task StartAsync() - { - return Task.CompletedTask; + + [Fact] + public void UnknownEventTypeDoesNotCauseError() + { + VerifyEventDoesNotCauseStreamRestart("weird", "data"); + } + + private void VerifyEventDoesNotCauseStreamRestart(string eventName, string eventData) + { + // We'll end another event after that event, so we can see when we've got past the first one + var events = Handlers.SSE.Event(eventName, eventData) + .Then(PatchEvent(new FeatureFlagBuilder().Build().ToJsonString("ignore"))); + + DoTestAfterEmptyPut(events, server => + { + _updateSink.ExpectUpsert(BasicUser, "ignore"); + + server.Recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100)); + + Assert.Empty(logCapture.GetMessages().Where(m => m.Level == Logging.LogLevel.Error)); + }); + } + + private void DoTestAfterEmptyPut(Handler contentHandler, Action action) + { + var useContentForFirstRequestOnly = Handlers.Sequential( + StreamWithEmptyInitialDataAndThen(contentHandler), + StreamThatStaysOpenWithNoEvents + ); + WithDataSourceAndServer(useContentForFirstRequestOnly, (dataSource, server, initTask) => + { + _updateSink.ExpectInit(BasicUser); + server.Recorder.RequireRequest(); + + action(server); + }); } - - public void Restart(bool withDelay) { } - - public void RaiseError(ExceptionEventArgs e) => Error(null, e); - - public void RaiseMessageRcvd(MessageReceivedEventArgs e) => MessageReceived(null, e); - - public void RaiseOpened(StateChangedEventArgs e) => Opened(null, e); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index a0b2b17d..657f6b1c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -5,7 +5,7 @@ LaunchDarkly.Sdk.Client - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 88d6d8cc..6245c3f1 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -129,7 +129,7 @@ public void UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? n public FullDataSet ExpectInit(User user) { var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); - Assert.Equal(user, action.User); + AssertHelpers.UsersEqual(user, action.User); return action.Data; } @@ -140,6 +140,14 @@ public ItemDescriptor ExpectUpsert(User user, string key) Assert.Equal(key, action.Key); return action.Data; } + + public DataSourceStatus ExpectStatusUpdate() + { + var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); + return new DataSourceStatus { State = action.State, LastError = action.Error }; + } + + public void ExpectNoMoreActions() => Actions.ExpectNoValue(); } internal class MockDeviceInfo : IDeviceInfo diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs index 6e31b906..67cc6485 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs @@ -15,15 +15,49 @@ public static class MockResponses public static Handler PollingResponse(FullDataSet? data = null) => Handlers.BodyJson((data ?? DataSetBuilder.Empty).ToJsonString()); + public static Handler StreamWithEmptyData => StreamWithInitialData(null); + public static Handler StreamWithInitialData(FullDataSet? data = null) => Handlers.SSE.Start() .Then(PutEvent(data)) .Then(Handlers.SSE.LeaveOpen()); + public static Handler StreamWithEmptyInitialDataAndThen(params Handler[] handlers) + { + var ret = Handlers.SSE.Start().Then(PutEvent()); + foreach (var h in handlers) + { + if (h != null) + { + ret = ret.Then(h); + } + } + return ret.Then(Handlers.SSE.LeaveOpen()); + } + + public static Handler StreamThatStaysOpenWithNoEvents => + Handlers.SSE.Start().Then(Handlers.SSE.LeaveOpen()); + + public static Handler AllowOnlyStreamRequests(Handler streamHandler) + { + var ret = Handlers.Router(out var router); + router.AddRegex("^/meval/.*", streamHandler); + router.AddRegex(".*", Handlers.Status(500)); + return ret; + } + public static Handler PutEvent(FullDataSet? data = null) => Handlers.SSE.Event( "put", (data ?? DataSetBuilder.Empty).ToJsonString() ); + + public static Handler PatchEvent(string data) => + Handlers.SSE.Event("patch", data); + + public static Handler DeleteEvent(string key, int version) => + Handlers.SSE.Event("delete", @"{""key"":""" + key + @""",""version"":" + version + "}"); + + public static Handler PingEvent => Handlers.SSE.Event("ping", ""); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs index f1e5f0f7..f0923769 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs @@ -118,5 +118,11 @@ public static class DataSetExtensions { public static string ToJsonString(this FullDataSet data) => DataModelSerialization.SerializeAll(data); + + public static string ToJsonString(this FeatureFlag flag) => + DataModelSerialization.SerializeFlag(flag); + + public static string ToJsonString(this FeatureFlag flag, string key) => + LdValue.BuildObject().Copy(LdValue.Parse(flag.ToJsonString())).Set("key", key).Build().ToJsonString(); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs index 7a41a2f5..60f40d5a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestHttpUtils.cs @@ -1,10 +1,13 @@ using System; +using System.IO; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; @@ -12,6 +15,8 @@ namespace LaunchDarkly.Sdk.Client { internal static class TestHttpUtils { + public static readonly Uri FakeUri = new Uri("http://not-real"); + // Used for TestWithSpecialHttpConfigurations internal class MessageHandlerThatAddsPathSuffix : HttpClientHandler { @@ -103,5 +108,88 @@ static void TestHttpClientCanUseProxy(Handler responseHandler, testActionShouldSucceed(fakeBaseUri, httpConfig, server); } } + + public struct ServerErrorCondition + { + public const int FakeIOException = -1; // constant to be used in the constructor + + public int StatusCode { get; set; } + public Exception IOException { get; set; } + + public static ServerErrorCondition FromStatus(int status) + { + return new ServerErrorCondition + { + StatusCode = status, + IOException = status == FakeIOException ? new IOException("deliberate error") : null + }; + } + + public bool Recoverable => IOException != null || HttpErrors.IsRecoverable(StatusCode); + + public Handler Handler => + IOException is null ? Handlers.Status(StatusCode) : Handlers.Error(IOException); + + public void VerifyDataSourceStatusError(DataSourceStatus status) + { + Assert.Equal(Recoverable ? DataSourceState.Interrupted : DataSourceState.Shutdown, status.State); + Assert.NotNull(status.LastError); + Assert.Equal( + IOException is null + ? DataSourceStatus.ErrorKind.ErrorResponse + : DataSourceStatus.ErrorKind.NetworkError, + status.LastError.Value.Kind); + Assert.Equal( + IOException is null ? StatusCode : 0, + status.LastError.Value.StatusCode + ); + if (IOException != null) + { + Assert.Contains(IOException.Message, status.LastError.Value.Message); + } + } + + public void VerifyLogMessage(LogCapture logCapture) + { + var level = Recoverable ? LogLevel.Warn : LogLevel.Error; + var message = (IOException is null) + ? "HTTP error " + StatusCode + ".*" + (Recoverable ? "will retry" : "giving up") + : IOException.Message; + AssertHelpers.LogMessageRegex(logCapture, true, level, message); + } + } + + /// + /// Sets up the HttpTest framework to simulate a server error of some kind. If + /// it is an HTTP error response, we'll use an embedded HttpServer. If it is an + /// I/O error, we have to use a custom message handler instead. + /// + /// + /// if not null, the second request will + /// receive this response instead of the error + /// + public static void WithServerErrorCondition(ServerErrorCondition errorCondition, + Handler successResponseAfterError, + Action action) + { + var responseHandler = successResponseAfterError is null ? errorCondition.Handler : + Handlers.Sequential(errorCondition.Handler, successResponseAfterError); + if (errorCondition.IOException is null) + { + using (var server = HttpServer.Start(responseHandler)) + { + action(server.Uri, Components.HttpConfiguration(), server.Recorder); + } + } + else + { + var handler = Handlers.Record(out var recorder).Then(responseHandler); + action( + FakeUri, + Components.HttpConfiguration().MessageHandler(handler.AsMessageHandler()), + recorder + ); + } + } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs index 96f12936..ac8e1478 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs @@ -1,7 +1,7 @@ using System.Reflection; using Foundation; +using LaunchDarkly.Sdk.Client.Tests; using UIKit; - using Xunit.Runner; using Xunit.Sdk; @@ -15,6 +15,11 @@ namespace LaunchDarkly.Sdk.Client.iOS.Tests [Register("AppDelegate")] public partial class AppDelegate : RunnerAppDelegate { + public AppDelegate() + { + ResultChannel = new XunitConsoleLoggingResultChannel(); + } + public override bool FinishedLaunching(UIApplication app, NSDictionary options) { AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj index 4697ad76..b54c4710 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj @@ -75,7 +75,7 @@ - + diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs index f5652685..963a7b4a 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs @@ -7,7 +7,7 @@ public class Application { static void Main(string[] args) { - UIApplication.Main(args, null, "AppDelegate"); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs new file mode 100644 index 00000000..6f57d93b --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit.Runners; + +// This class is used in both iOS and Android test projects. It is not applicable in .NET Standard. +// +// It is based on the TextWriterResultChannel provided by xunit.runner.devices, but it has better +// diagnostic output for our purposes: captured test output is included for failed tests, and any +// multi-line error messages are logged as individual lines so that our log-parsing logic won't be +// confused by lines with no log prefix. + +namespace LaunchDarkly.Sdk.Client.Tests +{ + public class XunitConsoleLoggingResultChannel : IResultChannel + { + private readonly TextWriter _writer; + private readonly object _lock = new object(); + + private int _passed, _skipped, _failed; + + public XunitConsoleLoggingResultChannel() + { + _writer = Console.Out; + } + + public Task OpenChannel(string message = null) + { + lock (_lock) + { + _failed = _passed = _skipped = 0; + _writer.WriteLine("[Runner executing:\t{0}]", message); + return Task.FromResult(true); + } + } + + public Task CloseChannel() + { + lock (_lock) + { + var total = _passed + _failed; + _writer.WriteLine("Tests run: {0} Passed: {1} Failed: {2} Skipped: {3}", total, _passed, _failed, _skipped); + return Task.FromResult(true); + } + } + + public void RecordResult(TestResultViewModel result) + { + lock (_lock) + { + switch (result.TestCase.Result) + { + case TestState.Passed: + _writer.Write("\t[PASS] "); + _passed++; + break; + case TestState.Skipped: + _writer.Write("\t[SKIPPED] "); + _skipped++; + break; + case TestState.Failed: + _writer.Write("\t[FAIL] "); + _failed++; + break; + default: + _writer.Write("\t[INFO] "); + break; + } + _writer.Write(result.TestCase.DisplayName); + + var message = result.ErrorMessage; + if (!string.IsNullOrEmpty(message)) + { + _writer.Write(" : {0}", message.Replace("\r\n", "\\r\\n")); + } + _writer.WriteLine(); + + var stacktrace = result.ErrorStackTrace; + if (!string.IsNullOrEmpty(result.ErrorStackTrace)) + { + WriteMultiLine(result.ErrorStackTrace, "\t\t"); + } + } + + if (result.HasOutput && result.TestCase.Result != TestState.Passed) + { + _writer.WriteLine(">>> test output follows:"); + WriteMultiLine(result.Output, ""); + } + } + + private void WriteMultiLine(string text, string prefix) + { + var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + _writer.WriteLine(prefix + line); + } + } + } +} From 0ded38e2ed75c5caed7ea92671c363e8bcdbf2a3 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 5 Nov 2021 14:06:39 -0700 Subject: [PATCH 379/499] use Releaser v2 configuration (#135) --- .circleci/scripts/macos-install-xamarin.sh | 3 +- .ldrelease/build-docs.sh | 28 +++++++++++++++ .ldrelease/config.yml | 35 ++++++++++++------- .ldrelease/mac-build.sh | 2 +- .ldrelease/mac-prepare.sh | 7 ++-- .ldrelease/mac-publish-dry-run.sh | 7 ++++ .ldrelease/mac-publish.sh | 12 +++---- .ldrelease/mac-test.sh | 2 +- .ldrelease/secrets.properties | 2 ++ .ldrelease/windows-build.ps1 | 11 ------ .../LaunchDarkly.ClientSdk.csproj | 12 ++++--- 11 files changed, 79 insertions(+), 42 deletions(-) create mode 100755 .ldrelease/build-docs.sh create mode 100755 .ldrelease/mac-publish-dry-run.sh create mode 100644 .ldrelease/secrets.properties delete mode 100644 .ldrelease/windows-build.ps1 diff --git a/.circleci/scripts/macos-install-xamarin.sh b/.circleci/scripts/macos-install-xamarin.sh index 0ec57028..a10e3a5e 100755 --- a/.circleci/scripts/macos-install-xamarin.sh +++ b/.circleci/scripts/macos-install-xamarin.sh @@ -22,7 +22,8 @@ set -e # The .NET SDK 5.0 installer is pinned to a specific version -DOTNET_SDK_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/de613120-9306-4867-b504-45fcc81ba1b6/2a03f18c549f52cf78f88afa44e6dc6a/dotnet-sdk-5.0.201-osx-x64.pkg +# See: https://dotnet.microsoft.com/download/dotnet/5.0 +DOTNET_SDK_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/88bc1553-e90f-4a4f-9574-65d9a5065cd2/1d5646e1abb8b4d4a61ba0b0be976047/dotnet-sdk-5.0.402-osx-x64.pkg # Currently we are also pinning the rest of the installers to specific version URLs. # Alternately, we could use the "latest stable" mode of boots: diff --git a/.ldrelease/build-docs.sh b/.ldrelease/build-docs.sh new file mode 100755 index 00000000..18163a0b --- /dev/null +++ b/.ldrelease/build-docs.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -eu + +# This script is only run in the Dockerized Linux job that is used for building +# documentation. + +# This is a hack to deal with the conflict between two requirements: +# 1. Currently Xamarin projects must be built with msbuild, so the project file +# must use "MSBuild.Sdk.Extras". +# 2. Our documentation build process uses the dotnet CLI command, so it expects +# the project file to use "Microsoft.NET.Sdk" instead. +# As long as we are not building any Xamarin-specific targets, but only the .NET +# Standard target, it is OK to use "Microsoft.NET.Sdk". This script modifies the +# project file (only within the Docker container, not in source control) so it +# will build with "Microsoft.NET.Sdk" during this job. + +PROJECT_FILE=./src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + +cp "${PROJECT_FILE}" "${PROJECT_FILE}.bak" +sed "s/MSBuild.Sdk.Extras/Microsoft.NET.Sdk/g" "${PROJECT_FILE}" > "${PROJECT_FILE}.tmp" +mv "${PROJECT_FILE}.tmp" "${PROJECT_FILE}" + +# Now run the actual build-docs script from Releaser's project template +${LD_RELEASE_TEMP_DIR}/../template/build-docs.sh + +# Change the project file back +mv "${PROJECT_FILE}.bak" "${PROJECT_FILE}" diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml index 23fb7625..71b33b14 100644 --- a/.ldrelease/config.yml +++ b/.ldrelease/config.yml @@ -1,21 +1,32 @@ +version: 2 + repo: public: dotnet-client-sdk private: dotnet-client-sdk-private -circleci: - mac: - xcode: "12.4.0" - windows: # only for building documentation +jobs: + # The main build happens on a Mac host + - circleCI: + mac: + xcode: "12.4.0" + context: org-global + steps: + - step: prepare + - step: build + - step: test + - step: publish + + # Documentation is built in a Linux container + - template: + name: dotnet-linux + skip: + - test + - publish + - publish-dry-run env: + BUILDFRAMEWORKS: netstandard2.0 LD_RELEASE_DOCS_TARGET_FRAMEWORK: netstandard2.0 LD_RELEASE_DOCS_ASSEMBLIES: LaunchDarkly.ClientSdk LaunchDarkly.CommonSdk - steps: - - step: build - - step: build-docs - - step: publish-docs - -template: - name: dotnet-windows # only for building documentation publications: - url: https://www.nuget.org/packages/LaunchDarkly.ClientSdk @@ -23,7 +34,7 @@ publications: documentation: title: LaunchDarkly Client-Side SDK for .NET - githubPages: true + gitHubPages: true sdk: displayName: ".NET (client-side)" diff --git a/.ldrelease/mac-build.sh b/.ldrelease/mac-build.sh index 92c4149c..39e99b36 100755 --- a/.ldrelease/mac-build.sh +++ b/.ldrelease/mac-build.sh @@ -5,4 +5,4 @@ set -eu # Build the project for all target frameworks. This includes building the .nupkg, because of # the directive in our project file. -msbuild /restore /p:Configuration=Debug src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +msbuild /restore /p:Configuration=Release src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh index 11412c5e..811a0f9d 100755 --- a/.ldrelease/mac-prepare.sh +++ b/.ldrelease/mac-prepare.sh @@ -3,8 +3,5 @@ set -eu set +o pipefail -./scripts/macos-install-xamarin.sh android ios -./scripts/macos-install-android-sdk.sh 25 26 27 - -export HOMEBREW_NO_AUTO_UPDATE=1 -brew install awscli +./.circleci/scripts/macos-install-xamarin.sh android ios +./.circleci/scripts/macos-install-android-sdk.sh 25 26 27 diff --git a/.ldrelease/mac-publish-dry-run.sh b/.ldrelease/mac-publish-dry-run.sh new file mode 100755 index 00000000..3298c420 --- /dev/null +++ b/.ldrelease/mac-publish-dry-run.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eu + +for pkg in $(find ./src/LaunchDarkly.ClientSdk/bin/Release -name '*.nupkg' -o -name '*.snupkg'); do + cp "$pkg" "${LD_RELEASE_ARTIFACTS_DIR}" +done diff --git a/.ldrelease/mac-publish.sh b/.ldrelease/mac-publish.sh index 56d6ca40..ca34033a 100755 --- a/.ldrelease/mac-publish.sh +++ b/.ldrelease/mac-publish.sh @@ -2,10 +2,10 @@ set -eu -# Since we are currently publishing in Debug configuration, we can push the .nupkg that build.sh already built. +NUGET_KEY=$(cat "${LD_RELEASE_SECRETS_DIR}/dotnet_nuget_api_key") -export AWS_DEFAULT_REGION=us-east-1 -NUGET_KEY=$(aws ssm get-parameter --name /production/common/services/nuget/api_key --with-decryption --query "Parameter.Value" --output text) - -nuget push "./src/LaunchDarkly.ClientSdk/bin/Debug/LaunchDarkly.ClientSdk.${LD_RELEASE_VERSION}.nupkg" \ - -ApiKey "${NUGET_KEY}" -Source https://www.nuget.org +for pkg in $(find ./src/LaunchDarkly.ClientSdk/bin/Release -name '*.nupkg' -o -name '*.snupkg'); do + echo "publishing $pkg" + nuget push "$pkg" -ApiKey "${NUGET_KEY}" -Source https://www.nuget.org + echo "published $pkg" +done diff --git a/.ldrelease/mac-test.sh b/.ldrelease/mac-test.sh index 6f2d49f9..12b5dae1 100755 --- a/.ldrelease/mac-test.sh +++ b/.ldrelease/mac-test.sh @@ -4,4 +4,4 @@ set -eu # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by regular CI jobs) -dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f netcoreapp2.0 +dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f net5.0 diff --git a/.ldrelease/secrets.properties b/.ldrelease/secrets.properties new file mode 100644 index 00000000..d770817d --- /dev/null +++ b/.ldrelease/secrets.properties @@ -0,0 +1,2 @@ + +dotnet_nuget_api_key=param:/production/common/services/nuget/api_key diff --git a/.ldrelease/windows-build.ps1 b/.ldrelease/windows-build.ps1 deleted file mode 100644 index 3d550745..00000000 --- a/.ldrelease/windows-build.ps1 +++ /dev/null @@ -1,11 +0,0 @@ - -# Different from the standard build.ps1 for .NET projects because we must use msbuild -# instead of dotnet build. We're only doing this build in order to support building -# documentation, so we only use the .NET Standard 2.0 target and don't need Xamarin. - -$ErrorActionPreference = "Stop" - -$scriptDir = split-path -parent $MyInvocation.MyCommand.Definition -Import-Module "$scriptDir/circleci/template/helpers.psm1" -Force - -ExecuteOrFail { msbuild /restore /p:TargetFramework=netstandard2.0 /p:Configuration=Debug src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index e2349c8d..abba7120 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -2,11 +2,13 @@ 2.0.0-alpha.1 - - netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 - $(BaseTargetFrameworks);net452 - $(BaseTargetFrameworks) - $(LD_TARGET_FRAMEWORKS) + + netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 + $(BUILDFRAMEWORKS) Library LaunchDarkly.ClientSdk LaunchDarkly.ClientSdk From ffebfde281b175a33359ceea5835781c2d4df71d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Nov 2021 15:59:50 -0800 Subject: [PATCH 380/499] add Authenticode signing in release builds; remove obsolete scripts (#145) --- .ldrelease/mac-build.sh | 35 ++++++- .ldrelease/mac-prepare.sh | 28 ++++++ .ldrelease/secrets.properties | 10 ++ .ldrelease/update-version.sh | 11 --- CONTRIBUTING.md | 8 +- docs/launchdarkly.css | 7 -- docs/project.shfbproj | 91 ------------------- scripts/build-docs.ps1 | 79 ---------------- scripts/build-test-package.sh | 8 +- scripts/package.sh | 30 ------ scripts/publish-docs.sh | 66 -------------- scripts/release.sh | 20 ---- scripts/update-version.sh | 12 --- .../LaunchDarkly.ClientSdk.csproj | 2 - 14 files changed, 78 insertions(+), 329 deletions(-) delete mode 100755 .ldrelease/update-version.sh delete mode 100644 docs/launchdarkly.css delete mode 100644 docs/project.shfbproj delete mode 100644 scripts/build-docs.ps1 delete mode 100755 scripts/package.sh delete mode 100755 scripts/publish-docs.sh delete mode 100755 scripts/release.sh delete mode 100755 scripts/update-version.sh diff --git a/.ldrelease/mac-build.sh b/.ldrelease/mac-build.sh index 39e99b36..2d768679 100755 --- a/.ldrelease/mac-build.sh +++ b/.ldrelease/mac-build.sh @@ -2,7 +2,36 @@ set -eu -# Build the project for all target frameworks. This includes building the .nupkg, because of -# the directive in our project file. +PROJECT_FILE=src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj -msbuild /restore /p:Configuration=Release src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +# Build the project for all target frameworks. This does not include building the +# .nupkg, because we didn't set in our project file; we +# don't want to create the package until we've signed the assemblies. + +msbuild /restore /p:Configuration=Release "${PROJECT_FILE}" + +# Sign the code with osslsigncode (which was installed in mac-prepare.sh) + +SIGNCODE_DIR="${LD_RELEASE_TEMP_DIR}/osslsigncode" +SIGNCODE="${SIGNCODE_DIR}/osslsigncode" + +echo "" +echo "Signing assemblies..." +for dll in $(find ./src/LaunchDarkly.ClientSdk/bin/Release -name LaunchDarkly.ClientSdk.dll); do + echo -n "${dll}: " + ${SIGNCODE} sign \ + -certs "${LD_RELEASE_SECRETS_DIR}/dotnet_code_signing_certificate" \ + -key "${LD_RELEASE_SECRETS_DIR}/dotnet_code_signing_private_key" \ + -readpass "${LD_RELEASE_SECRETS_DIR}/dotnet_code_signing_private_key_passphrase" \ + -h sha1 \ + -n "LaunchDarkly" \ + -i "https://www.launchdarkly.com/" \ + -t "http://timestamp.comodoca.com/authenticode" \ + -in "${dll}" \ + -out "${dll}.signed" || (echo "Signing failed; see log for error" >&2; exit 1) + mv ${dll}.signed ${dll} +done + +echo "" +echo "Creating NuGet package" +msbuild /t:pack /p:NoBuild=true /p:Configuration=Release "${PROJECT_FILE}" diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh index 811a0f9d..95d0d8a2 100755 --- a/.ldrelease/mac-prepare.sh +++ b/.ldrelease/mac-prepare.sh @@ -5,3 +5,31 @@ set +o pipefail ./.circleci/scripts/macos-install-xamarin.sh android ios ./.circleci/scripts/macos-install-android-sdk.sh 25 26 27 + +# Download and build the osslsigncode tool. In our other .NET projects, code signing +# is handled by our standard project template scripts for .NET, which use a Docker +# image that already has osslsigncode installed. But here we're using a CircleCI job +# to do the build, so we need to do the code-signing in the same place and download +# the tool as needed. Unfortunately it has to be built from source. + +SIGNCODE_DOWNLOAD_URL=https://github.com/mtrojnar/osslsigncode/releases/download/2.1/osslsigncode-2.1.0.tar.gz +SIGNCODE_ARCHIVE="${LD_RELEASE_TEMP_DIR}/osslsigncode-2.1.0.tar.gz" +SIGNCODE_DIR="${LD_RELEASE_TEMP_DIR}/osslsigncode" +SIGNCODE="${SIGNCODE_DIR}/osslsigncode" + +echo "" +echo "Downloading osslsigncode..." +curl --fail --silent -L "${SIGNCODE_DOWNLOAD_URL}" >"${SIGNCODE_ARCHIVE}" +mkdir -p "${SIGNCODE_DIR}" +tar xfz "${SIGNCODE_ARCHIVE}" -C "${SIGNCODE_DIR}" + +echo "" +echo "Building osslsigncode..." +HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config +export PKG_CONFIG=$(which pkg-config) +export PKG_CONFIG_PATH=/usr/local/Cellar/openssl@1.1/1.1.1i/lib/pkgconfig +pushd "${SIGNCODE_DIR}/osslsigncode-2.1.0" +./configure +make +cp osslsigncode .. +popd diff --git a/.ldrelease/secrets.properties b/.ldrelease/secrets.properties index d770817d..7ba115e4 100644 --- a/.ldrelease/secrets.properties +++ b/.ldrelease/secrets.properties @@ -1,2 +1,12 @@ +# Authenticode certificate. This is a "blob:" rather than a "param:" because it's several K of binary data. +dotnet_code_signing_certificate=blob:/code-signing/catamorphic_code_signing_certificate.pem + +# Private key for the certificate. +dotnet_code_signing_private_key=blob:/code-signing/catamorphic_code_signing_private_key.pem + +# Passphrase for the private key. +dotnet_code_signing_private_key_passphrase=param:/production/common/releasing/code_signing/private_key_passphrase + +# NuGet API key for publishing packages. dotnet_nuget_api_key=param:/production/common/services/nuget/api_key diff --git a/.ldrelease/update-version.sh b/.ldrelease/update-version.sh deleted file mode 100755 index fc7dcda0..00000000 --- a/.ldrelease/update-version.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# This script gets run on the Releaser host, not in the Mac or Windows CI jobs - -set -eu - -PROJECT_FILE=./src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj -TEMP_FILE="${PROJECT_FILE}.tmp" - -sed "s#^\( *\)[^<]*#\1${LD_RELEASE_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" -mv "${TEMP_FILE}" "${PROJECT_FILE}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34fa139c..7bfebdfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We encourage pull requests and other contributions from the community. Before su ### Prerequisites -The .NET Standard target requires only the .NET Core 2.1 SDK, while the iOS and Android targets require the corresponding Xamarin SDKs. +The .NET Standard target requires only the .NET Core 2.1 SDK or higher, while the iOS and Android targets require the corresponding Xamarin SDKs. ### Building @@ -51,11 +51,9 @@ Note that the mobile unit tests currently do not cover background-mode behavior ### Packaging/releasing -Run `./scripts/update-version.sh ` to set the project version. +Releases are done through LaunchDarkly's standard project releaser tool. The scripts in `.ldrelease` implement most of this process, because unlike our other .NET projects which can be built with the .NET 5 SDK in a Linux container, this one currently must be built on a MacOS host in CircleCI. Do not modify these scripts unless you are very sure what you're doing. -Run `./scripts/release.sh` to build the project for all target frameworks and upload the package to NuGet. You must have already configured the necessary NuGet key locally. - -To verify that the package can be built without uploading it, run `./scripts/package.sh`. +If you need to do a manual package build for any reason, you can do it (on MacOS) using the same command shown above under "Building", but adding the option `/t:pack`. However, this will not include Authenticode signing; therefore, please do not publish a GA release of the package from a manual build. If it's absolutely necessary to do so, consult with the SDK team to find out how to access the code-signing certificate. ### Building a temporary package diff --git a/docs/launchdarkly.css b/docs/launchdarkly.css deleted file mode 100644 index 4fd0fffd..00000000 --- a/docs/launchdarkly.css +++ /dev/null @@ -1,7 +0,0 @@ - -/* LaunchDarkly additions to default Sandcastle styles */ - -/* hide search box because it is PHP-based and can't work in Github Pages */ -form#SearchForm { - display: none; -} diff --git a/docs/project.shfbproj b/docs/project.shfbproj deleted file mode 100644 index ccd15bd0..00000000 --- a/docs/project.shfbproj +++ /dev/null @@ -1,91 +0,0 @@ - - - - - Debug - AnyCPU - 2.0 - {0555bc4c-d824-4f83-a272-6bcac93347a1} - 2017.9.26.0 - - Documentation - Documentation - Documentation - - .NET Framework 4.5 - .\build\html - Documentation - en-US - - - - - - - - LaunchDarkly Client-Side SDK for .NET $(LD_RELEASE_VERSION) - 1.0.0.0 - True - False - MemberName - 2 - False - Blank - Website - C#, Visual Basic - VS2013 - True - True - False - False - OnlyWarningsAndErrors - 100 - - - - - - - - - - - - - - - - - - - - - - - - - - - OnBuildSuccess - - - - ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Common.Logging.dll - - - ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Common.Logging.Core.dll - - - ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\LaunchDarkly.Cache.dll - - - ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\LaunchDarkly.EventSource.dll - - - ..\src\LaunchDarkly.ClientSdk\bin\Debug\netstandard2.0\Newtonsoft.Json.dll - - - \ No newline at end of file diff --git a/scripts/build-docs.ps1 b/scripts/build-docs.ps1 deleted file mode 100644 index 44b792b9..00000000 --- a/scripts/build-docs.ps1 +++ /dev/null @@ -1,79 +0,0 @@ - -# -# This script builds HTML documentation for the SDK using Sandcastle Help File Builder. It assumes that -# the Sandcastle software is already installed on the host. The Sandcastle GUI is not required, only the -# core tools and the SandcastleBuilderUtils package that provides the MSBuild targets. -# -# The script takes no parameters; it infers the project version by looking at the .csproj file. It starts -# by building the project in Debug configuration. -# -# Since some public APIs are provided by the LaunchDarkly.CommonSdk package, the Sandcastle project is -# configured to merge that package's documentation into this one, which requires some special file -# copying as seen below. -# - -# Terminate the script if any PowerShell command fails -$ErrorActionPreference = "Stop" - -# Terminate the script if any external command fails -function ExecuteOrFail { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, - [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd) - ) - & $cmd - if ($lastexitcode -ne 0) { - throw ($errorMessage) - } -} - -ExecuteOrFail { msbuild /restore /p:Configuration=Debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj } - -# Building the SDK causes the assemblies for all its package dependencies to be copied into bin\Debug\netstandard2.0. -# The .shfbproj is configured to expect them to be there. However, we also need the XML documentation file -# for LaunchDarkly.CommonSdk, which isn't automatically copied. We can get it out of the NuGet package -# cache, but first we need to determine what version of it we're using. -$match = Select-String ` - -Path src\LaunchDarkly.ClientSdk\LaunchDarkly.ClientSdk.csproj ` - -Pattern "([^<]*)" -if ($match.Matches.Length -ne 1) { - throw "Could not find SDK version string in project file" -} -$sdkVersion = $match.Matches[0].Groups[1].Value - -[System.Environment]::SetEnvironmentVariable("LD_RELEASE_VERSION", $sdkVersion, "Process") - -try -{ - Push-Location - Set-Location docs - ExecuteOrFail { msbuild project.shfbproj /p:Verbose=True } -} -finally -{ - Pop-Location -} - -# Add our own stylesheet overrides. You're supposed to be able to put customized stylesheets in -# ./styles (relative to the project file) and have them be automatically copied in, but that -# doesn't seem to work, so we'll just modify the CSS file after building. -Get-Content docs\launchdarkly.css | Add-Content docs\build\html\styles\branding-Website.css diff --git a/scripts/build-test-package.sh b/scripts/build-test-package.sh index 62871341..060a6b0f 100755 --- a/scripts/build-test-package.sh +++ b/scripts/build-test-package.sh @@ -16,11 +16,13 @@ mkdir -p "${TEST_PACKAGE_DIR}" cp "${PROJECT_FILE}" "${SAVE_PROJECT_FILE}" -"$(dirname "$0")/update-version.sh" "${TEST_VERSION}" - trap 'mv "${SAVE_PROJECT_FILE}" "${PROJECT_FILE}"' EXIT -msbuild /restore +temp_file="${PROJECT_FILE}.tmp" +sed "s#^\( *\)[^<]*#\1${TEST_VERSION}#g" "${project_file}" > "${temp_file}" +mv "${temp_file}" "${PROJECT_FILE}" + +msbuild /restore -t:pack "${PROJECT_FILE}" NUPKG_FILE="src/LaunchDarkly.ClientSdk/bin/Debug/LaunchDarkly.ClientSdk.${TEST_VERSION}.nupkg" if [ -f "${NUPKG_FILE}" ]; then diff --git a/scripts/package.sh b/scripts/package.sh deleted file mode 100755 index ba822d94..00000000 --- a/scripts/package.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -set -e - -# Usage: ./scripts/package.sh [debug|release] - -# This script performs a clean build for all target platforms, which produces both the DLLs and the .nupkg, -# and also runs the .NET Standard unit tests. It is used in the LaunchDarkly release process. It must be run -# on MacOS, since iOS is one of the targets. - -# msbuild expects word-capitalization of this parameter -CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` -if [[ -z "$CONFIG" ]]; then - CONFIG=Debug # currently we're releasing debug builds by default -fi - -# Remove any existing build products. - -msbuild /t:clean -rm -f ./src/LaunchDarkly.ClientSdk/bin/Debug/*.nupkg -rm -f ./src/LaunchDarkly.ClientSdk/bin/Release/*.nupkg - -# Build the project for all target frameworks. This includes building the .nupkg, because of -# the directive in our project file. - -msbuild /restore /p:Configuration=$CONFIG src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - -# Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by CI jobs in config.yml) - -export ASPNETCORE_SUPPRESSSTATUSMESSAGES=true # suppresses some annoying test output -dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f netcoreapp2.0 diff --git a/scripts/publish-docs.sh b/scripts/publish-docs.sh deleted file mode 100755 index 56c36876..00000000 --- a/scripts/publish-docs.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -set -ue - -# Publishes HTML content built by build-docs.ps1 to Github Pages. If the gh-pages branch -# doesn't already exist, we will create it. - -# This logic is copied from the publish-github-pages.sh script in Releaser. Once we are able -# to build docs in CI, this step can just be done by Releaser. - -if [ ! -d ./docs/build/html ]; then - echo "Docs have not been built" - exit 1 -fi - -if [ -z "${LD_RELEASE_VERSION:-}" ]; then - LD_RELEASE_VERSION=$(sed -n -e "s%.*\([^<]*\).*%\1%p" src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj) - if [ -z "${LD_RELEASE_VERSION}" ]; then - echo "Could not find SDK version string in project file" - exit 1 - fi -fi - -CONTENT_PATH=$(pwd)/docs/build/html -COMMIT_MESSAGE="Updating documentation to version ${LD_RELEASE_VERSION}" - -GH_PAGES_BRANCH=gh-pages - -LD_RELEASE_PROJECT_DIR=$(pwd) -LD_RELEASE_TEMP_DIR=$(mktemp -d) -trap "rm -rf ${LD_RELEASE_TEMP_DIR}" EXIT - -# Check for a prerelease version string like "2.0.0-beta.1" -if [[ "${LD_RELEASE_VERSION}" =~ '-' ]]; then - echo "Not publishing documentation because this is not a production release" - exit 0 -fi - -echo "Publishing to Github Pages" - -cd "${LD_RELEASE_PROJECT_DIR}" -GIT_URL="$(git remote get-url origin)" -GH_PAGES_CHECKOUT_DIR="${LD_RELEASE_TEMP_DIR}/gh-pages-checkout" - -rm -rf "${GH_PAGES_CHECKOUT_DIR}" -if git clone -b "${GH_PAGES_BRANCH}" --single-branch "${GIT_URL}" "${GH_PAGES_CHECKOUT_DIR}"; then - cd "${GH_PAGES_CHECKOUT_DIR}" - git rm -qr ./* || true -else - echo "Can't find ${GH_PAGES_BRANCH} branch; creating one from default branch" - git clone "${GIT_URL}" "${GH_PAGES_CHECKOUT_DIR}" - cd "${GH_PAGES_CHECKOUT_DIR}" - # branch off of the very first commit, so the history of the new branch will be simple - first_commit=$(git log --reverse --format=%H | head -n 1) - git checkout "${first_commit}" - git checkout -b "${GH_PAGES_BRANCH}" - git rm -qr ./* || true - git commit -m "clearing Github Pages branch" || true -fi - -touch .nojekyll # this turns off unneeded preprocessing by GH Pages which can break our docs -git add .nojekyll -cp -r "${CONTENT_PATH}"/* . -git add ./* -git commit -m "${COMMIT_MESSAGE}" || true # possibly there are no changes -git push origin "${GH_PAGES_BRANCH}" || { echo "push to gh-pages failed" >&2; exit 1; } diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 9ef9d21e..00000000 --- a/scripts/release.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -e - -# Usage: ./scripts/release.sh [debug|release] - -# This script calls package.sh to create a NuGet package, and then uploads it to NuGet. It is used in -# the LaunchDarkly release process. It must be run on MacOS, since iOS is one of the targets. - -# msbuild expects word-capitalization of this parameter -CONFIG=`echo $1 | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'` -if [[ -z "$CONFIG" ]]; then - CONFIG=Debug # currently we're releasing debug builds by default -fi - -./scripts/package.sh $CONFIG - -# Since package.sh does a clean build, whichever .nupkg file now exists in the output directory -# is the one we want to upload. - -nuget push ./src/LaunchDarkly.ClientSdk/bin/$CONFIG/LaunchDarkly.ClientSdk.*.nupkg -Source https://www.nuget.org diff --git a/scripts/update-version.sh b/scripts/update-version.sh deleted file mode 100755 index 45418e5a..00000000 --- a/scripts/update-version.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# update-version.sh -# Updates the version string in the project file. - -NEW_VERSION="$1" - -PROJECT_FILE=./src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj -TEMP_FILE="${PROJECT_FILE}.tmp" - -sed "s#^\( *\)[^<]*#\1${NEW_VERSION}#g" "${PROJECT_FILE}" > "${TEMP_FILE}" -mv "${TEMP_FILE}" "${PROJECT_FILE}" diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index abba7120..42260d89 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -14,9 +14,7 @@ LaunchDarkly.ClientSdk false bin\$(Configuration)\$(Framework) - true 7.3 - True False True true From b7c071baf6c84f94930e57840dc76c7900c943d8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Nov 2021 16:50:32 -0800 Subject: [PATCH 381/499] add prerelease notice --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f6e12dd7..3093f59f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # LaunchDarkly Client-Side SDK for .NET +## This is a prerelease branch + +The `master` branch currently contains prerelease code for development of version 2.0.0 of the SDK. For the source code of the latest version 1.x release, see the [`1.x` branch](https://github.com/launchdarkly/dotnet-client-sdk/tree/1.x). + [![NuGet](https://img.shields.io/nuget/v/LaunchDarkly.ClientSdk.svg?style=flat-square)](https://www.nuget.org/packages/LaunchDarkly.ClientSdk/) [![CircleCI](https://circleci.com/gh/launchdarkly/dotnet-client-sdk.svg?style=shield)](https://circleci.com/gh/launchdarkly/dotnet-client-sdk) [![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/dotnet-client-sdk) From 5e70127a8eaeedf75994efe365d45a1463a0344e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Nov 2021 17:17:39 -0800 Subject: [PATCH 382/499] add strong naming in release build --- .ldrelease/mac-prepare.sh | 3 +++ .ldrelease/secrets.properties | 3 +++ LaunchDarkly.pk | Bin 0 -> 160 bytes README.md | 12 ++++++------ .../LaunchDarkly.ClientSdk.csproj | 5 +++++ .../Properties/AssemblyInfo.cs | 6 ++++++ 6 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 LaunchDarkly.pk diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh index 95d0d8a2..9f65d424 100755 --- a/.ldrelease/mac-prepare.sh +++ b/.ldrelease/mac-prepare.sh @@ -33,3 +33,6 @@ pushd "${SIGNCODE_DIR}/osslsigncode-2.1.0" make cp osslsigncode .. popd + +# Copy the strong-naming key that was downloaded due to our secrets.properties declaration +cp "${LD_RELEASE_SECRETS_DIR}/LaunchDarkly.ClientSdk.snk" . diff --git a/.ldrelease/secrets.properties b/.ldrelease/secrets.properties index 7ba115e4..c1b7d8df 100644 --- a/.ldrelease/secrets.properties +++ b/.ldrelease/secrets.properties @@ -10,3 +10,6 @@ dotnet_code_signing_private_key_passphrase=param:/production/common/releasing/co # NuGet API key for publishing packages. dotnet_nuget_api_key=param:/production/common/services/nuget/api_key + +# Strong-naming key. +LaunchDarkly.ClientSdk.snk=blob:/dotnet/LaunchDarkly.ClientSdk.snk diff --git a/LaunchDarkly.pk b/LaunchDarkly.pk new file mode 100644 index 0000000000000000000000000000000000000000..d8290e41aed16ba3e3752701be6d9a3c063507f4 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa500966iXFVm!$NhP$%`JV zq(*!F>INn$@)FN{W}otFPA~Js2q`3oyfmANXnk`7Kk-e!h8`a3BQQE!T@$g}14o$? zy%Gzfa?h3>C)=N8CNWlDPKy6ZdEp%XaX2#-e#>3Uz%6$vXW1F}@Nd52C#GcFSs60b OuwPA~$U~O`?ESGf_CA>a literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 3093f59f..1ed10f75 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,13 @@ The published version of this assembly is digitally signed with Authenticode and ``` Public Key: -0024000004800000940000000602000000240000525341310004000001000100f121bbf427e4d7 -edc64131a9efeefd20978dc58c285aa6f548a4282fc6d871fbebeacc13160e88566f427497b625 -56bf7ff01017b0f7c9de36869cc681b236bc0df0c85927ac8a439ecb7a6a07ae4111034e03042c -4b1569ebc6d3ed945878cca97e1592f864ba7cc81a56b8668a6d7bbe6e44c1279db088b0fdcc35 -52f746b4 +0024000004800000940000000602000000240000525341310004000001000100 +058a1dbccbc342759dc98b1eaba4467bfdea062629f212cf7c669ff26b4e2ff3 +c408292487bc349b8a687d73033ff14dbf861e1eea23303a5b5d13b1db034799 +13bd120ba372cf961d27db9f652631565f4e8aff4a79e11cfe713833157ecb5d +cbc02d772967d919f8f06fbee227a664dc591932d5b05f4da1c8439702ecfdb1 -Public Key Token: f86add69004e6885 +Public Key Token: 90b24964a3dfb906 ``` ## Learn more diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 42260d89..705bcfd9 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -73,4 +73,9 @@ + + ../../LaunchDarkly.ClientSdk.snk + true + + diff --git a/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs index 2d8a378b..4c4e7c75 100644 --- a/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs +++ b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs @@ -1,6 +1,12 @@ using System; using System.Runtime.CompilerServices; +#if DEBUG +// Allow unit tests to see internal classes. The test assemblies are not +// strong-named, so tests must be run against the Debug configuration of +// this assembly. + [assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Tests")] [assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.iOS.Tests")] [assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Android.Tests")] +#endif From 67910df8b00f3b8fe90beee8178e6bf2071b0ab0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Nov 2021 17:20:28 -0800 Subject: [PATCH 383/499] revise local storage usage to only use base64url-safe characters in namespaces/keys --- .../Interfaces/IPersistentDataStore.cs | 16 +++++--- src/LaunchDarkly.ClientSdk/Internal/Base64.cs | 14 +++---- .../Internal/DataStores/FlagDataManager.cs | 2 +- .../DataStores/PersistentDataStoreWrapper.cs | 11 +++++- .../LocalStorage.netstandard.cs | 37 +------------------ .../FlagDataManagerWithPersistenceTest.cs | 4 +- .../PersistentDataStoreWrapperTest.cs | 8 ++-- .../DataStores/PlatformLocalStorageTest.cs | 30 +++++++-------- .../MockComponents.cs | 4 +- 9 files changed, 53 insertions(+), 73 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs index 4ea285c7..d363d298 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Immutable; namespace LaunchDarkly.Sdk.Client.Interfaces { @@ -15,11 +14,18 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// only need to use this interface if you want to provide different storage behavior. /// /// - /// Each data item is defined by a "namespace" and a "key". Both of these are non-null and non-empty - /// strings defined by the SDK. Keys are unique within a namespace. The value of the data item is - /// simply a string. The store should not make any assumptions about the allowed content of these - /// strings. + /// Each data item is uniquely identified by the combination of a "namespace" and a "key", and has + /// a string value. These are defined as follows: /// + /// + /// Both the namespace and the key are non-null and non-empty strings. + /// + /// Both the namespace and the key contain only alphanumeric characters, + /// hyphens, and underscores. + /// The namespace always starts with "LaunchDarkly". + /// The value can be any non-null string, including an empty string. + /// + /// /// /// Unlike server-side SDKs, the persistent data store in this SDK treats the entire set of flags /// for a given user as a single value which is written to the store all at once, rather than one diff --git a/src/LaunchDarkly.ClientSdk/Internal/Base64.cs b/src/LaunchDarkly.ClientSdk/Internal/Base64.cs index 174040a0..d8d877c9 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Base64.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Base64.cs @@ -8,15 +8,15 @@ internal static class Base64 { private static readonly SHA256 _hasher = SHA256.Create(); - public static string UrlSafeEncode(this string plainText) - { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_'); - } + public static string UrlSafeEncode(this string plainText) => + UrlSafeBase64String(Encoding.UTF8.GetBytes(plainText)); - public static string Sha256Hash(string input) => - Convert.ToBase64String( + public static string UrlSafeSha256Hash(string input) => + UrlSafeBase64String( _hasher.ComputeHash(Encoding.UTF8.GetBytes(input)) ); + + public static string UrlSafeBase64String(byte[] input) => + Convert.ToBase64String(input).Replace('+', '-').Replace('/', '_'); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs index 9aef9a58..adcfeb34 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs @@ -181,6 +181,6 @@ public bool Upsert(string key, ItemDescriptor data) public void Dispose() => _persistentStore?.Dispose(); - internal static string UserIdFor(User user) => Base64.Sha256Hash(user.Key); + internal static string UserIdFor(User user) => Base64.UrlSafeSha256Hash(user.Key); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index f0f42a18..1a9a6442 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -15,12 +15,19 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores /// allows FlagDataManager (and other parts of the SDK that may need to access persistent /// storage) to be written in a clearer way without embedding many implementation details. /// + /// + /// See for the rules about what namespaces and keys + /// we can use. It is 's responsibility to follow + /// those rules. We are OK as long as we use base64url-encoding for all variables such as + /// user key and mobile key, and use only characters from the base64url set (A-Z, a-z, + /// 0-9, -, and _) for other namespace/key components. + /// internal sealed class PersistentDataStoreWrapper : IDisposable { private const string NamespacePrefix = "LaunchDarkly"; private const string GlobalAnonUserKey = "anonUser"; private const string EnvironmentMetadataKey = "index"; - private const string EnvironmentUserDataKeyPrefix = "flags:"; + private const string EnvironmentUserDataKeyPrefix = "flags_"; private readonly IPersistentDataStore _persistentStore; private readonly string _globalNamespace; @@ -40,7 +47,7 @@ Logger log _log = log; _globalNamespace = NamespacePrefix; - _environmentNamespace = NamespacePrefix + ":" + Base64.Sha256Hash(mobileKey); + _environmentNamespace = NamespacePrefix + "_" + Base64.UrlSafeSha256Hash(mobileKey); } public FullDataSet? GetUserData(string userId) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs index b9e177e8..c6117891 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.IO.IsolatedStorage; -using System.Text; using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client.PlatformSpecific @@ -16,8 +15,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal sealed partial class LocalStorage : IPersistentDataStore { - private const string ConfigDirectoryName = "LaunchDarkly"; - public string GetValue(string storageNamespace, string key) { return WithStore(store => @@ -56,8 +53,7 @@ public void SetValue(string storageNamespace, string key, string value) } else { - var dirPath = MakeDirectoryPath(storageNamespace); - store.CreateDirectory(dirPath); // has no effect if directory already exists + store.CreateDirectory(storageNamespace); // has no effect if directory already exists using (var stream = store.OpenFile(filePath, FileMode.Create, FileAccess.Write)) { using (var sw = new StreamWriter(stream)) @@ -87,36 +83,7 @@ private void WithStore(Action callback) }); } - private static string MakeDirectoryPath(string storageNamespace) => - string.IsNullOrEmpty(storageNamespace) - ? ConfigDirectoryName - : (ConfigDirectoryName + "." + EscapeFilenameComponent(storageNamespace)); - private static string MakeFilePath(string storageNamespace, string key) => - MakeDirectoryPath(storageNamespace) + "/" + EscapeFilenameComponent(key); - - private static string EscapeFilenameComponent(string name) - { - StringBuilder buf = null; - for (var i = 0; i < name.Length; i++) - { - var ch = name[i]; - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' - || (ch == '.' && i > 0)) - { - buf?.Append(ch); - } - else - { - if (buf == null) // create StringBuilder lazily since most names will be valid - { - buf = new StringBuilder(name.Length + 30); - buf.Append(name.Substring(0, i)); - } - buf.Append('%').Append(((int)ch).ToString("X")); // hex value - } - } - return buf == null ? name : buf.ToString(); - } + storageNamespace + "/" + key; } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index dc09a1c8..8fa1296e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -113,7 +113,7 @@ public void InitUpdatesIndex() var index = _persistentStore.InspectUserIndex(BasicMobileKey); Assert.Equal( - ImmutableList.Create(Base64.Sha256Hash(BasicUser.Key), Base64.Sha256Hash(OtherUser.Key)), + ImmutableList.Create(Base64.UrlSafeSha256Hash(BasicUser.Key), Base64.UrlSafeSha256Hash(OtherUser.Key)), index.Data.Select(e => e.UserId).ToImmutableList() ); } @@ -133,7 +133,7 @@ public void InitEvictsLeastRecentUser() var index = _persistentStore.InspectUserIndex(BasicMobileKey); Assert.Equal( - ImmutableList.Create(Base64.Sha256Hash(OtherUser.Key), Base64.Sha256Hash(user3.Key)), + ImmutableList.Create(Base64.UrlSafeSha256Hash(OtherUser.Key), Base64.UrlSafeSha256Hash(user3.Key)), index.Data.Select(e => e.UserId).ToImmutableList() ); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs index 1fa974dc..3a69265c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -10,12 +10,12 @@ public class PersistentDataStoreWrapperTest : BaseTest // This verifies non-platform-dependent behavior, such as what keys we store particular // things under, using a mock persistent storage implementation. - private static readonly string MobileKeyHash = Base64.Sha256Hash(BasicMobileKey); + private static readonly string MobileKeyHash = Base64.UrlSafeSha256Hash(BasicMobileKey); private static readonly string ExpectedGlobalNamespace = "LaunchDarkly"; - private static readonly string ExpectedEnvironmentNamespace = "LaunchDarkly:" + MobileKeyHash; + private static readonly string ExpectedEnvironmentNamespace = "LaunchDarkly_" + MobileKeyHash; private const string UserKey = "user-key"; - private static readonly string UserHash = Base64.Sha256Hash(UserKey); - private static readonly string ExpectedUserFlagsKey = "flags:" + UserHash; + private static readonly string UserHash = Base64.UrlSafeSha256Hash(UserKey); + private static readonly string ExpectedUserFlagsKey = "flags_" + UserHash; private static readonly string ExpectedIndexKey = "index"; private static readonly string ExpectedAnonUserKey = "anonUser"; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs index 4bc27574..a9a57057 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs @@ -74,26 +74,26 @@ public void RemoveUnknownKey() [Fact] public void KeysWithSpecialCharacters() { - var storageNamespace = TestNamespacePrefix + nameof(KeysWithSpecialCharacters); var keys = new string[] { - "$", - "/", - ".", - "?", - "key/with//slashes", - "key.with.dots", - "këy.wíth.âccents" + "-", + "_", + "key-with-dashes", + "key_with_underscores" }; var keysAndValues = keys.ToDictionary(key => key, key => "value-" + key); - foreach (var kv in keysAndValues) + foreach (var k in keysAndValues.Keys) { - testLogger.Info("*** setting {0} to {1}", kv.Key, kv.Value); - _storage.SetValue(storageNamespace, kv.Key, kv.Value); - } - foreach (var kv in keysAndValues) - { - Assert.Equal(kv.Value, _storage.GetValue(storageNamespace, kv.Key)); + var ns = TestNamespacePrefix + nameof(KeysWithSpecialCharacters) + k; + foreach (var kv in keysAndValues) + { + testLogger.Info("*** setting {0} to {1}", kv.Key, kv.Value); + _storage.SetValue(ns, kv.Key, kv.Value); + } + foreach (var kv in keysAndValues) + { + Assert.Equal(kv.Value, _storage.GetValue(ns, kv.Key)); + } } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 6245c3f1..e28522e7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -317,10 +317,10 @@ private PersistentDataStoreWrapper WithWrapper(string mobileKey) => new PersistentDataStoreWrapper(this, mobileKey, Logs.None.Logger("")); internal void SetupUserData(string mobileKey, string userKey, FullDataSet data) => - WithWrapper(mobileKey).SetUserData(Base64.Sha256Hash(userKey), data); + WithWrapper(mobileKey).SetUserData(Base64.UrlSafeSha256Hash(userKey), data); internal FullDataSet? InspectUserData(string mobileKey, string userKey) => - WithWrapper(mobileKey).GetUserData(Base64.Sha256Hash(userKey)); + WithWrapper(mobileKey).GetUserData(Base64.UrlSafeSha256Hash(userKey)); internal UserIndex InspectUserIndex(string mobileKey) => WithWrapper(mobileKey).GetIndex(); From 48a33be65151ee87b38ec68e66182aae62c15fd5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Nov 2021 17:26:18 -0800 Subject: [PATCH 384/499] fix test package build script --- scripts/build-test-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-test-package.sh b/scripts/build-test-package.sh index 060a6b0f..3f44c922 100755 --- a/scripts/build-test-package.sh +++ b/scripts/build-test-package.sh @@ -19,7 +19,7 @@ cp "${PROJECT_FILE}" "${SAVE_PROJECT_FILE}" trap 'mv "${SAVE_PROJECT_FILE}" "${PROJECT_FILE}"' EXIT temp_file="${PROJECT_FILE}.tmp" -sed "s#^\( *\)[^<]*#\1${TEST_VERSION}#g" "${project_file}" > "${temp_file}" +sed "s#^\( *\)[^<]*#\1${TEST_VERSION}#g" "${PROJECT_FILE}" > "${temp_file}" mv "${temp_file}" "${PROJECT_FILE}" msbuild /restore -t:pack "${PROJECT_FILE}" From acc4dcccf8fe3f1e7bbae303a44404f3f380b292 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Nov 2021 17:44:21 -0800 Subject: [PATCH 385/499] remove ReadTimeout setting which only worked in Android but not in a useful way --- .../Integrations/HttpConfigurationBuilder.cs | 36 +++---------------- .../Interfaces/HttpConfiguration.cs | 23 +++--------- .../DataSources/FeatureFlagRequestor.cs | 12 +++---- .../PlatformSpecific/Http.android.cs | 2 -- .../PlatformSpecific/Http.ios.cs | 1 - .../PlatformSpecific/Http.netstandard.cs | 2 +- .../PlatformSpecific/Http.shared.cs | 3 +- .../HttpConfigurationBuilderTest.cs | 8 ----- .../LdClientDiagnosticEventTest.cs | 4 +-- 9 files changed, 18 insertions(+), 73 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index f4fd2278..f8104017 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -38,11 +38,6 @@ public sealed class HttpConfigurationBuilder : IDiagnosticDescription public static readonly TimeSpan DefaultConnectTimeout = TimeSpan.FromSeconds(10); // deliberately longer than the server-side SDK's default connection timeout - /// - /// The default value for : 10 seconds. - /// - public static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromSeconds(10); - /// /// The default value for : 10 seconds. /// @@ -52,7 +47,6 @@ public sealed class HttpConfigurationBuilder : IDiagnosticDescription internal List> _customHeaders = new List>(); internal HttpMessageHandler _messageHandler = null; internal IWebProxy _proxy = null; - internal TimeSpan _readTimeout = DefaultReadTimeout; internal TimeSpan _responseStartTimeout = DefaultResponseStartTimeout; internal string _wrapperName = null; internal string _wrapperVersion = null; @@ -165,30 +159,6 @@ public HttpConfigurationBuilder Proxy(IWebProxy proxy) return this; } - /// - /// Sets the socket read timeout. - /// - /// - /// - /// Sets the socket timeout. This is the amount of time without receiving data on a connection that the - /// SDK will tolerate before signaling an error. This does not apply to the streaming connection - /// used by , which has its own non-configurable read timeout - /// based on the expected behavior of the LaunchDarkly streaming service. - /// - /// - /// Not all .NET platforms support setting a sockettimeout. It is supported in - /// .NET Core 2.1+, .NET 5+, and Xamarin Android, but not in Xamarin iOS. On platforms - /// where it is not supported, this parameter is ignored. - /// - /// - /// the socket read timeout - /// the builder - public HttpConfigurationBuilder ReadTimeout(TimeSpan readTimeout) - { - _readTimeout = readTimeout; - return this; - } - /// /// Sets the maximum amount of time to wait for the beginning of an HTTP response. /// @@ -278,6 +248,9 @@ public LdValue DescribeConfiguration(LdClientContext context) => LdValue.BuildObject() .WithHttpProperties(MakeHttpProperties(context.Basic)) .Add("useReport", _useReport) + .Set("socketTimeoutMillis", _responseStartTimeout.TotalMilliseconds) + // WithHttpProperties normally sets socketTimeoutMillis to the ReadTimeout value, + // which is more correct, but we can't really set ReadTimeout in this SDK .Build(); private HttpProperties MakeHttpProperties(BasicConfiguration basic) @@ -285,7 +258,7 @@ private HttpProperties MakeHttpProperties(BasicConfiguration basic) Func handlerFn; if (_messageHandler is null) { - handlerFn = PlatformSpecific.Http.GetHttpMessageHandlerFactory(_connectTimeout, _readTimeout, _proxy); + handlerFn = PlatformSpecific.Http.GetHttpMessageHandlerFactory(_connectTimeout, _proxy); } else { @@ -297,7 +270,6 @@ private HttpProperties MakeHttpProperties(BasicConfiguration basic) .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) - .WithReadTimeout(_readTimeout) .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) .WithWrapper(_wrapperName, _wrapperVersion); diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs index 96d69175..5fd2d001 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs @@ -62,18 +62,6 @@ public sealed class HttpConfiguration /// public IWebProxy Proxy { get; } - /// - /// The network read timeout (socket timeout). - /// - /// - /// This is the amount of time without receiving data on a connection that the - /// SDK will tolerate before signaling an error. This does not apply to - /// the streaming connection used by , - /// which has its own non-configurable read timeout based on the expected behavior - /// of the LaunchDarkly streaming service. - /// - public TimeSpan ReadTimeout { get; } - /// /// The maximum amount of time to wait for the beginning of an HTTP response. /// @@ -123,7 +111,6 @@ public sealed class HttpConfiguration /// value for /// value for /// value for - /// value for /// value for /// value for public HttpConfiguration( @@ -131,12 +118,11 @@ public HttpConfiguration( IEnumerable> defaultHeaders, HttpMessageHandler messageHandler, IWebProxy proxy, - TimeSpan readTimeout, TimeSpan responseStartTimeout, bool useReport ) : this( - MakeHttpProperties(connectTimeout, defaultHeaders, messageHandler, readTimeout), + MakeHttpProperties(connectTimeout, defaultHeaders, messageHandler, proxy), messageHandler, responseStartTimeout, useReport @@ -155,7 +141,6 @@ bool useReport DefaultHeaders = httpProperties.BaseHeaders; MessageHandler = messageHandler; Proxy = httpProperties.Proxy; - ReadTimeout = httpProperties.ReadTimeout; ResponseStartTimeout = responseStartTimeout; UseReport = useReport; } @@ -181,15 +166,15 @@ internal static HttpProperties MakeHttpProperties( TimeSpan connectTimeout, IEnumerable> defaultHeaders, HttpMessageHandler messageHandler, - TimeSpan readTimeout + IWebProxy proxy ) { var ret = HttpProperties.Default .WithConnectTimeout(connectTimeout) - .WithReadTimeout(readTimeout) .WithHttpMessageHandlerFactory(messageHandler is null ? (Func)null : - _ => messageHandler); + _ => messageHandler) + .WithProxy(proxy); foreach (var kv in defaultHeaders) { ret = ret.WithHeader(kv.Key, kv.Value); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index 4319df97..68594dea 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -40,7 +40,7 @@ internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor private readonly bool _useReport; private readonly bool _withReasons; private readonly HttpClient _httpClient; - private readonly HttpProperties _httpProperties; + private readonly HttpConfiguration _httpConfig; private readonly Logger _log; private volatile EntityTagHeaderValue _etag; @@ -53,8 +53,8 @@ Logger log ) { this._baseUri = baseUri; - this._httpProperties = httpConfig.HttpProperties; - this._httpClient = _httpProperties.NewHttpClient(); + this._httpConfig = httpConfig; + this._httpClient = httpConfig.HttpProperties.NewHttpClient(); this._currentUser = user; this._useReport = httpConfig.UseReport; this._withReasons = withReasons; @@ -89,8 +89,8 @@ private Uri MakeRequestUriWithPath(string path) private async Task MakeRequest(HttpRequestMessage request) { - _httpProperties.AddHeaders(request); - using (var cts = new CancellationTokenSource(_httpProperties.ConnectTimeout)) + _httpConfig.HttpProperties.AddHeaders(request); + using (var cts = new CancellationTokenSource(_httpConfig.ResponseStartTimeout)) { if (_etag != null) { @@ -127,7 +127,7 @@ private async Task MakeRequest(HttpRequestMessage request) } //Otherwise this was a request timeout. throw new TimeoutException("Get item with URL: " + request.RequestUri + - " timed out after : " + _httpProperties.ConnectTimeout); + " timed out after : " + _httpConfig.ResponseStartTimeout); } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs index 37953780..07351f43 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.android.cs @@ -10,13 +10,11 @@ internal static partial class Http { private static Func PlatformGetHttpMessageHandlerFactory( TimeSpan connectTimeout, - TimeSpan readTimeout, IWebProxy proxy ) => p => new AndroidClientHandler() { ConnectTimeout = connectTimeout, - ReadTimeout = readTimeout, Proxy = proxy }; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs index 32deb955..155a9710 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.ios.cs @@ -20,7 +20,6 @@ internal static partial class Http private static Func PlatformGetHttpMessageHandlerFactory( TimeSpan connectTimeout, - TimeSpan readTimeout, IWebProxy proxy ) => (proxy is null) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs index 01ad76fa..82424c34 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.netstandard.cs @@ -8,7 +8,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class Http { private static Func PlatformGetHttpMessageHandlerFactory( - TimeSpan connectTimeout, TimeSpan readTimeout, IWebProxy proxy) => + TimeSpan connectTimeout, IWebProxy proxy) => null; // Returning null means HttpProperties will use the default .NET implementation, // which will take care of configuring the HTTP client with timeouts/proxies. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs index ab9dc787..e3846bb4 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Http.shared.cs @@ -22,10 +22,9 @@ internal static partial class Http /// an HTTP message handler factory or null public static Func GetHttpMessageHandlerFactory( TimeSpan connectTimeout, - TimeSpan readTimeout, IWebProxy proxy ) => - PlatformGetHttpMessageHandlerFactory(connectTimeout, readTimeout, proxy); + PlatformGetHttpMessageHandlerFactory(connectTimeout, proxy); /// /// Converts any platform-specific exceptions that might be thrown by the platform-specific diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index 547a67dd..b3d0cf7c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -52,14 +52,6 @@ public void MobileKeyHeader() Assert.Equal(basicConfig.MobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); } - [Fact] - public void ReadTimeout() - { - var prop = _tester.Property(c => c.ReadTimeout, (b, v) => b.ReadTimeout(v)); - prop.AssertDefault(HttpConfigurationBuilder.DefaultReadTimeout); - prop.AssertCanSet(TimeSpan.FromSeconds(7)); - } - [Fact] public void ResponseStartTimeout() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs index f8213553..9c878927 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -219,7 +219,7 @@ public void CustomConfigForHTTP() c => c.Http( Components.HttpConfiguration() .ConnectTimeout(TimeSpan.FromMilliseconds(8888)) - .ReadTimeout(TimeSpan.FromMilliseconds(9999)) + .ResponseStartTimeout(TimeSpan.FromMilliseconds(9999)) .MessageHandler(StreamWithInitialData().AsMessageHandler()) .UseReport(true) ), @@ -349,7 +349,7 @@ public static LdValue.ObjectBuilder Base() => .Add("eventsFlushIntervalMillis", EventProcessorBuilder.DefaultFlushInterval.TotalMilliseconds) .Add("inlineUsersInEvents", false) .Add("reconnectTimeMillis", StreamingDataSourceBuilder.DefaultInitialReconnectDelay.TotalMilliseconds) - .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultReadTimeout.TotalMilliseconds) + .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultResponseStartTimeout.TotalMilliseconds) .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds) .Add("streamingDisabled", false) .Add("useReport", false) From d936b0945023184091d213d5d7fa9f10959c553a Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 19 Nov 2021 13:10:21 -0800 Subject: [PATCH 386/499] refactor ConnectionManager state management to handle diagnostic events correctly --- .../Internal/DataSources/ConnectionManager.cs | 140 +++++++++++------- src/LaunchDarkly.ClientSdk/LdClient.cs | 99 ++++--------- 2 files changed, 112 insertions(+), 127 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index 57454f32..c345589b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Internal; namespace LaunchDarkly.Sdk.Client.Internal.DataSources @@ -13,10 +14,12 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources /// /// /// Whenever the state of this object is modified by , - /// , , - /// or , it will decide whether to make a new connection, drop an existing - /// connection, both, or neither. If the caller wants to know when a new connection (if any) is - /// ready, it should await the returned task. + /// , , + /// , or , it will decide whether to make a new + /// connection, drop an existing connection, both, or neither. If the caller wants to know when a + /// new connection (if any) is ready, it should await the returned task. + /// + /// ConnectionManager also keeps track of whether event sending should be enabled. /// /// The object begins in a non-started state, so regardless of what properties are set, it will not /// make a connection until after has been called. @@ -25,14 +28,20 @@ internal sealed class ConnectionManager : IDisposable { private readonly Logger _log; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private readonly LdClientContext _clientContext; + private readonly IDataSourceFactory _dataSourceFactory; private readonly IDataSourceUpdateSink _updateSink; + private readonly IEventProcessor _eventProcessor; + private readonly DiagnosticDisablerImpl _diagnosticDisabler; + private readonly bool _enableBackgroundUpdating; private bool _disposed = false; private bool _started = false; private bool _initialized = false; private bool _forceOffline = false; private bool _networkEnabled = false; + private bool _inBackground = false; + private User _user = null; private IDataSource _dataSource = null; - private Func _dataSourceConstructor = null; // Note that these properties do not have simple setter methods, because the setters all // need to return Tasks. @@ -54,9 +63,24 @@ internal sealed class ConnectionManager : IDisposable /// public bool Initialized => LockUtils.WithReadLock(_lock, () => _initialized); - internal ConnectionManager(IDataSourceUpdateSink updateSink, Logger log) + internal ConnectionManager( + LdClientContext clientContext, + IDataSourceFactory dataSourceFactory, + IDataSourceUpdateSink updateSink, + IEventProcessor eventProcessor, + DiagnosticDisablerImpl diagnosticDisabler, + bool enableBackgroundUpdating, + User initialUser, + Logger log + ) { + _clientContext = clientContext; + _dataSourceFactory = dataSourceFactory; _updateSink = updateSink; + _eventProcessor = eventProcessor; + _diagnosticDisabler = diagnosticDisabler; + _enableBackgroundUpdating = enableBackgroundUpdating; + _user = initialUser; _log = log; } @@ -98,7 +122,7 @@ public Task SetForceOffline(bool forceOffline) } _forceOffline = forceOffline; _log.Info("Offline mode is now {0}", forceOffline); - return OpenOrCloseConnectionIfNecessary(); // not awaiting + return OpenOrCloseConnectionIfNecessary(false); // not awaiting }); } @@ -140,61 +164,49 @@ public Task SetNetworkEnabled(bool networkEnabled) } _networkEnabled = networkEnabled; _log.Info("Network availability is now {0}", networkEnabled); - return OpenOrCloseConnectionIfNecessary(); // not awaiting + return OpenOrCloseConnectionIfNecessary(false); // not awaiting }); } /// - /// Sets the factory function for creating an update processor, and attempts to connect if - /// appropriate. + /// Sets whether the application is currently in the background. /// /// - /// The factory function encapsulates all the information that takes into - /// account when making a connection, i.e. whether we are in streaming or polling mode, the - /// polling interval, and the curent user. ConnectionManager itself has no knowledge of - /// those things. - /// - /// Besides updating the private factory function field, we do the following: - /// - /// If the function is null, we drop our current connection (if any), and we will not make - /// any connections no matter what other properties are changed as long as it is still null. - /// - /// If it is non-null and we already have the same factory function, nothing happens. - /// - /// If it is non-null and we do not already have the same factory function, but other conditions - /// disallow making a connection, nothing happens. - /// - /// If it is non-null and we do not already have the same factory function, and no other - /// conditions disallow making a connection, we create an update processor and tell it to start. - /// In this case, we also reset to false if resetInitialized is - /// true. - /// - /// The returned task is immediately completed unless we are making a new connection, in which - /// case it is completed when the update processor signals success or failure. The task yields - /// a true result if we successfully made a connection or if we decided not to connect - /// because we are in offline mode. In other words, the result is true if - /// is true. + /// When in the background, we use a different data source (polling, at a longer interval) + /// and we do not send diagnostic events. /// - /// a factory function or null - /// true if we should reset the initialized state (e.g. if we - /// are switching users - /// a task as described above - public Task SetDataSourceConstructor(Func dataSourceConstructor, bool resetInitialized) + /// true if the application is now in the background + public void SetInBackground(bool inBackground) { - return LockUtils.WithWriteLock(_lock, () => + LockUtils.WithWriteLock(_lock, () => { - if (_disposed || _dataSourceConstructor == dataSourceConstructor) + if (_disposed || _inBackground == inBackground) { - return Task.FromResult(false); + return; } - _dataSourceConstructor = dataSourceConstructor; - _dataSource?.Dispose(); - _dataSource = null; - if (resetInitialized) + _inBackground = inBackground; + _log.Debug("Background mode is changing to {0}", inBackground); + _ = OpenOrCloseConnectionIfNecessary(true); // not awaiting + }); + } + + /// + /// Updates the current user. + /// + /// the new user + /// a task that is completed when we have received data for the new user, if the + /// data source is online, or completed immediately otherwise + public Task SetUser(User user) + { + return LockUtils.WithWriteLock(_lock, () => + { + if (_disposed) { - _initialized = false; + return Task.FromResult(false); } - return OpenOrCloseConnectionIfNecessary(); // not awaiting + _user = user; + _initialized = false; + return OpenOrCloseConnectionIfNecessary(true); }); } @@ -212,7 +224,7 @@ public Task Start() return Task.FromResult(_initialized); } _started = true; - return OpenOrCloseConnectionIfNecessary(); // not awaiting + return OpenOrCloseConnectionIfNecessary(false); // not awaiting }); } @@ -227,7 +239,6 @@ public void Dispose() } dataSource = _dataSource; _dataSource = null; - _dataSourceConstructor = null; _disposed = true; }); dataSource?.Dispose(); @@ -237,21 +248,42 @@ public void Dispose() // *not* wait for it to succeed; we return a Task that will be completed once it succeeds. In all // other cases we return an immediately-completed Task. - private Task OpenOrCloseConnectionIfNecessary() + private Task OpenOrCloseConnectionIfNecessary(bool mustReinitializeDataSource) { if (!_started) { return Task.FromResult(false); } + + // Analytics event sending is enabled as long as we're allowed to do any network things. + // (If the SDK is configured not to send events, then this is a no-op because _eventProcessor + // will be a no-op implementation). + _eventProcessor.SetOffline(_forceOffline || !_networkEnabled); + + // Diagnostic events are disabled if we're in the background. + _diagnosticDisabler?.SetDisabled(_forceOffline || !_networkEnabled || _inBackground); + + if (mustReinitializeDataSource && _dataSource != null) + { + _dataSource?.Dispose(); + _dataSource = null; + } + if (_networkEnabled && !_forceOffline) { - if (_dataSource == null && _dataSourceConstructor != null) + if (_inBackground && !_enableBackgroundUpdating) + { + _log.Debug("Background updating is disabled"); + _updateSink.UpdateStatus(DataSourceState.BackgroundDisabled, null); + return Task.FromResult(true); + } + if (_dataSource is null) { // Set the state to Initializing when there's a new data source that has not yet // started. The state will then be updated as appropriate by the data source either // calling UpdateStatus, or Init which implies UpdateStatus(Valid). _updateSink.UpdateStatus(DataSourceState.Initializing, null); - _dataSource = _dataSourceConstructor(); + _dataSource = _dataSourceFactory.CreateDataSource(_clientContext, _updateSink, _user, _inBackground); return _dataSource.Start() .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); } diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 017bfcb8..bb115148 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -32,11 +32,9 @@ public sealed class LdClient : ILdClient // Immutable client state readonly Configuration _config; readonly LdClientContext _context; - readonly IDataSourceFactory _dataSourceFactory; readonly IDataSourceStatusProvider _dataSourceStatusProvider; readonly IDataSourceUpdateSink _dataSourceUpdateSink; readonly FlagDataManager _dataStore; - readonly DiagnosticDisablerImpl _diagnosticDisabler; readonly ConnectionManager _connectionManager; readonly IBackgroundModeManager _backgroundModeManager; readonly IConnectivityStateManager _connectivityStateManager; @@ -50,7 +48,6 @@ public sealed class LdClient : ILdClient // Mutable client state (some state is also in the ConnectionManager) readonly ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim(); volatile User _user; - volatile bool _inBackground; /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot @@ -132,10 +129,10 @@ public sealed class LdClient : ILdClient _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); var diagnosticStore = _config.DiagnosticOptOut ? null : new ClientDiagnosticStore(_config, startWaitTime); - _diagnosticDisabler = _config.DiagnosticOptOut ? null : + var diagnosticDisabler = _config.DiagnosticOptOut ? null : new DiagnosticDisablerImpl(); - _context = new LdClientContext(configuration, this, diagnosticStore, _diagnosticDisabler); + _context = new LdClientContext(configuration, this, diagnosticStore, diagnosticDisabler); _log = _context.BaseLogger; _taskExecutor = _context.TaskExecutor; diagnosticStore?.SetContext(_context); @@ -175,33 +172,39 @@ public sealed class LdClient : ILdClient _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); _flagTracker = new FlagTrackerImpl(dataSourceUpdateSink); - _dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); + var dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); - _connectionManager = new ConnectionManager(_dataSourceUpdateSink, _log); + _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); + var isConnected = _connectivityStateManager.IsConnected; + + _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) + .CreateEventProcessor(_context); + _eventProcessor.SetOffline(configuration.Offline || !isConnected); + diagnosticDisabler?.SetDisabled(!isConnected && !configuration.Offline); + + _connectionManager = new ConnectionManager( + _context, + dataSourceFactory, + _dataSourceUpdateSink, + _eventProcessor, + diagnosticDisabler, + configuration.EnableBackgroundUpdating, + _user, + _log + ); _connectionManager.SetForceOffline(configuration.Offline); + _connectionManager.SetNetworkEnabled(isConnected); if (configuration.Offline) { _log.Info("Starting LaunchDarkly client in offline mode"); } - _connectionManager.SetDataSourceConstructor( - MakeDataSourceConstructor(_user, _inBackground), - true - ); - _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); _connectivityStateManager.ConnectionChanged += networkAvailable => { _log.Debug("Setting online to {0} due to a connectivity change event", networkAvailable); _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result - _eventProcessor.SetOffline(!networkAvailable || _connectionManager.ForceOffline); }; - var isConnected = _connectivityStateManager.IsConnected; - _connectionManager.SetNetworkEnabled(isConnected); - - _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) - .CreateEventProcessor(_context); - _eventProcessor.SetOffline(configuration.Offline || !isConnected); - + // Send an initial identify event, but only if we weren't explicitly set to be offline if (!configuration.Offline) @@ -625,10 +628,7 @@ public async Task IdentifyAsync(User user) }); } - return await _connectionManager.SetDataSourceConstructor( - MakeDataSourceConstructor(newUser, _inBackground), - true - ); + return await _connectionManager.SetUser(newUser); } /// @@ -691,60 +691,13 @@ void Dispose(bool disposing) Interlocked.CompareExchange(ref _instance, null, this); } - internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) - { - _ = OnBackgroundModeChangedAsync(sender, args); // do not wait for the result - } - - internal async Task OnBackgroundModeChangedAsync(object sender, BackgroundModeChangedEventArgs args) - { - var goingIntoBackground = args.IsInBackground; - var wasInBackground = LockUtils.WithWriteLock(_stateLock, () => - { - var oldValue = _inBackground; - _inBackground = goingIntoBackground; - return oldValue; - }); - if (goingIntoBackground == wasInBackground) - { - return; - } - _log.Debug("Background mode is changing to {0}", goingIntoBackground); - _diagnosticDisabler?.SetDisabled(goingIntoBackground); - if (goingIntoBackground) - { - if (!Config.EnableBackgroundUpdating) - { - _log.Debug("Background updating is disabled"); - await _connectionManager.SetDataSourceConstructor(null, false); - // Normally the data source status is updated by ConnectionManager and/or by the - // data source itself, but in this particular case neither of those are involved, - // so we need to explicitly set the state to BackgroundDisabled. - _dataSourceUpdateSink.UpdateStatus(DataSourceState.BackgroundDisabled, null); - return; - } - _log.Debug("Background updating is enabled, starting polling processor"); - } - await _connectionManager.SetDataSourceConstructor( - MakeDataSourceConstructor(User, goingIntoBackground), - false // don't reset initialized state because the user is still the same - ); - } + internal void OnBackgroundModeChanged(object sender, BackgroundModeChangedEventArgs args) => + _connectionManager.SetInBackground(args.IsInBackground); // Returns our configured event processor (which might be the null implementation, if configured // with NoEvents)-- or, a stub if we have been explicitly put offline. This way, during times // when the application does not want any network activity, we won't bother buffering events. internal IEventProcessor EventProcessorIfEnabled() => Offline ? ComponentsImpl.NullEventProcessor.Instance : _eventProcessor; - - internal Func MakeDataSourceConstructor(User user, bool background) - { - return () => _dataSourceFactory.CreateDataSource( - _context, - _dataSourceUpdateSink, - user, - background - ); - } } } From 4e57c2ab7748a3b48d667fd3e6229f20f7b4fe42 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 19 Nov 2021 13:58:13 -0800 Subject: [PATCH 387/499] fix init logic, add tests --- src/LaunchDarkly.ClientSdk/LdClient.cs | 5 ++- .../LdClientDiagnosticEventTest.cs | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index bb115148..8eb11efb 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -177,10 +177,11 @@ public sealed class LdClient : ILdClient _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); var isConnected = _connectivityStateManager.IsConnected; + diagnosticDisabler?.SetDisabled(!isConnected || configuration.Offline); + _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) .CreateEventProcessor(_context); _eventProcessor.SetOffline(configuration.Offline || !isConnected); - diagnosticDisabler?.SetDisabled(!isConnected && !configuration.Offline); _connectionManager = new ConnectionManager( _context, @@ -215,7 +216,7 @@ public sealed class LdClient : ILdClient User = user }); } - + _backgroundModeManager = _config.BackgroundModeManager ?? new DefaultBackgroundModeManager(); _backgroundModeManager.BackgroundModeChanged += OnBackgroundModeChanged; } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs index 9c878927..5cfb3d99 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -116,6 +116,45 @@ public void DiagnosticPeriodicEventsAreSent() } } + [Fact] + public void DiagnosticEventsAreNotSentWhenConfiguredOffline() + { + var config = BasicConfig() + .Offline(true) + .Events(Components.SendEvents() + .EventSender(_testEventSender) + .DiagnosticRecordingIntervalNoMinimum(TimeSpan.FromMilliseconds(50))) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + _testEventSender.RequireNoPayloadSent(TimeSpan.FromMilliseconds(100)); + + client.SetOffline(false, TimeSpan.FromMilliseconds(100)); + + _testEventSender.RequirePayload(); + } + } + + [Fact] + public void DiagnosticEventsAreNotSentWhenNetworkIsUnavailable() + { + var connectivityStateManager = new MockConnectivityStateManager(false); + var config = BasicConfig() + .ConnectivityStateManager(connectivityStateManager) + .Events(Components.SendEvents() + .EventSender(_testEventSender) + .DiagnosticRecordingIntervalNoMinimum(TimeSpan.FromMilliseconds(50))) + .Build(); + using (var client = TestUtil.CreateClient(config, BasicUser)) + { + _testEventSender.RequireNoPayloadSent(TimeSpan.FromMilliseconds(100)); + + connectivityStateManager.Connect(true); + + _testEventSender.RequirePayload(); + } + } + [Fact] public void DiagnosticPeriodicEventsAreNotSentWhenInBackground() { From 17a6e68b7bdcdf4f69c0b505438157e4830ea62d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 6 Jan 2022 14:00:28 -0800 Subject: [PATCH 388/499] remove prerelease notice from readme for GA release --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 1ed10f75..34e4ede7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # LaunchDarkly Client-Side SDK for .NET -## This is a prerelease branch - -The `master` branch currently contains prerelease code for development of version 2.0.0 of the SDK. For the source code of the latest version 1.x release, see the [`1.x` branch](https://github.com/launchdarkly/dotnet-client-sdk/tree/1.x). - [![NuGet](https://img.shields.io/nuget/v/LaunchDarkly.ClientSdk.svg?style=flat-square)](https://www.nuget.org/packages/LaunchDarkly.ClientSdk/) [![CircleCI](https://circleci.com/gh/launchdarkly/dotnet-client-sdk.svg?style=shield)](https://circleci.com/gh/launchdarkly/dotnet-client-sdk) [![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/dotnet-client-sdk) From 92edf526f769e6ef6015b77ab53017ef29d6be18 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 2 Feb 2022 15:26:18 -0800 Subject: [PATCH 389/499] update CommonSdk & InternalSdk to latest releases + delete obsolete project file --- .../LaunchDarkly.ClientSdk.csproj | 4 +-- .../LaunchDarkly.XamarinSdk.Tests.csproj | 27 ------------------- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ab092631..e980e17d 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,9 +40,9 @@ - + - + diff --git a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj b/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj deleted file mode 100644 index 851a50c5..00000000 --- a/tests/LaunchDarkly.XamarinSdk.Tests/LaunchDarkly.XamarinSdk.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp2.1 - LaunchDarkly.XamarinSdk.Tests - - - - - - - - - - - - - - - - - - - - - - From bb9ffa0ae2669115de84ed35875e428659e1ef67 Mon Sep 17 00:00:00 2001 From: Gavin Whelan Date: Mon, 7 Feb 2022 11:33:29 -0600 Subject: [PATCH 390/499] Use CircleCI macOS Gen2 resource class. (#152) --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index c7d10dc3..87adf643 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,7 @@ jobs: test-android: macos: xcode: "12.4.0" + resource_class: macos.x86.medium.gen2 environment: TERM: dumb @@ -98,6 +99,7 @@ jobs: test-ios: macos: xcode: "12.4.0" + resource_class: macos.x86.medium.gen2 steps: - checkout From 80c5324d3254d4298b1194f60dd9db61e07b9231 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 8 Feb 2022 14:20:19 -0800 Subject: [PATCH 391/499] fix unset timestamp in alias events --- src/LaunchDarkly.ClientSdk/LdClient.cs | 1 + .../LdClientEventTests.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 8eb11efb..2d2fc08a 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -645,6 +645,7 @@ public void Alias(User user, User previousUser) } EventProcessorIfEnabled().RecordAliasEvent(new EventProcessorTypes.AliasEvent { + Timestamp = UnixMillisecondTime.Now, User = user, PreviousUser = previousUser }); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index d9d1319b..1211b10a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -52,6 +52,7 @@ public void TrackSendsCustomEvent() Assert.Equal(user.Key, ce.User.Key); Assert.Equal(LdValue.Null, ce.Data); Assert.Null(ce.MetricValue); + Assert.NotEqual(0, ce.Timestamp.Value); }); } } @@ -71,6 +72,7 @@ public void TrackWithDataSendsCustomEvent() Assert.Equal(user.Key, ce.User.Key); Assert.Equal(data, ce.Data); Assert.Null(ce.MetricValue); + Assert.NotEqual(0, ce.Timestamp.Value); }); } } @@ -91,6 +93,7 @@ public void TrackWithMetricValueSendsCustomEvent() Assert.Equal(user.Key, ce.User.Key); Assert.Equal(data, ce.Data); Assert.Equal(metricValue, ce.MetricValue); + Assert.NotEqual(0, ce.Timestamp.Value); }); } } @@ -110,6 +113,7 @@ public void AliasSendsAliasEvent() AliasEvent ae = Assert.IsType(e); Assert.Equal(user, ae.User); Assert.Equal(oldUser, ae.PreviousUser); + Assert.NotEqual(0, ae.Timestamp.Value); }); } } @@ -132,6 +136,7 @@ public void IdentifySendsAliasEventFromAnonUserToNonAnonUserIfNotOptedOut() AliasEvent ae = Assert.IsType(e); Assert.Equal(newUser, ae.User); Assert.Equal(actualOldUser, ae.PreviousUser); + Assert.NotEqual(0, ae.Timestamp.Value); }); } } @@ -200,6 +205,7 @@ public void VariationSendsFeatureEventForValidFlag() Assert.True(fe.TrackEvents); Assert.Equal(UnixMillisecondTime.OfMillis(2000), fe.DebugEventsUntilDate); Assert.Null(fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -223,6 +229,7 @@ public void FeatureEventUsesFlagVersionIfProvided() Assert.Equal(1, fe.Variation); Assert.Equal(1500, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -246,6 +253,7 @@ public void VariationSendsFeatureEventForDefaultValue() Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -267,6 +275,7 @@ public void VariationSendsFeatureEventForUnknownFlag() Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -293,6 +302,7 @@ public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() Assert.Null(fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.Null(fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -319,6 +329,7 @@ public void VariationSendsFeatureEventWithTrackingAndReasonIfTrackReasonIsTrue() Assert.True(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.OffReason, fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -347,6 +358,7 @@ public void VariationDetailSendsFeatureEventWithReasonForValidFlag() Assert.True(fe.TrackEvents); Assert.Equal(UnixMillisecondTime.OfMillis(2000), fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.OffReason, fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -372,6 +384,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -401,6 +414,7 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni Assert.False(fe.TrackEvents); Assert.Null(fe.DebugEventsUntilDate); Assert.Equal(expectedReason, fe.Reason); + Assert.NotEqual(0, fe.Timestamp.Value); }); } } @@ -409,6 +423,7 @@ private void CheckIdentifyEvent(object e, User u) { IdentifyEvent ie = Assert.IsType(e); Assert.Equal(u.Key, ie.User.Key); + Assert.NotEqual(0, ie.Timestamp.Value); } } } From 1915f43bcb955e2fc78a593f8149351239d47fb0 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 2 May 2022 14:35:01 -0700 Subject: [PATCH 392/499] contract test service implementation (#154) --- .circleci/config.yml | 25 +- .gitignore | 1 + CONTRIBUTING.md | 6 + Makefile | 31 ++ contract-tests/README.md | 9 + contract-tests/Representations.cs | 123 ++++++++ contract-tests/SdkClientEntity.cs | 292 ++++++++++++++++++ contract-tests/TestService.cs | 134 ++++++++ contract-tests/TestService.csproj | 35 +++ contract-tests/TestService.sln | 31 ++ .../LaunchDarkly.ClientSdk.Tests.csproj | 2 + 11 files changed, 687 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 contract-tests/README.md create mode 100644 contract-tests/Representations.cs create mode 100644 contract-tests/SdkClientEntity.cs create mode 100644 contract-tests/TestService.cs create mode 100644 contract-tests/TestService.csproj create mode 100644 contract-tests/TestService.sln diff --git a/.circleci/config.yml b/.circleci/config.yml index 87adf643..65f69fa6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,15 +11,36 @@ workflows: jobs: test-netstandard2.0: docker: - - image: mcr.microsoft.com/dotnet/sdk:5.0-focal + - image: ldcircleci/dotnet5-release:1 + # This image is based on mcr.microsoft.com/dotnet/sdk:5.0-focal but is in a + # slightly better state for us to install the make tool (apt-get update has + # already been done). See: https://github.com/launchdarkly/sdks-ci-docker environment: ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests + TEST_HARNESS_PARAMS: -junit /tmp/circle-reports/contract-tests-junit.xml + TESTFRAMEWORK: net5.0 steps: - checkout + - run: apt install -y make + - run: mkdir -p /tmp/circle-reports - run: dotnet restore src/LaunchDarkly.ClientSdk - run: dotnet build src/LaunchDarkly.ClientSdk -f netstandard2.0 - run: dotnet restore tests/LaunchDarkly.ClientSdk.Tests - - run: dotnet test -v=normal tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj + - run: + name: run unit tests + command: | + dotnet test -v=normal \ + --logger:"junit;LogFilePath=/tmp/circle-reports/unit-tests.xml" \ + tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj + + - run: make build-contract-tests + - run: + command: make start-contract-test-service + background: true + - run: make run-contract-tests + + - store_test_results: + path: /tmp/circle-reports test-android: macos: diff --git a/.gitignore b/.gitignore index 43051c3b..25fdc901 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ Thumbs.db *.snk docs/build/ +launchSettings.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bfebdfd..11f6447a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,12 @@ You can run the mobile test projects from Visual Studio (the iOS tests require M Note that the mobile unit tests currently do not cover background-mode behavior or connectivity detection. +To run the SDK contract test suite, in Linux or MacOS (see [`contract-tests/README.md`](./contract-tests/README.md)): + +```bash +make contract-tests +``` + ### Packaging/releasing Releases are done through LaunchDarkly's standard project releaser tool. The scripts in `.ldrelease` implement most of this process, because unlike our other .NET projects which can be built with the .NET 5 SDK in a Linux container, this one currently must be built on a MacOS host in CircleCI. Do not modify these scripts unless you are very sure what you're doing. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..34e4d19e --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ + +build: + dotnet build + +test: + dotnet test + +clean: + dotnet clean + +TEMP_TEST_OUTPUT=/tmp/sdk-contract-test-service.log +BUILDFRAMEWORKS ?= netcoreapp2.1 +TESTFRAMEWORK ?= netcoreapp2.1 + +build-contract-tests: + @cd contract-tests && dotnet build TestService.csproj + +start-contract-test-service: + @cd contract-tests && dotnet bin/Debug/${TESTFRAMEWORK}/ContractTestService.dll + +start-contract-test-service-bg: + @echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)" + @make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 & + +run-contract-tests: + @curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh \ + | VERSION=v1 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh + +contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests + +.PHONY: build test clean build-contract-tests start-contract-test-service run-contract-tests contract-tests diff --git a/contract-tests/README.md b/contract-tests/README.md new file mode 100644 index 00000000..03caa44c --- /dev/null +++ b/contract-tests/README.md @@ -0,0 +1,9 @@ +# SDK contract test service + +This directory contains an implementation of the cross-platform SDK testing protocol defined by https://github.com/launchdarkly/sdk-test-harness. See that project's `README` for details of this protocol, and the kinds of SDK capabilities that are relevant to the contract tests. This code should not need to be updated unless the SDK has added or removed such capabilities. + +To run these tests locally, run `make contract-tests` from the SDK project root directory. This downloads the correct version of the test harness tool automatically. + +Or, to test against an in-progress local version of the test harness, run `make start-contract-test-service` from the SDK project root directory; then, in the root directory of the `sdk-test-harness` project, build the test harness and run it from the command line. + +Currently, the project does _not_ automatically detect the available target frameworks. It will default to building and running for .NET Core 2.1. To use a different target framework, set the environment variable `TESTFRAMEWORK` to the name of the application runtime framework (such as `netcoreapp3.1`), and set the environment variable `BUILDFRAMEWORKS` (note the S at the end) to the target framework that the SDK should be built for (which may or may not be the same). diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs new file mode 100644 index 00000000..8091295d --- /dev/null +++ b/contract-tests/Representations.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using LaunchDarkly.Sdk; + +namespace TestService +{ + public class Status + { + public string Name { get; set; } + public string[] Capabilities { get; set; } + public string ClientVersion { get; set; } + } + + public class CreateInstanceParams + { + public SdkConfigParams Configuration { get; set; } + public string Tag { get; set; } + } + + public class SdkConfigParams + { + public string Credential { get; set; } + public long? StartWaitTimeMs { get; set; } + public bool InitCanFail { get; set; } + public SdkConfigStreamParams Streaming { get; set; } + public SdkConfigPollingParams Polling { get; set; } + public SdkConfigEventParams Events { get; set; } + public SdkConfigServiceEndpointsParams ServiceEndpoints { get; set; } + public SdkClientSideParams ClientSide { get; set; } + } + + public class SdkConfigStreamParams + { + public Uri BaseUri { get; set; } + public long? InitialRetryDelayMs { get; set; } + } + + public class SdkConfigPollingParams + { + public Uri BaseUri { get; set; } + public long? PollIntervalMs { get; set; } + } + + public class SdkConfigEventParams + { + public Uri BaseUri { get; set; } + public bool AllAttributesPrivate { get; set; } + public int? Capacity { get; set; } + public bool EnableDiagnostics { get; set; } + public string[] GlobalPrivateAttributes { get; set; } + public long? FlushIntervalMs { get; set; } + public bool InlineUsers { get; set; } + } + + public class SdkConfigServiceEndpointsParams + { + public Uri Streaming { get; set; } + public Uri Polling { get; set; } + public Uri Events { get; set; } + } + + public class SdkClientSideParams + { + public bool? AutoAliasingOptOut { get; set; } + public bool? EvaluationReasons { get; set; } + public User InitialUser { get; set; } + public bool? UseReport { get; set; } + } + + public class CommandParams + { + public string Command { get; set; } + public EvaluateFlagParams Evaluate { get; set; } + public EvaluateAllFlagsParams EvaluateAll { get; set; } + public IdentifyEventParams IdentifyEvent { get; set; } + public CustomEventParams CustomEvent { get; set; } + public AliasEventParams AliasEvent { get; set; } + } + + public class EvaluateFlagParams + { + public string FlagKey { get; set; } + public String ValueType { get; set; } + public LdValue Value { get; set; } + public LdValue DefaultValue { get; set; } + public bool Detail { get; set; } + } + + public class EvaluateFlagResponse + { + public LdValue Value { get; set; } + public int? VariationIndex { get; set; } + public EvaluationReason? Reason { get; set; } + } + + public class EvaluateAllFlagsParams + { + } + + public class EvaluateAllFlagsResponse + { + public IDictionary State { get; set; } + } + + public class IdentifyEventParams + { + public User User { get; set; } + } + + public class CustomEventParams + { + public string EventKey { get; set; } + public LdValue Data { get; set; } + public bool OmitNullData { get; set; } + public double? MetricValue { get; set; } + } + + public class AliasEventParams + { + public User User { get; set; } + public User PreviousUser { get; set; } + } +} diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs new file mode 100644 index 00000000..f5fca115 --- /dev/null +++ b/contract-tests/SdkClientEntity.cs @@ -0,0 +1,292 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk; +using LaunchDarkly.Sdk.Client; + +namespace TestService +{ + public class SdkClientEntity + { + private static HttpClient _httpClient = new HttpClient(); + + private readonly LdClient _client; + private readonly Logger _log; + private readonly bool _evaluationReasons; + + public SdkClientEntity( + SdkConfigParams sdkParams, + ILogAdapter logAdapter, + string tag + ) + { + if (sdkParams.ClientSide == null) + { + throw new Exception("test harness did not provide clientSide configuration"); + } + + _log = logAdapter.Logger(tag); + Configuration config = BuildSdkConfig(sdkParams, logAdapter, tag); + + _evaluationReasons = sdkParams.ClientSide.EvaluationReasons ?? false; + + TimeSpan startWaitTime = TimeSpan.FromSeconds(5); + if (sdkParams.StartWaitTimeMs.HasValue) + { + startWaitTime = TimeSpan.FromMilliseconds(sdkParams.StartWaitTimeMs.Value); + } + + _client = LdClient.Init(config, sdkParams.ClientSide.InitialUser, startWaitTime); + if (!_client.Initialized && !sdkParams.InitCanFail) + { + _client.Dispose(); + throw new Exception("Client initialization failed"); + } + } + + public void Close() + { + _client.Dispose(); + _log.Info("Test ended"); + } + + public async Task<(bool, object)> DoCommand(CommandParams command) + { + _log.Info("Test harness sent command: {0}", command.Command); + switch (command.Command) + { + case "evaluate": + return (true, DoEvaluate(command.Evaluate)); + + case "evaluateAll": + return (true, DoEvaluateAll(command.EvaluateAll)); + + case "identifyEvent": + await _client.IdentifyAsync(command.IdentifyEvent.User); + return (true, null); + + case "customEvent": + var custom = command.CustomEvent; + if (custom.MetricValue.HasValue) + { + _client.Track(custom.EventKey, custom.Data, custom.MetricValue.Value); + } + else if (custom.OmitNullData && custom.Data.IsNull) + { + _client.Track(custom.EventKey); + } + else + { + _client.Track(custom.EventKey, custom.Data); + } + return (true, null); + + case "aliasEvent": + _client.Alias(command.AliasEvent.User, command.AliasEvent.PreviousUser); + return (true, null); + + case "flushEvents": + _client.Flush(); + return (true, null); + + default: + return (false, null); + } + } + + private object DoEvaluate(EvaluateFlagParams p) + { + var resp = new EvaluateFlagResponse(); + switch (p.ValueType) + { + case "bool": + if (p.Detail) + { + var detail = _client.BoolVariationDetail(p.FlagKey, p.DefaultValue.AsBool); + resp.Value = LdValue.Of(detail.Value); + resp.VariationIndex = detail.VariationIndex; + resp.Reason = detail.Reason; + } + else + { + resp.Value = LdValue.Of(_client.BoolVariation(p.FlagKey, p.DefaultValue.AsBool)); + } + break; + + case "int": + if (p.Detail) + { + var detail = _client.IntVariationDetail(p.FlagKey, p.DefaultValue.AsInt); + resp.Value = LdValue.Of(detail.Value); + resp.VariationIndex = detail.VariationIndex; + resp.Reason = detail.Reason; + } + else + { + resp.Value = LdValue.Of(_client.IntVariation(p.FlagKey, p.DefaultValue.AsInt)); + } + break; + + case "double": + if (p.Detail) + { + var detail = _client.DoubleVariationDetail(p.FlagKey, p.DefaultValue.AsDouble); + resp.Value = LdValue.Of(detail.Value); + resp.VariationIndex = detail.VariationIndex; + resp.Reason = detail.Reason; + } + else + { + resp.Value = LdValue.Of(_client.DoubleVariation(p.FlagKey, p.DefaultValue.AsDouble)); + } + break; + + case "string": + if (p.Detail) + { + var detail = _client.StringVariationDetail(p.FlagKey, p.DefaultValue.AsString); + resp.Value = LdValue.Of(detail.Value); + resp.VariationIndex = detail.VariationIndex; + resp.Reason = detail.Reason; + } + else + { + resp.Value = LdValue.Of(_client.StringVariation(p.FlagKey, p.DefaultValue.AsString)); + } + break; + + default: + if (p.Detail) + { + var detail = _client.JsonVariationDetail(p.FlagKey, p.DefaultValue); + resp.Value = detail.Value; + resp.VariationIndex = detail.VariationIndex; + resp.Reason = detail.Reason; + } + else + { + resp.Value = _client.JsonVariation(p.FlagKey, p.DefaultValue); + } + break; + } + + if (p.Detail && !_evaluationReasons && resp.Reason.HasValue && resp.Reason.Value.Kind == EvaluationReasonKind.Off) + { + resp.Reason = null; + } + + return resp; + } + + private object DoEvaluateAll(EvaluateAllFlagsParams p) + { + return new EvaluateAllFlagsResponse + { + State = _client.AllFlags() + }; + } + + private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) + { + var builder = Configuration.Builder(sdkParams.Credential); + + builder.Logging(Components.Logging(logAdapter).BaseLoggerName(tag + ".SDK")); + + var endpoints = Components.ServiceEndpoints(); + builder.ServiceEndpoints(endpoints); + if (sdkParams.ServiceEndpoints != null) + { + if (sdkParams.ServiceEndpoints.Streaming != null) + { + endpoints.Streaming(sdkParams.ServiceEndpoints.Streaming); + } + if (sdkParams.ServiceEndpoints.Polling != null) + { + endpoints.Polling(sdkParams.ServiceEndpoints.Polling); + } + if (sdkParams.ServiceEndpoints.Events != null) + { + endpoints.Events(sdkParams.ServiceEndpoints.Events); + } + } + + var streamingParams = sdkParams.Streaming; + var pollingParams = sdkParams.Polling; + if (streamingParams != null) + { + endpoints.Streaming(streamingParams.BaseUri); + var dataSource = Components.StreamingDataSource(); + if (streamingParams.InitialRetryDelayMs.HasValue) + { + dataSource.InitialReconnectDelay(TimeSpan.FromMilliseconds(streamingParams.InitialRetryDelayMs.Value)); + } + if (pollingParams != null) + { + endpoints.Polling(pollingParams.BaseUri); + if (pollingParams.PollIntervalMs.HasValue) + { + dataSource.BackgroundPollInterval(TimeSpan.FromMilliseconds(pollingParams.PollIntervalMs.Value)); + } + } + builder.DataSource(dataSource); + } + else if (pollingParams != null) + { + endpoints.Polling(pollingParams.BaseUri); + var dataSource = Components.PollingDataSource(); + if (pollingParams.PollIntervalMs.HasValue) + { + dataSource.PollInterval(TimeSpan.FromMilliseconds(pollingParams.PollIntervalMs.Value)); + } + builder.DataSource(dataSource); + } + + var eventParams = sdkParams.Events; + if (eventParams == null) + { + builder.Events(Components.NoEvents); + } + else + { + endpoints.Events(eventParams.BaseUri); + var events = Components.SendEvents() + .AllAttributesPrivate(eventParams.AllAttributesPrivate) + .InlineUsersInEvents(eventParams.InlineUsers); + if (eventParams.Capacity.HasValue && eventParams.Capacity.Value > 0) + { + events.Capacity(eventParams.Capacity.Value); + } + if (eventParams.FlushIntervalMs.HasValue && eventParams.FlushIntervalMs.Value > 0) + { + events.FlushInterval(TimeSpan.FromMilliseconds(eventParams.FlushIntervalMs.Value)); + } + if (eventParams.GlobalPrivateAttributes != null) + { + events.PrivateAttributeNames(eventParams.GlobalPrivateAttributes); + } + builder.Events(events); + builder.DiagnosticOptOut(!eventParams.EnableDiagnostics); + } + + var http = Components.HttpConfiguration(); + if (sdkParams.ClientSide.UseReport.HasValue) + { + http.UseReport(sdkParams.ClientSide.UseReport.Value); + } + builder.Http(http); + + if (sdkParams.ClientSide.AutoAliasingOptOut.HasValue) + { + builder.AutoAliasingOptOut(sdkParams.ClientSide.AutoAliasingOptOut.Value); + } + + if (sdkParams.ClientSide.EvaluationReasons.HasValue) + { + builder.EvaluationReasons(sdkParams.ClientSide.EvaluationReasons.Value); + } + + return builder.Build(); + } + } +} diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs new file mode 100644 index 00000000..f285ea03 --- /dev/null +++ b/contract-tests/TestService.cs @@ -0,0 +1,134 @@ +using System.Collections.Concurrent; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client; +using LaunchDarkly.TestHelpers.HttpTest; + +namespace TestService +{ + public class Program + { + const int Port = 8000; + + public static void Main(string[] args) + { + var quitSignal = new EventWaitHandle(false, EventResetMode.AutoReset); + + var app = new Webapp(quitSignal); + var server = HttpServer.Start(Port, app.Handler); + server.Recorder.Enabled = false; + + System.Console.WriteLine("Listening on port {0}", Port); + + quitSignal.WaitOne(); + server.Dispose(); + } + } + + public class Webapp + { + private static readonly string[] Capabilities = { + "client-side", + "mobile", + "service-endpoints", + "singleton", + "strongly-typed" + }; + + public readonly Handler Handler; + + private readonly string _version; + private readonly ILogAdapter _logging = Logs.ToConsole; + private readonly ConcurrentDictionary _clients = + new ConcurrentDictionary(); + private readonly EventWaitHandle _quitSignal; + private volatile int _lastClientId = 0; + + public Webapp(EventWaitHandle quitSignal) + { + _quitSignal = quitSignal; + + _version = LdClient.Version.ToString(); + + var service = new SimpleJsonService(); + Handler = service.Handler; + + // Tell the service about the custom JSON conversions for LaunchDarkly SDK types like User + service.SetJsonConverters(LaunchDarkly.Sdk.Json.LdJsonNet.Converter); + + service.Route(HttpMethod.Get, "/", GetStatus); + service.Route(HttpMethod.Delete, "/", ForceQuit); + service.Route(HttpMethod.Post, "/", PostCreateClient); + service.Route(HttpMethod.Post, "/clients/(.*)", PostClientCommand); + service.Route(HttpMethod.Delete, "/clients/(.*)", DeleteClient); + } + + SimpleResponse GetStatus(IRequestContext context) => + SimpleResponse.Of(200, new Status + { + Name = "dotnet-client-sdk", + Capabilities = Capabilities, + ClientVersion = _version + }); + + SimpleResponse ForceQuit(IRequestContext context) + { + _logging.Logger("").Info("Test harness has told us to exit"); + + // The web server won't send the response till we return, so we'll defer the actual shutdown + _ = Task.Run(async () => + { + await Task.Delay(100); + _quitSignal.Set(); + }); + + return SimpleResponse.Of(204); + } + + SimpleResponse PostCreateClient(IRequestContext context, CreateInstanceParams createParams) + { + var client = new SdkClientEntity(createParams.Configuration, _logging, createParams.Tag); + + var id = Interlocked.Increment(ref _lastClientId); + var clientId = id.ToString(); + _clients[clientId] = client; + + var resourceUrl = "/clients/" + clientId; + return SimpleResponse.Of(201).WithHeader("Location", resourceUrl); + } + + async Task> PostClientCommand(IRequestContext context, CommandParams command) + { + var id = context.GetPathParam(0); + if (!_clients.TryGetValue(id, out var client)) + { + return SimpleResponse.Of(404, null); + } + + var result = await client.DoCommand(command); + if (result.Item1) + { + return SimpleResponse.Of(202, result.Item2); + } + else + { + return SimpleResponse.Of(400, null); + } + } + + SimpleResponse DeleteClient(IRequestContext context) + { + var id = context.GetPathParam(0); + if (!_clients.TryGetValue(id, out var client)) + { + return SimpleResponse.Of(400); + } + client.Close(); + _clients.TryRemove(id, out _); + + return SimpleResponse.Of(204); + } + } +} diff --git a/contract-tests/TestService.csproj b/contract-tests/TestService.csproj new file mode 100644 index 00000000..132a889b --- /dev/null +++ b/contract-tests/TestService.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp2.1 + $(TESTFRAMEWORK) + portable + ContractTestService + Exe + ContractTestService + false + false + false + false + false + false + + + + + + + + + + + + + + + true + PreserveNewest + + + + diff --git a/contract-tests/TestService.sln b/contract-tests/TestService.sln new file mode 100644 index 00000000..7826ab8f --- /dev/null +++ b/contract-tests/TestService.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.810.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk", "..\src\LaunchDarkly.ClientSdk\LaunchDarkly.ClientSdk.csproj", "{6AD8F034-7096-4420-953F-68F529F2B300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestService", "TestService.csproj", "{D69B2FDF-76D3-475B-971E-64120FD84401}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6AD8F034-7096-4420-953F-68F529F2B300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AD8F034-7096-4420-953F-68F529F2B300}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AD8F034-7096-4420-953F-68F529F2B300}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AD8F034-7096-4420-953F-68F529F2B300}.Release|Any CPU.Build.0 = Release|Any CPU + {D69B2FDF-76D3-475B-971E-64120FD84401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D69B2FDF-76D3-475B-971E-64120FD84401}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D69B2FDF-76D3-475B-971E-64120FD84401}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D69B2FDF-76D3-475B-971E-64120FD84401}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {934189FE-533F-429D-87E8-A2311F8730A6} + EndGlobalSection +EndGlobal diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index 657f6b1c..b2330e85 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -8,6 +8,7 @@ + @@ -17,6 +18,7 @@ + From 5220969657b8daa5110810272a4da47caea55cd1 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 16 May 2022 14:16:59 -0700 Subject: [PATCH 393/499] Change master to main. (#155) --- .github/pull_request_template.md | 2 +- .ldrelease/config.yml | 2 +- docs-src/index.md | 2 +- docs-src/namespaces/LaunchDarkly.Sdk.Json.md | 2 +- src/LaunchDarkly.ClientSdk/DataModel.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 19806760..fc89ce0f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ **Requirements** - [ ] I have added test coverage for new or changed functionality -- [ ] I have followed the repository's [pull request submission guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests) +- [ ] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [ ] I have validated my changes against all supported platform versions **Related issues** diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml index 9ea61d76..e6c77181 100644 --- a/.ldrelease/config.yml +++ b/.ldrelease/config.yml @@ -5,7 +5,7 @@ repo: private: dotnet-client-sdk-private branches: - - name: master + - name: main description: 2.x - name: 1.x diff --git a/docs-src/index.md b/docs-src/index.md index 98d739ac..fd8ff8e0 100644 --- a/docs-src/index.md +++ b/docs-src/index.md @@ -1,4 +1,4 @@ This site contains the full API reference for the [`LaunchDarkly.ClientSdk`](https://www.nuget.org/packages/LaunchDarkly.ClientSdk) package, as well as the `LaunchDarkly.CommonSdk` package that is included automatically as a dependency of the SDK. -For source code, see the [GitHub repository](https://github.com/launchdarkly/dotnet-client-sdk). The [developer notes](https://github.com/launchdarkly/dotnet-client-sdk/blob/master/CONTRIBUTING.md) there include links to other repositories used in the SDK. +For source code, see the [GitHub repository](https://github.com/launchdarkly/dotnet-client-sdk). The [developer notes](https://github.com/launchdarkly/dotnet-client-sdk/blob/main/CONTRIBUTING.md) there include links to other repositories used in the SDK. diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Json.md b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md index 05828f78..54961abb 100644 --- a/docs-src/namespaces/LaunchDarkly.Sdk.Json.md +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md @@ -8,4 +8,4 @@ Any LaunchDarkly SDK type that has the marker interface methods and to convert to or from a JSON-encoded string. * You may use the lower-level `LaunchDarkly.JsonStream` API (https://github.com/launchdarkly/dotnet-jsonstream) in conjunction with the converters in . -Earlier versions of the LaunchDarkly SDKs used `Newtonsoft.Json` for JSON serialization, but current versions have no such third-party dependency. Therefore, these types will not work correctly with the reflection-based `JsonConvert` methods in `Newtonsoft.Json` without some extra logic. There is an add-on package, [`LaunchDarkly.CommonSdk.JsonNet`](https://github.com/launchdarkly/dotnet-sdk-common/tree/master/src/LaunchDarkly.CommonSdk.JsonNet), that provides an adapter to make this work; alternatively, you can call and put the resulting JSON output into a `Newtonsoft.Json.Linq.JRaw` value. +Earlier versions of the LaunchDarkly SDKs used `Newtonsoft.Json` for JSON serialization, but current versions have no such third-party dependency. Therefore, these types will not work correctly with the reflection-based `JsonConvert` methods in `Newtonsoft.Json` without some extra logic. There is an add-on package, [`LaunchDarkly.CommonSdk.JsonNet`](https://github.com/launchdarkly/dotnet-sdk-common/tree/main/src/LaunchDarkly.CommonSdk.JsonNet), that provides an adapter to make this work; alternatively, you can call and put the resulting JSON output into a `Newtonsoft.Json.Linq.JRaw` value. diff --git a/src/LaunchDarkly.ClientSdk/DataModel.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs index 46404add..14dcf08d 100644 --- a/src/LaunchDarkly.ClientSdk/DataModel.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -103,7 +103,7 @@ public static FeatureFlag ReadJsonValue(ref JReader reader) { // The use of multiple == tests instead of switch allows for a slight optimization on // some platforms where it wouldn't always need to allocate a string for or.Name. See: - // https://github.com/launchdarkly/dotnet-jsonstream/blob/master/src/LaunchDarkly.JsonStream/PropertyNameToken.cs + // https://github.com/launchdarkly/dotnet-jsonstream/blob/main/src/LaunchDarkly.JsonStream/PropertyNameToken.cs var name = or.Name; if (name == "value") { From 1dd6444e847a16ddbea0d00bc6887501ca2c2b43 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 19 May 2022 09:56:48 -0700 Subject: [PATCH 394/499] remove obsolete Alias, AutoAliasingOptOut, InlineUsersInEvents --- src/LaunchDarkly.ClientSdk/Configuration.cs | 15 ---- .../ConfigurationBuilder.cs | 24 ------ .../Integrations/EventProcessorBuilder.cs | 17 ---- .../Interfaces/EventProcessorTypes.cs | 22 ----- .../Interfaces/IEventProcessor.cs | 5 -- .../Interfaces/ILdClient.cs | 13 --- .../Internal/ComponentsImpl.cs | 2 - .../Internal/Events/ClientDiagnosticStore.cs | 1 - .../Events/DefaultEventProcessorWrapper.cs | 14 --- src/LaunchDarkly.ClientSdk/LdClient.cs | 28 ------ .../Resources/Resource.designer.cs | 2 +- .../ConfigurationTest.cs | 8 -- .../ILdClientExtensionsTest.cs | 3 - .../Integrations/EventProcessorBuilderTest.cs | 8 -- .../LdClientDiagnosticEventTest.cs | 5 +- .../LdClientEventTests.cs | 85 ------------------- .../MockComponents.cs | 3 - 17 files changed, 2 insertions(+), 253 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 1af7d4e6..281384e2 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -32,20 +32,6 @@ public sealed class Configuration internal IConnectivityStateManager ConnectivityStateManager { get; } internal IDeviceInfo DeviceInfo { get; } - /// - /// Whether to disable the automatic sending of an alias event when the current user is changed - /// to a non-anonymous user and the previous user was anonymous. - /// - /// - /// By default, if you call or - /// with a non-anonymous user, and the current user - /// (previously specified either with one of those methods or when creating the ) - /// was anonymous, the SDK assumes the two users should be correlated and sends an analytics - /// event equivalent to calling . Setting - /// AutoAliasingOptOut to disables this behavior. - /// - public bool AutoAliasingOptOut { get; } - /// /// A factory object that creates an implementation of , which will /// receive feature flag data. @@ -166,7 +152,6 @@ public static ConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { - AutoAliasingOptOut = builder._autoAliasingOptOut; DataSourceFactory = builder._dataSourceFactory; DiagnosticOptOut = builder._diagnosticOptOut; EnableBackgroundUpdating = builder._enableBackgroundUpdating; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 45207384..65d91c32 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Net.Http; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; namespace LaunchDarkly.Sdk.Client @@ -33,7 +31,6 @@ public sealed class ConfigurationBuilder // will replace it with a platform-specific implementation. internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); - internal bool _autoAliasingOptOut = false; internal IDataSourceFactory _dataSourceFactory = null; internal bool _diagnosticOptOut = false; internal bool _enableBackgroundUpdating = true; @@ -58,7 +55,6 @@ internal ConfigurationBuilder(string mobileKey) internal ConfigurationBuilder(Configuration copyFrom) { - _autoAliasingOptOut = copyFrom.AutoAliasingOptOut; _dataSourceFactory = copyFrom.DataSourceFactory; _diagnosticOptOut = copyFrom.DiagnosticOptOut; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; @@ -82,26 +78,6 @@ public Configuration Build() return new Configuration(this); } - /// - /// Whether to disable the automatic sending of an alias event when the current user is changed - /// to a non-anonymous user and the previous user was anonymous. - /// - /// - /// By default, if you call or - /// with a non-anonymous user, and the current user - /// (previously specified either with one of those methods or when creating the ) - /// was anonymous, the SDK assumes the two users should be correlated and sends an analytics - /// event equivalent to calling . Setting - /// AutoAliasingOptOut to disables this behavior. - /// - /// true to disable automatic user aliasing - /// the same builder - public ConfigurationBuilder AutoAliasingOptOut(bool autoAliasingOptOut) - { - _autoAliasingOptOut = autoAliasingOptOut; - return this; - } - /// /// Sets the implementation of the component that receives feature flag data from LaunchDarkly, /// using a factory object. diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 4c32fd0e..f925a21e 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -65,7 +65,6 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory, IDiagnosticD internal int _capacity = DefaultCapacity; internal TimeSpan _diagnosticRecordingInterval = DefaultDiagnosticRecordingInterval; internal TimeSpan _flushInterval = DefaultFlushInterval; - internal bool _inlineUsersInEvents = false; internal HashSet _privateAttributes = new HashSet(); internal IEventSender _eventSender = null; // used in testing @@ -161,21 +160,6 @@ internal EventProcessorBuilder FlushIntervalNoMinimum(TimeSpan flushInterval) return this; } - /// - /// Sets whether to include full user details in every analytics event. - /// - /// - /// The default value is : events will only include the user key, except for one - /// "identify" event that provides the full details for the user. - /// - /// true if you want full user details in each event - /// the builder - public EventProcessorBuilder InlineUsersInEvents(bool inlineUsersInEvents) - { - _inlineUsersInEvents = inlineUsersInEvents; - return this; - } - /// /// Marks a set of attribute names as private. /// @@ -269,7 +253,6 @@ private EventsConfiguration MakeEventsConfiguration(LdClientContext context, boo EventsUri = baseUri.AddPath(StandardEndpoints.AnalyticsEventsPostRequestPath), DiagnosticRecordingInterval = _diagnosticRecordingInterval, DiagnosticUri = baseUri.AddPath(StandardEndpoints.DiagnosticEventsPostRequestPath), - InlineUsersInEvents = _inlineUsersInEvents, PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), RetryInterval = TimeSpan.FromSeconds(1) }; diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs index 8cfa1c5f..8f3a5ec7 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs @@ -121,27 +121,5 @@ public struct CustomEvent /// public double? MetricValue { get; set; } } - - /// - /// Parameters for . - /// - public struct AliasEvent - { - /// - /// Date/timestamp of the event. - /// - public UnixMillisecondTime Timestamp { get; set; } - - /// - /// Attributes of the user who generated the event. Some attributes may not be sent - /// to LaunchDarkly if they are private. - /// - public User User { get; set; } - - /// - /// Attributes of the previous user that should be considered equivalent to this user. - /// - public User PreviousUser { get; set; } - } } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs index c24e544c..0743a244 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs @@ -40,11 +40,6 @@ public interface IEventProcessor : IDisposable /// parameters for a custom event void RecordCustomEvent(EventProcessorTypes.CustomEvent e); - /// - /// Records an alias event. - /// - void RecordAliasEvent(EventProcessorTypes.AliasEvent e); - /// /// Puts the component into offline mode if appropriate. /// diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index 561b3554..d5c12506 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -346,19 +346,6 @@ public interface ILdClient : IDisposable /// a task that yields true if new flag values were obtained Task IdentifyAsync(User user); - /// - /// Associates two users for analytics purposes. - /// - /// - /// This can be helpful in the situation where a person is represented by multiple - /// LaunchDarkly users. This may happen, for example, when a person initially logs into - /// an application-- the person might be represented by an anonymous user prior to logging - /// in and a different user after logging in, as denoted by a different user key. - /// - /// the newly identified user - /// the previously identified user - void Alias(User user, User previousUser); - /// /// Tells the client that all pending analytics events should be delivered as soon as possible. /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index fa58f6f0..3e2921b9 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -42,8 +42,6 @@ public void Dispose() { } public void Flush() { } - public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) { } - public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) { } public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) { } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index 28228570..1bb455ef 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -47,7 +47,6 @@ internal void SetContext(LdClientContext context) private IEnumerable GetConfigProperties() { yield return LdValue.BuildObject() - .WithAutoAliasingOptOut(_config.AutoAliasingOptOut) .WithStartWaitTime(_startWaitTime) .Add("backgroundPollingDisabled", !_config.EnableBackgroundUpdating) .Add("evaluationReasonsRequested", _config.EvaluationReasons) diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs index 466ae9a3..38c1dd1e 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -50,20 +50,6 @@ public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) }); } - public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) - { - _eventProcessor.RecordAliasEvent(new EventTypes.AliasEvent - { - Timestamp = e.Timestamp, - Key = e.User.Key, - ContextKind = e.User.Anonymous ? EventTypes.ContextKind.AnonymousUser : - EventTypes.ContextKind.User, - PreviousKey = e.PreviousUser.Key, - PreviousContextKind = e.PreviousUser.Anonymous ? EventTypes.ContextKind.AnonymousUser : - EventTypes.ContextKind.User - }); - } - public void SetOffline(bool offline) => _eventProcessor.SetOffline(offline); diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 2d2fc08a..c96e2440 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -619,38 +619,10 @@ public async Task IdentifyAsync(User user) Timestamp = UnixMillisecondTime.Now, User = user }); - if (oldUser.Anonymous && !newUser.Anonymous && !_config.AutoAliasingOptOut) - { - EventProcessorIfEnabled().RecordAliasEvent(new EventProcessorTypes.AliasEvent - { - Timestamp = UnixMillisecondTime.Now, - User = user, - PreviousUser = oldUser - }); - } return await _connectionManager.SetUser(newUser); } - /// - public void Alias(User user, User previousUser) - { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (previousUser is null) - { - throw new ArgumentNullException(nameof(previousUser)); - } - EventProcessorIfEnabled().RecordAliasEvent(new EventProcessorTypes.AliasEvent - { - Timestamp = UnixMillisecondTime.Now, - User = user, - PreviousUser = previousUser - }); - } - /// /// Permanently shuts down the SDK client. /// diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index b11b4230..c3238a4a 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.4.160")] public partial class Resource { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index aac63f78..924dd83a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -31,14 +31,6 @@ public void BuilderSetsKey() Assert.Equal(mobileKey, config.MobileKey); } - [Fact] - public void AutoAliasingOptOut() - { - var prop = _tester.Property(c => c.AutoAliasingOptOut, (b, v) => b.AutoAliasingOptOut(v)); - prop.AssertDefault(false); - prop.AssertCanSet(true); - } - [Fact] public void DataSource() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index 97ea8498..6ea24bbc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -178,9 +178,6 @@ public void Track(string eventName, LdValue data) => public void Track(string eventName, LdValue data, double metricValue) => throw new System.NotImplementedException(); - - public void Alias(User user, User previousUser) => - throw new System.NotImplementedException(); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs index de5b8f81..1b5ed029 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs @@ -51,14 +51,6 @@ public void FlushInterval() prop.AssertSetIsChangedTo(TimeSpan.FromMilliseconds(-1), EventProcessorBuilder.DefaultFlushInterval); } - [Fact] - public void InlineUsersInEvents() - { - var prop = _tester.Property(b => b._inlineUsersInEvents, (b, v) => b.InlineUsersInEvents(v)); - prop.AssertDefault(false); - prop.AssertCanSet(true); - } - [Fact] public void PrivateAttributes() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs index 5cfb3d99..d01f09ec 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -240,14 +240,12 @@ public void CustomConfigForEvents() e => e.AllAttributesPrivate(true) .Capacity(333) .DiagnosticRecordingInterval(TimeSpan.FromMinutes(32)) - .FlushInterval(TimeSpan.FromMilliseconds(555)) - .InlineUsersInEvents(true), + .FlushInterval(TimeSpan.FromMilliseconds(555)), ExpectedConfigProps.Base() .Set("allAttributesPrivate", true) .Set("diagnosticRecordingIntervalMillis", TimeSpan.FromMinutes(32).TotalMilliseconds) .Set("eventsCapacity", 333) .Set("eventsFlushIntervalMillis", 555) - .Set("inlineUsersInEvents", true) ); } @@ -375,7 +373,6 @@ static class ExpectedConfigProps public static LdValue.ObjectBuilder Base() => LdValue.BuildObject() .Add("allAttributesPrivate", false) - .Add("autoAliasingOptOut", false) .Add("backgroundPollingDisabled", false) .Add("backgroundPollingIntervalMillis", Configuration.DefaultBackgroundPollInterval.TotalMilliseconds) .Add("customBaseURI", false) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 1211b10a..9b7c7eef 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -98,91 +98,6 @@ public void TrackWithMetricValueSendsCustomEvent() } } - [Fact] - public void AliasSendsAliasEvent() - { - User oldUser = User.Builder("anon-key").Anonymous(true).Build(); - User newUser = User.WithKey("real-key"); - - using (LdClient client = MakeClient(user)) - { - client.Alias(user, oldUser); - Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, user), - e => { - AliasEvent ae = Assert.IsType(e); - Assert.Equal(user, ae.User); - Assert.Equal(oldUser, ae.PreviousUser); - Assert.NotEqual(0, ae.Timestamp.Value); - }); - } - } - - [Fact] - public void IdentifySendsAliasEventFromAnonUserToNonAnonUserIfNotOptedOut() - { - User oldUser = User.Builder("anon-key").Anonymous(true).Build(); - User newUser = User.WithKey("real-key"); - - using (LdClient client = MakeClient(oldUser)) - { - User actualOldUser = client.User; // so we can get any automatic properties that the client added - client.Identify(newUser, TimeSpan.FromSeconds(1)); - - Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, actualOldUser), - e => CheckIdentifyEvent(e, newUser), - e => { - AliasEvent ae = Assert.IsType(e); - Assert.Equal(newUser, ae.User); - Assert.Equal(actualOldUser, ae.PreviousUser); - Assert.NotEqual(0, ae.Timestamp.Value); - }); - } - } - - [Fact] - public void IdentifyDoesNotSendAliasEventIfOptedOUt() - { - User oldUser = User.Builder("anon-key").Anonymous(true).Build(); - User newUser = User.WithKey("real-key"); - - var config = BasicConfig() - .Events(_factory) - .AutoAliasingOptOut(true) - .Build(); - - using (LdClient client = TestUtil.CreateClient(config, oldUser)) - { - User actualOldUser = client.User; // so we can get any automatic properties that the client added - client.Identify(newUser, TimeSpan.FromSeconds(1)); - - Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, actualOldUser), - e => CheckIdentifyEvent(e, newUser)); - } - } - - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public void IdentifyDoesNotSendAliasEventIfNewUserIsAnonymousOrOldUserIsNot( - bool oldAnon, bool newAnon) - { - User oldUser = User.Builder("old-key").Anonymous(oldAnon).Build(); - User newUser = User.Builder("new-key").Anonymous(newAnon).Build(); - - using (LdClient client = MakeClient(oldUser)) - { - client.Identify(newUser, TimeSpan.FromSeconds(1)); - - Assert.Collection(eventProcessor.Events, - e => CheckIdentifyEvent(e, oldUser), - e => CheckIdentifyEvent(e, newUser)); - } - } - [Fact] public void VariationSendsFeatureEventForValidFlag() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index e28522e7..10a7cdbe 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -220,9 +220,6 @@ public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) => public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) => Events.Add(e); - - public void RecordAliasEvent(EventProcessorTypes.AliasEvent e) => - Events.Add(e); } public class MockEventSender : IEventSender From a221a5fd71dfb9d248db4065501a9d3681e5c36b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 19 May 2022 12:39:13 -0700 Subject: [PATCH 395/499] update contract tests --- Makefile | 6 ++++++ contract-tests/Representations.cs | 9 --------- contract-tests/SdkClientEntity.cs | 12 +----------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 34e4d19e..566872bf 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,12 @@ TEMP_TEST_OUTPUT=/tmp/sdk-contract-test-service.log BUILDFRAMEWORKS ?= netcoreapp2.1 TESTFRAMEWORK ?= netcoreapp2.1 +# temporary skips for contract tests that can't pass till more U2C work is done +TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \ + -skip events/alias \ + -skip events/custom \ + -skip events/user/inlineUsers=true + build-contract-tests: @cd contract-tests && dotnet build TestService.csproj diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index 8091295d..8ddcd64b 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -49,7 +49,6 @@ public class SdkConfigEventParams public bool EnableDiagnostics { get; set; } public string[] GlobalPrivateAttributes { get; set; } public long? FlushIntervalMs { get; set; } - public bool InlineUsers { get; set; } } public class SdkConfigServiceEndpointsParams @@ -61,7 +60,6 @@ public class SdkConfigServiceEndpointsParams public class SdkClientSideParams { - public bool? AutoAliasingOptOut { get; set; } public bool? EvaluationReasons { get; set; } public User InitialUser { get; set; } public bool? UseReport { get; set; } @@ -74,7 +72,6 @@ public class CommandParams public EvaluateAllFlagsParams EvaluateAll { get; set; } public IdentifyEventParams IdentifyEvent { get; set; } public CustomEventParams CustomEvent { get; set; } - public AliasEventParams AliasEvent { get; set; } } public class EvaluateFlagParams @@ -114,10 +111,4 @@ public class CustomEventParams public bool OmitNullData { get; set; } public double? MetricValue { get; set; } } - - public class AliasEventParams - { - public User User { get; set; } - public User PreviousUser { get; set; } - } } diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index f5fca115..9a66e783 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -82,10 +82,6 @@ public void Close() } return (true, null); - case "aliasEvent": - _client.Alias(command.AliasEvent.User, command.AliasEvent.PreviousUser); - return (true, null); - case "flushEvents": _client.Flush(); return (true, null); @@ -251,8 +247,7 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt { endpoints.Events(eventParams.BaseUri); var events = Components.SendEvents() - .AllAttributesPrivate(eventParams.AllAttributesPrivate) - .InlineUsersInEvents(eventParams.InlineUsers); + .AllAttributesPrivate(eventParams.AllAttributesPrivate); if (eventParams.Capacity.HasValue && eventParams.Capacity.Value > 0) { events.Capacity(eventParams.Capacity.Value); @@ -276,11 +271,6 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt } builder.Http(http); - if (sdkParams.ClientSide.AutoAliasingOptOut.HasValue) - { - builder.AutoAliasingOptOut(sdkParams.ClientSide.AutoAliasingOptOut.Value); - } - if (sdkParams.ClientSide.EvaluationReasons.HasValue) { builder.EvaluationReasons(sdkParams.ClientSide.EvaluationReasons.Value); From 93d2b4d4f4a25ec87ed9cf2ef43691d80dee5ca3 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 14:16:57 -0700 Subject: [PATCH 396/499] use U2C alpha packages, replace user with context --- Makefile | 8 +- contract-tests/Representations.cs | 4 +- contract-tests/SdkClientEntity.cs | 6 +- src/LaunchDarkly.ClientSdk/Components.cs | 2 +- .../Integrations/EventProcessorBuilder.cs | 43 +---- .../Integrations/HttpConfigurationBuilder.cs | 4 +- .../PersistenceConfigurationBuilder.cs | 20 +-- .../Integrations/PollingDataSourceBuilder.cs | 6 +- .../StreamingDataSourceBuilder.cs | 8 +- .../Integrations/TestData.cs | 44 ++--- .../Interfaces/EventProcessorTypes.cs | 20 +-- .../Interfaces/HttpConfiguration.cs | 2 +- .../Interfaces/IDataSourceFactory.cs | 4 +- .../Interfaces/IDataSourceUpdateSink.cs | 10 +- .../Interfaces/IEventProcessor.cs | 6 +- .../Interfaces/IFlagTracker.cs | 2 +- .../Interfaces/ILdClient.cs | 30 ++-- .../Internal/ComponentsImpl.cs | 8 +- .../Internal/Constants.cs | 3 + .../Internal/DataModelSerialization.cs | 6 +- .../Internal/DataSources/ConnectionManager.cs | 16 +- .../DataSources/DataSourceUpdateSinkImpl.cs | 27 ++-- .../DataSources/FeatureFlagRequestor.cs | 10 +- .../Internal/DataSources/PollingDataSource.cs | 8 +- .../DataSources/StreamingDataSource.cs | 18 +-- .../Internal/DataStores/FlagDataManager.cs | 38 ++--- .../DataStores/PersistenceConfiguration.cs | 3 +- .../DataStores/PersistentDataStoreWrapper.cs | 22 +-- .../Events/DefaultEventProcessorWrapper.cs | 12 +- .../Internal/Events/EventFactory.cs | 12 +- .../Internal/UserDecorator.cs | 28 ++-- .../LaunchDarkly.ClientSdk.csproj | 4 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 141 ++++++++-------- .../PlatformSpecific/Connectivity.android.cs | 2 +- .../PlatformSpecific/Platform.android.cs | 4 +- .../AssertHelpers.cs | 16 +- .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 3 +- .../ConfigurationTest.cs | 2 +- .../ILdClientExtensionsTest.cs | 4 +- .../Integrations/EventProcessorBuilderTest.cs | 9 +- .../Integrations/TestDataTest.cs | 2 +- .../Integrations/TestDataWithClientTest.cs | 10 +- .../Internal/DataModelSerializationTest.cs | 8 +- .../DataSourceUpdateSinkImplTest.cs | 4 +- .../DataSources/FeatureFlagRequestorTests.cs | 12 +- .../DataSources/PollingDataSourceTest.cs | 9 +- .../DataSources/StreamingDataSourceTest.cs | 15 +- .../FlagDataManagerWithPersistenceTest.cs | 4 +- .../PersistentDataStoreWrapperTest.cs | 14 +- .../LDClientEndToEndTests.cs | 8 +- .../LdClientDiagnosticEventTest.cs | 1 - .../LdClientEvaluationTests.cs | 2 +- .../LdClientEventTests.cs | 19 ++- .../LdClientTests.cs | 150 +++++++----------- .../MockComponents.cs | 72 ++++----- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 44 +++-- 56 files changed, 455 insertions(+), 534 deletions(-) diff --git a/Makefile b/Makefile index 566872bf..e731fb30 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,9 @@ TESTFRAMEWORK ?= netcoreapp2.1 # temporary skips for contract tests that can't pass till more U2C work is done TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \ - -skip events/alias \ - -skip events/custom \ - -skip events/user/inlineUsers=true + -skip events/requests/method +# events/request/method only fails because the latest alpha LaunchDarkly.InternalSdk +# doesn't set the correct schema version build-contract-tests: @cd contract-tests && dotnet build TestService.csproj @@ -30,7 +30,7 @@ start-contract-test-service-bg: run-contract-tests: @curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh \ - | VERSION=v1 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh + | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index 8ddcd64b..73b02240 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -61,7 +61,7 @@ public class SdkConfigServiceEndpointsParams public class SdkClientSideParams { public bool? EvaluationReasons { get; set; } - public User InitialUser { get; set; } + public Context InitialContext { get; set; } public bool? UseReport { get; set; } } @@ -101,7 +101,7 @@ public class EvaluateAllFlagsResponse public class IdentifyEventParams { - public User User { get; set; } + public Context Context { get; set; } } public class CustomEventParams diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index 9a66e783..25509f54 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -37,7 +37,7 @@ string tag startWaitTime = TimeSpan.FromMilliseconds(sdkParams.StartWaitTimeMs.Value); } - _client = LdClient.Init(config, sdkParams.ClientSide.InitialUser, startWaitTime); + _client = LdClient.Init(config, sdkParams.ClientSide.InitialContext, startWaitTime); if (!_client.Initialized && !sdkParams.InitCanFail) { _client.Dispose(); @@ -63,7 +63,7 @@ public void Close() return (true, DoEvaluateAll(command.EvaluateAll)); case "identifyEvent": - await _client.IdentifyAsync(command.IdentifyEvent.User); + await _client.IdentifyAsync(command.IdentifyEvent.Context); return (true, null); case "customEvent": @@ -258,7 +258,7 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt } if (eventParams.GlobalPrivateAttributes != null) { - events.PrivateAttributeNames(eventParams.GlobalPrivateAttributes); + events.PrivateAttributes(eventParams.GlobalPrivateAttributes); } builder.Events(events); builder.DiagnosticOptOut(!eventParams.EnableDiagnostics); diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index a607bbca..4dd276cf 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -165,7 +165,7 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// /// public static PersistenceConfigurationBuilder NoPersistence => - Persistence().Storage(NullPersistentDataStoreFactory.Instance).MaxCachedUsers(0); + Persistence().Storage(NullPersistentDataStoreFactory.Instance).MaxCachedContexts(0); /// /// Returns a configuration builder for the SDK's persistent storage configuration. diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index f925a21e..acfbe3ba 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -65,7 +65,7 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory, IDiagnosticD internal int _capacity = DefaultCapacity; internal TimeSpan _diagnosticRecordingInterval = DefaultDiagnosticRecordingInterval; internal TimeSpan _flushInterval = DefaultFlushInterval; - internal HashSet _privateAttributes = new HashSet(); + internal HashSet _privateAttributes = new HashSet(); internal IEventSender _eventSender = null; // used in testing /// @@ -73,7 +73,7 @@ public sealed class EventProcessorBuilder : IEventProcessorFactory, IDiagnosticD /// /// /// If this is , all user attribute values (other than the key) will be private, not just - /// the attributes specified in or on a per-user basis with + /// the attributes specified in or on a per-user basis with /// methods. By default, it is . /// /// true if all user attributes should be private @@ -164,44 +164,17 @@ internal EventProcessorBuilder FlushIntervalNoMinimum(TimeSpan flushInterval) /// Marks a set of attribute names as private. /// /// - /// Any users sent to LaunchDarkly with this configuration active will have attributes with these + /// Any contexts sent to LaunchDarkly with this configuration active will have attributes with these /// names removed. This is in addition to any attributes that were marked as private for an - /// individual user with methods. + /// individual context with methods. /// - /// a set of attributes that will be removed from user data set to LaunchDarkly + /// a set of attributes that will be removed from context data set to LaunchDarkly /// the builder - /// - public EventProcessorBuilder PrivateAttributes(params UserAttribute[] attributes) + public EventProcessorBuilder PrivateAttributes(params string[] attributes) { foreach (var a in attributes) { - _privateAttributes.Add(a); - } - return this; - } - - /// - /// Marks a set of attribute names as private. - /// - /// - /// - /// Any users sent to LaunchDarkly with this configuration active will have attributes with these - /// names removed. This is in addition to any attributes that were marked as private for an - /// individual user with methods. - /// - /// - /// Using is preferable to avoid the possibility of - /// misspelling a built-in attribute. - /// - /// - /// a set of names that will be removed from user data set to LaunchDarkly - /// the builder - /// - public EventProcessorBuilder PrivateAttributeNames(params string[] attributes) - { - foreach (var a in attributes) - { - _privateAttributes.Add(UserAttribute.ForName(a)); + _privateAttributes.Add(AttributeRef.FromPath(a)); } return this; } @@ -253,7 +226,7 @@ private EventsConfiguration MakeEventsConfiguration(LdClientContext context, boo EventsUri = baseUri.AddPath(StandardEndpoints.AnalyticsEventsPostRequestPath), DiagnosticRecordingInterval = _diagnosticRecordingInterval, DiagnosticUri = baseUri.AddPath(StandardEndpoints.DiagnosticEventsPostRequestPath), - PrivateAttributeNames = _privateAttributes.ToImmutableHashSet(), + PrivateAttributes = _privateAttributes.ToImmutableHashSet(), RetryInterval = TimeSpan.FromSeconds(1) }; } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index f8104017..83965a02 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -61,7 +61,7 @@ public sealed class HttpConfigurationBuilder : IDiagnosticDescription /// LaunchDarkly server, for any individual network connection. /// /// - /// It is not the same as the timeout parameter to , + /// It is not the same as the timeout parameter to , /// which limits the time for initializing the SDK regardless of how many individual HTTP requests /// are done in that time. /// @@ -170,7 +170,7 @@ public HttpConfigurationBuilder Proxy(IWebProxy proxy) /// HttpClient. /// /// - /// It is not the same as the timeout parameter to, + /// It is not the same as the timeout parameter to, /// which limits the time for initializing the SDK regardless of how many individual HTTP requests /// are done in that time. /// diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs index 5ed96e3b..568adc6a 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs @@ -31,18 +31,18 @@ namespace LaunchDarkly.Sdk.Client.Integrations public sealed class PersistenceConfigurationBuilder { /// - /// Default value for : 5. + /// Default value for : 5. /// - public const int DefaultMaxCachedUsers = 5; + public const int DefaultMaxCachedContexts = 5; /// - /// Passing this value (or any negative number) to + /// Passing this value (or any negative number) to /// means there is no limit on cached user data. /// - public const int UnlimitedCachedUsers = -1; + public const int UnlimitedCachedContexts = -1; private IPersistentDataStoreFactory _storeFactory = null; - private int _maxCachedUsers = DefaultMaxCachedUsers; + private int _maxCachedContexts = DefaultMaxCachedContexts; internal PersistenceConfigurationBuilder() { } @@ -78,16 +78,16 @@ public PersistenceConfigurationBuilder Storage(IPersistentDataStoreFactory persi /// flag data it has received since the current LdClient instance was started. /// /// - /// A value of or any other negative number means there is no + /// A value of or any other negative number means there is no /// limit. Use this mode with caution, as it could cause the size of mobile device preferences to /// grow indefinitely if your application uses many different user keys on the same device. /// /// - /// + /// /// - public PersistenceConfigurationBuilder MaxCachedUsers(int maxCachedUsers) + public PersistenceConfigurationBuilder MaxCachedContexts(int maxCachedContexts) { - _maxCachedUsers = maxCachedUsers; + _maxCachedContexts = maxCachedContexts; return this; } @@ -95,7 +95,7 @@ internal PersistenceConfiguration CreatePersistenceConfiguration(LdClientContext new PersistenceConfiguration( _storeFactory is null ? PlatformSpecific.LocalStorage.Instance : _storeFactory.CreatePersistentDataStore(context), - _maxCachedUsers + _maxCachedContexts ); } } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 9364ba63..6a3a6ec7 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -89,7 +89,7 @@ internal PollingDataSourceBuilder PollIntervalNoMinimum(TimeSpan pollInterval) public IDataSource CreateDataSource( LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, + Context currentContext, bool inBackground ) { @@ -107,7 +107,7 @@ bool inBackground var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); var requestor = new FeatureFlagRequestor( baseUri, - currentUser, + currentContext, context.EvaluationReasons, context.Http, logger @@ -115,7 +115,7 @@ bool inBackground return new PollingDataSource( updateSink, - currentUser, + currentContext, requestor, _pollInterval, TimeSpan.Zero, diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index 2cefb668..b9a1bbbf 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -88,7 +88,7 @@ public StreamingDataSourceBuilder InitialReconnectDelay(TimeSpan initialReconnec public IDataSource CreateDataSource( LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, + Context currentContext, bool inBackground ) { @@ -110,13 +110,13 @@ bool inBackground // When in the background, always use polling instead of streaming return new PollingDataSourceBuilder() .BackgroundPollInterval(_backgroundPollInterval) - .CreateDataSource(context, updateSink, currentUser, true); + .CreateDataSource(context, updateSink, currentContext, true); } var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); var requestor = new FeatureFlagRequestor( pollingBaseUri, - currentUser, + currentContext, context.EvaluationReasons, context.Http, logger @@ -124,7 +124,7 @@ bool inBackground return new StreamingDataSource( updateSink, - currentUser, + currentContext, baseUri, context.EvaluationReasons, _initialReconnectDelay, diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs index 323ed0ba..6bfb1e95 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -84,7 +84,7 @@ private TestData() { } /// /// /// Otherwise, it starts with a new default configuration in which the flag has true - /// and false variations, and is true by default for all users. You can change + /// and false variations, and is true by default for all contexts. You can change /// any of those properties, and provide more complex behavior, using the /// methods. /// @@ -163,7 +163,7 @@ private void UpdateInternal(string key, FlagBuilder builder) foreach (var instance in instances) { - instance.DoUpdate(key, builder.CreateFlag(newVersion, instance.User)); + instance.DoUpdate(key, builder.CreateFlag(newVersion, instance.Context)); } } @@ -198,14 +198,14 @@ public TestData UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInf public IDataSource CreateDataSource( LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, + Context currentContext, bool inBackground ) { var instance = new DataSourceImpl( this, updateSink, - currentUser, + currentContext, context.BaseLogger.SubLogger("DataSource.TestData") ); lock (_lock) @@ -215,7 +215,7 @@ bool inBackground return instance; } - internal FullDataSet MakeInitData(User user) + internal FullDataSet MakeInitData(Context context) { lock (_lock) { @@ -228,7 +228,7 @@ internal FullDataSet MakeInitData(User user) _currentFlagVersions[fb.Key] = version; } b.Add(new KeyValuePair(fb.Key, - fb.Value.CreateFlag(version, user))); + fb.Value.CreateFlag(version, context))); } return new FullDataSet(b.ToImmutable()); } @@ -262,7 +262,7 @@ public sealed class FlagBuilder private List _variations; private int _defaultVariation; private Dictionary _variationByUserKey; - private Func _variationFunc; + private Func _variationFunc; private FeatureFlag _preconfiguredFlag; #endregion @@ -424,10 +424,10 @@ public FlagBuilder VariationForUser(string userKey, LdValue value) /// /// a function to determine the variation /// the builder - public FlagBuilder VariationFunc(Func variationFunc) => - BooleanFlag().VariationFunc(user => + public FlagBuilder VariationFunc(Func variationFunc) => + BooleanFlag().VariationFunc(context => { - var b = variationFunc(user); + var b = variationFunc(context); return b.HasValue ? VariationForBoolean(b.Value) : (int?)null; }); @@ -448,7 +448,7 @@ public FlagBuilder VariationFunc(Func variationFunc) => /// /// a function to determine the variation /// the builder - public FlagBuilder VariationFunc(Func variationFunc) + public FlagBuilder VariationFunc(Func variationFunc) { _variationFunc = variationFunc; return this; @@ -475,10 +475,10 @@ public FlagBuilder VariationFunc(Func variationFunc) /// /// a function to determine the variation /// the builder - public FlagBuilder VariationFunc(Func variationFunc) => - VariationFunc(user => + public FlagBuilder VariationFunc(Func variationFunc) => + VariationFunc(context => { - var v = variationFunc(user); + var v = variationFunc(context); if (!v.HasValue || !_variations.Contains(v.Value)) { return null; @@ -514,7 +514,7 @@ internal FlagBuilder PreconfiguredFlag(FeatureFlag preconfiguredFlag) #region Internal methods - internal ItemDescriptor CreateFlag(int version, User user) + internal ItemDescriptor CreateFlag(int version, Context context) { if (_preconfiguredFlag != null) { @@ -529,9 +529,9 @@ internal ItemDescriptor CreateFlag(int version, User user) _preconfiguredFlag.DebugEventsUntilDate)); } int variation; - if (!_variationByUserKey.TryGetValue(user.Key, out variation)) + if (!_variationByUserKey.TryGetValue(context.Key, out variation)) { - variation = _variationFunc?.Invoke(user) ?? _defaultVariation; + variation = _variationFunc?.Invoke(context) ?? _defaultVariation; } var value = (variation < 0 || variation >= _variations.Count) ? LdValue.Null : _variations[variation]; @@ -579,19 +579,19 @@ internal class DataSourceImpl : IDataSource private readonly IDataSourceUpdateSink _updateSink; private readonly Logger _log; - internal readonly User User; + internal readonly Context Context; - internal DataSourceImpl(TestData parent, IDataSourceUpdateSink updateSink, User user, Logger log) + internal DataSourceImpl(TestData parent, IDataSourceUpdateSink updateSink, Context context, Logger log) { _parent = parent; _updateSink = updateSink; - User = user; + Context = context; _log = log; } public Task Start() { - _updateSink.Init(User, _parent.MakeInitData(User)); + _updateSink.Init(Context, _parent.MakeInitData(Context)); return Task.FromResult(true); } @@ -604,7 +604,7 @@ internal void DoUpdate(string key, ItemDescriptor item) { _log.Debug("updating \"{0}\" to {1}", key, LogValues.Defer(() => item.Item is null ? "" : DataModelSerialization.SerializeFlag(item.Item))); - _updateSink.Upsert(User, key, item); + _updateSink.Upsert(Context, key, item); } internal void DoUpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs index 8f3a5ec7..8b96bedb 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs @@ -12,7 +12,7 @@ namespace LaunchDarkly.Sdk.Client.Interfaces public static class EventProcessorTypes { /// - /// Parameters for . + /// Parameters for . /// public struct EvaluationEvent { @@ -22,10 +22,10 @@ public struct EvaluationEvent public UnixMillisecondTime Timestamp { get; set; } /// - /// Attributes of the user who generated the event. Some attributes may not be sent - /// to LaunchDarkly if they are private. + /// The context for the evaluation. Some attributes may not be sent to LaunchDarkly if they + /// are private. /// - public User User { get; set; } + public Context Context { get; set; } /// /// The unique key of the feature flag involved in the event. @@ -74,7 +74,7 @@ public struct EvaluationEvent } /// - /// Parameters for . + /// Parameters for . /// public struct IdentifyEvent { @@ -84,14 +84,14 @@ public struct IdentifyEvent public UnixMillisecondTime Timestamp { get; set; } /// - /// Attributes of the user being identified. Some attributes may not be sent + /// The evaluation context associated with the event. Some attributes may not be sent /// to LaunchDarkly if they are private. /// - public User User { get; set; } + public Context Context { get; set; } } /// - /// Parameters for . + /// Parameters for . /// public struct CustomEvent { @@ -100,10 +100,10 @@ public struct CustomEvent /// public UnixMillisecondTime Timestamp { get; set; } /// - /// Attributes of the user who generated the event. Some attributes may not be sent + /// The evaluation context associated with the event. Some attributes may not be sent /// to LaunchDarkly if they are private. /// - public User User { get; set; } + public Context Context { get; set; } /// /// The event key. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs index 5fd2d001..dbbac1c0 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs @@ -73,7 +73,7 @@ public sealed class HttpConfiguration /// HttpClient. /// /// - /// It is not the same as the timeout parameter to, + /// It is not the same as the timeout parameter to, /// which limits the time for initializing the SDK regardless of how many individual HTTP /// requests are done in that time. /// diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs index 55c2abcd..bf439c53 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs @@ -14,13 +14,13 @@ public interface IDataSourceFactory /// /// configuration of the current client instance /// the destination for pushing data and status updates - /// the current user attributes + /// the current evaluation context /// true if the application is known to be in the background /// an instance IDataSource CreateDataSource( LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, + Context currentContext, bool inBackground ); } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs index 74b957dc..2fb5f360 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs @@ -18,19 +18,19 @@ public interface IDataSourceUpdateSink /// /// Completely overwrites the current contents of the data store with a set of items for each collection. /// - /// the current user + /// the current evaluation context /// the data set /// true if the update succeeded, false if it failed - void Init(User user, FullDataSet data); + void Init(Context context, FullDataSet data); /// /// Updates or inserts an item. For updates, the object will only be updated if the existing /// version is less than the new version. /// - /// the current user + /// the current evaluation context /// the feature flag key /// the item data - void Upsert(User user, string key, ItemDescriptor data); + void Upsert(Context context, string key, ItemDescriptor data); /// /// Informs the SDK of a change in the data source's status. @@ -55,7 +55,7 @@ public interface IDataSourceUpdateSink /// /// Data source implementations normally should not need to set the state to /// , because that will happen automatically if they call - /// . + /// . /// /// /// the data source state diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs index 0743a244..1e18d755 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs @@ -26,19 +26,19 @@ public interface IEventProcessor : IDisposable /// events service as an individual event, or may only be added into summary data. /// /// parameters for an evaluation event - void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e); + void RecordEvaluationEvent(in EventProcessorTypes.EvaluationEvent e); /// /// Records a set of user properties. /// /// parameters for an identify event - void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e); + void RecordIdentifyEvent(in EventProcessorTypes.IdentifyEvent e); /// /// Records a custom event. /// /// parameters for a custom event - void RecordCustomEvent(EventProcessorTypes.CustomEvent e); + void RecordCustomEvent(in EventProcessorTypes.CustomEvent e); /// /// Puts the component into offline mode if appropriate. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs index 2b68f588..722e971c 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IFlagTracker.cs @@ -24,7 +24,7 @@ public interface IFlagTracker /// /// /// Currently this event will not fire in a scenario where 1. the client is offline, 2. - /// or + /// or /// has been called to change the current user, and 3. the SDK had previously stored flag data /// for that user (see ) and has /// now loaded those flags. The event will only fire if the SDK has received new flag data diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index d5c12506..462cd7ce 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -39,8 +39,8 @@ public interface ILdClient : IDisposable /// /// /// - /// When you first start the client, once or - /// has returned, should be + /// When you first start the client, once or + /// has returned, should be /// if and only if either 1. it connected to LaunchDarkly and successfully retrieved /// flags, or 2. it started in offline mode so there's no need to connect to LaunchDarkly. If the client /// timed out trying to connect to LD, then is (even if we @@ -48,8 +48,8 @@ public interface ILdClient : IDisposable /// . This serves the purpose of letting the app know that there was a problem of some kind. /// /// - /// If you call or , - /// will become until the SDK receives the new user's flags. + /// If you call or , + /// will become until the SDK receives the new context's flags. /// /// bool Initialized { get; } @@ -304,28 +304,28 @@ public interface ILdClient : IDisposable IDictionary AllFlags(); /// - /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates - /// an analytics event to tell LaunchDarkly about the user. + /// Changes the current evaluation context, requests flags for that context from LaunchDarkly if we are online, + /// and generates an analytics event to tell LaunchDarkly about the context. /// /// /// - /// This is equivalent to , but as a synchronous method. + /// This is equivalent to , but as a synchronous method. /// /// - /// If the SDK is online, waits to receive feature flag values for the new user from + /// If the SDK is online, waits to receive feature flag values for the new context from /// LaunchDarkly. If it receives the new flag values before maxWaitTime has elapsed, it returns /// . If the timeout elapses, it returns (although the SDK might /// still receive the flag values later). If we do not need to request flags from LaunchDarkly because we are /// in offline mode, it returns . /// /// - /// If you do not want to wait, you can either set maxWaitTime to zero or call . + /// If you do not want to wait, you can either set maxWaitTime to zero or call . /// /// - /// the new user + /// the new context /// the maximum time to wait for the new flag values /// true if new flag values were obtained - bool Identify(User user, TimeSpan maxWaitTime); + bool Identify(Context context, TimeSpan maxWaitTime); /// /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates @@ -333,7 +333,7 @@ public interface ILdClient : IDisposable /// /// /// - /// This is equivalent to , but as an asynchronous method. + /// This is equivalent to , but as an asynchronous method. /// /// /// If the SDK is online, the returned task is completed once the SDK has received feature flag values for the @@ -342,9 +342,9 @@ public interface ILdClient : IDisposable /// and yields . /// /// - /// the new user + /// the new context /// a task that yields true if new flag values were obtained - Task IdentifyAsync(User user); + Task IdentifyAsync(Context context); /// /// Tells the client that all pending analytics events should be delivered as soon as possible. @@ -352,7 +352,7 @@ public interface ILdClient : IDisposable /// /// /// When the LaunchDarkly client generates analytics events (from flag evaluations, or from - /// or ), they are queued on a worker thread. + /// or ), they are queued on a worker thread. /// The event thread normally sends all queued events to LaunchDarkly at regular intervals, controlled by the /// option. Calling triggers a send /// without waiting for the next interval. diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index 3e2921b9..1c10e70b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -11,7 +11,7 @@ internal sealed class NullDataSourceFactory : IDataSourceFactory public IDataSource CreateDataSource( LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, + Context currentContext, bool inBackground ) => new NullDataSource(); @@ -42,11 +42,11 @@ public void Dispose() { } public void Flush() { } - public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) { } + public void RecordCustomEvent(in EventProcessorTypes.CustomEvent e) { } - public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) { } + public void RecordEvaluationEvent(in EventProcessorTypes.EvaluationEvent e) { } - public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) { } + public void RecordIdentifyEvent(in EventProcessorTypes.IdentifyEvent e) { } public void SetOffline(bool offline) { } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs index 8775a8c2..15b52113 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs @@ -12,5 +12,8 @@ internal static class Constants public const string DELETE = "delete"; public const string PING = "ping"; public const string UNIQUE_ID_KEY = "unique_id_key"; + + // Temporary because the current implementation of Context does not allow a null key + public const string AutoKeyMagicValue = "$$$auto"; } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs index d31ad1cc..560a6cb2 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs @@ -12,7 +12,7 @@ namespace LaunchDarkly.Sdk.Client.Internal { // Methods for converting data to or from a serialized form. // - // The JSON representation of a User is defined along with User in LaunchDarkly.CommonSdk. + // The JSON representation of a Context is defined along with Context in LaunchDarkly.CommonSdk. // // The serialized representation of a single FeatureFlag is simply a JSON object containing // its properties, as defined in FeatureFlag. @@ -29,8 +29,8 @@ internal static class DataModelSerialization { private const string ParseErrorMessage = "Data was not in a recognized format"; - internal static string SerializeUser(User user) => - LdJsonSerialization.SerializeObject(user); + internal static string SerializeContext(Context context) => + LdJsonSerialization.SerializeObject(context); internal static string SerializeFlag(FeatureFlag flag) => LdJsonSerialization.SerializeObject(flag); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index c345589b..503de66d 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -15,7 +15,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources /// /// Whenever the state of this object is modified by , /// , , - /// , or , it will decide whether to make a new + /// , or , it will decide whether to make a new /// connection, drop an existing connection, both, or neither. If the caller wants to know when a /// new connection (if any) is ready, it should await the returned task. /// @@ -40,7 +40,7 @@ internal sealed class ConnectionManager : IDisposable private bool _forceOffline = false; private bool _networkEnabled = false; private bool _inBackground = false; - private User _user = null; + private Context _context; private IDataSource _dataSource = null; // Note that these properties do not have simple setter methods, because the setters all @@ -70,7 +70,7 @@ internal ConnectionManager( IEventProcessor eventProcessor, DiagnosticDisablerImpl diagnosticDisabler, bool enableBackgroundUpdating, - User initialUser, + Context initialContext, Logger log ) { @@ -80,7 +80,7 @@ Logger log _eventProcessor = eventProcessor; _diagnosticDisabler = diagnosticDisabler; _enableBackgroundUpdating = enableBackgroundUpdating; - _user = initialUser; + _context = initialContext; _log = log; } @@ -193,10 +193,10 @@ public void SetInBackground(bool inBackground) /// /// Updates the current user. /// - /// the new user + /// the new context /// a task that is completed when we have received data for the new user, if the /// data source is online, or completed immediately otherwise - public Task SetUser(User user) + public Task SetContext(Context context) { return LockUtils.WithWriteLock(_lock, () => { @@ -204,7 +204,7 @@ public Task SetUser(User user) { return Task.FromResult(false); } - _user = user; + _context = context; _initialized = false; return OpenOrCloseConnectionIfNecessary(true); }); @@ -283,7 +283,7 @@ private Task OpenOrCloseConnectionIfNecessary(bool mustReinitializeDataSou // started. The state will then be updated as appropriate by the data source either // calling UpdateStatus, or Init which implies UpdateStatus(Valid). _updateSink.UpdateStatus(DataSourceState.Initializing, null); - _dataSource = _dataSourceFactory.CreateDataSource(_clientContext, _updateSink, _user, _inBackground); + _dataSource = _dataSourceFactory.CreateDataSource(_clientContext, _updateSink, _context, _inBackground); return _dataSource.Start() .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index d39db681..a038e1cd 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -46,14 +46,14 @@ Logger log _status = new StateMonitor(initialStatus, MaybeUpdateStatus, log); } - public void Init(User user, FullDataSet data) + public void Init(Context context, FullDataSet data) { - _dataStore.Init(user, data, true); + _dataStore.Init(context, data, true); ImmutableDictionary oldValues, newValues; lock (_lastValuesLock) { - _lastValues.TryGetValue(user.Key, out oldValues); + _lastValues.TryGetValue(context.Key, out oldValues); var builder = ImmutableDictionary.CreateBuilder(); foreach (var newEntry in data.Items) { @@ -64,7 +64,7 @@ public void Init(User user, FullDataSet data) } } newValues = builder.ToImmutable(); - _lastValues = _lastValues.SetItem(user.Key, newValues); + _lastValues = _lastValues.SetItem(context.Key, newValues); } UpdateStatus(DataSourceState.Valid, null); @@ -105,37 +105,38 @@ public void Init(User user, FullDataSet data) } } - public void Upsert(User user, string key, ItemDescriptor data) + public void Upsert(Context context, string flagKey, ItemDescriptor data) { - var updated = _dataStore.Upsert(key, data); + var updated = _dataStore.Upsert(flagKey, data); if (!updated) { return; } FeatureFlag oldFlag = null; + var contextKey = context.FullyQualifiedKey; lock (_lastValuesLock) { - _lastValues.TryGetValue(user.Key, out var oldValues); + _lastValues.TryGetValue(contextKey, out var oldValues); if (oldValues is null) { // didn't have any flags for this user var initValues = ImmutableDictionary.Empty; if (data.Item != null) { - initValues = initValues.SetItem(key, data.Item); + initValues = initValues.SetItem(flagKey, data.Item); } - _lastValues = _lastValues.SetItem(user.Key, initValues); + _lastValues = _lastValues.SetItem(contextKey, initValues); return; // don't bother with change events if we had no previous data } - oldValues.TryGetValue(key, out oldFlag); + oldValues.TryGetValue(flagKey, out oldFlag); var newValues = data.Item is null ? - oldValues.Remove(key) : oldValues.SetItem(key, data.Item); - _lastValues = _lastValues.SetItem(user.Key, newValues); + oldValues.Remove(flagKey) : oldValues.SetItem(flagKey, data.Item); + _lastValues = _lastValues.SetItem(contextKey, newValues); } if (oldFlag?.Variation != data.Item?.Variation) { - var eventArgs = new FlagValueChangeEvent(key, + var eventArgs = new FlagValueChangeEvent(flagKey, oldFlag?.Value ?? LdValue.Null, data.Item?.Value ?? LdValue.Null, data.Item is null diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index 68594dea..76d761dd 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -36,7 +36,7 @@ internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor private static readonly HttpMethod ReportMethod = new HttpMethod("REPORT"); private readonly Uri _baseUri; - private readonly User _currentUser; + private readonly Context _currentContext; private readonly bool _useReport; private readonly bool _withReasons; private readonly HttpClient _httpClient; @@ -46,7 +46,7 @@ internal sealed class FeatureFlagRequestor : IFeatureFlagRequestor internal FeatureFlagRequestor( Uri baseUri, - User user, + Context context, bool withReasons, HttpConfiguration httpConfig, Logger log @@ -55,7 +55,7 @@ Logger log this._baseUri = baseUri; this._httpConfig = httpConfig; this._httpClient = httpConfig.HttpProperties.NewHttpClient(); - this._currentUser = user; + this._currentContext = context; this._useReport = httpConfig.UseReport; this._withReasons = withReasons; this._log = log; @@ -70,14 +70,14 @@ public async Task FeatureFlagsAsync() private HttpRequestMessage GetRequestMessage() { var path = StandardEndpoints.PollingRequestGetRequestPath( - Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_currentUser))); + Base64.UrlSafeEncode(DataModelSerialization.SerializeContext(_currentContext))); return new HttpRequestMessage(HttpMethod.Get, MakeRequestUriWithPath(path)); } private HttpRequestMessage ReportRequestMessage() { var request = new HttpRequestMessage(ReportMethod, MakeRequestUriWithPath(StandardEndpoints.PollingRequestReportRequestPath)); - request.Content = new StringContent(DataModelSerialization.SerializeUser(_currentUser), Encoding.UTF8, Constants.APPLICATION_JSON); + request.Content = new StringContent(DataModelSerialization.SerializeContext(_currentContext), Encoding.UTF8, Constants.APPLICATION_JSON); return request; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index 21fe1ba1..45c38a47 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -14,7 +14,7 @@ internal sealed class PollingDataSource : IDataSource { private readonly IFeatureFlagRequestor _featureFlagRequestor; private readonly IDataSourceUpdateSink _updateSink; - private readonly User _user; + private readonly Context _context; private readonly TimeSpan _pollingInterval; private readonly TimeSpan _initialDelay; private readonly Logger _log; @@ -25,7 +25,7 @@ internal sealed class PollingDataSource : IDataSource internal PollingDataSource( IDataSourceUpdateSink updateSink, - User user, + Context context, IFeatureFlagRequestor featureFlagRequestor, TimeSpan pollingInterval, TimeSpan initialDelay, @@ -34,7 +34,7 @@ internal PollingDataSource( { this._featureFlagRequestor = featureFlagRequestor; this._updateSink = updateSink; - this._user = user; + this._context = context; this._pollingInterval = pollingInterval; this._initialDelay = initialDelay; this._taskExecutor = taskExecutor; @@ -71,7 +71,7 @@ private async Task UpdateTaskAsync() { var flagsAsJsonString = response.jsonResponse; var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); - _updateSink.Init(_user, allData); + _updateSink.Init(_context, allData); if (_initialized.GetAndSet(true) == false) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index 0971edd7..2cea4609 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -27,7 +27,7 @@ internal sealed class StreamingDataSource : IDataSource private readonly IDataSourceUpdateSink _updateSink; private readonly Uri _baseUri; - private readonly User _user; + private readonly Context _context; private readonly bool _useReport; private readonly bool _withReasons; private readonly TimeSpan _initialReconnectDelay; @@ -44,7 +44,7 @@ internal sealed class StreamingDataSource : IDataSource internal StreamingDataSource( IDataSourceUpdateSink updateSink, - User user, + Context context, Uri baseUri, bool withReasons, TimeSpan initialReconnectDelay, @@ -55,7 +55,7 @@ IDiagnosticStore diagnosticStore ) { this._updateSink = updateSink; - this._user = user; + this._context = context; this._baseUri = baseUri; this._useReport = httpConfig.UseReport; this._withReasons = withReasons; @@ -77,7 +77,7 @@ public Task Start() _httpProperties, ReportMethod, MakeRequestUriWithPath(StandardEndpoints.StreamingReportRequestPath), - DataModelSerialization.SerializeUser(_user) + DataModelSerialization.SerializeContext(_context) ); } else @@ -86,7 +86,7 @@ public Task Start() _httpProperties, HttpMethod.Get, MakeRequestUriWithPath(StandardEndpoints.StreamingGetRequestPath( - Base64.UrlSafeEncode(DataModelSerialization.SerializeUser(_user)))), + Base64.UrlSafeEncode(DataModelSerialization.SerializeContext(_context)))), null ); } @@ -222,7 +222,7 @@ void HandleMessage(string messageType, string messageData) case Constants.PUT: { var allData = DataModelSerialization.DeserializeV1Schema(messageData); - _updateSink.Init(_user, allData); + _updateSink.Init(_context, allData); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); @@ -236,7 +236,7 @@ void HandleMessage(string messageType, string messageData) var parsed = LdValue.Parse(messageData); var flagkey = parsed.Get(Constants.KEY).AsString; var featureFlag = DataModelSerialization.DeserializeFlag(messageData); - _updateSink.Upsert(_user, flagkey, featureFlag.ToItemDescriptor()); + _updateSink.Upsert(_context, flagkey, featureFlag.ToItemDescriptor()); } catch (Exception ex) { @@ -253,7 +253,7 @@ void HandleMessage(string messageType, string messageData) int version = parsed.Get(Constants.VERSION).AsInt; string flagKey = parsed.Get(Constants.KEY).AsString; var deletedItem = new ItemDescriptor(version, null); - _updateSink.Upsert(_user, flagKey, deletedItem); + _updateSink.Upsert(_context, flagKey, deletedItem); } catch (Exception ex) { @@ -271,7 +271,7 @@ void HandleMessage(string messageType, string messageData) var response = await _requestor.FeatureFlagsAsync(); var flagsAsJsonString = response.jsonResponse; var allData = DataModelSerialization.DeserializeV1Schema(flagsAsJsonString); - _updateSink.Init(_user, allData); + _updateSink.Init(_context, allData); if (!_initialized.GetAndSet(true)) { _initTask.SetResult(true); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs index adcfeb34..0b100433 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs @@ -36,7 +36,7 @@ internal sealed class FlagDataManager : IDisposable private volatile ImmutableDictionary _flags = ImmutableDictionary.Empty; private volatile UserIndex _storeIndex = null; - private volatile string _currentUserId = null; + private string _currentContextId = null; public PersistentDataStoreWrapper PersistentStore => _persistentStore; @@ -67,27 +67,27 @@ Logger log } /// - /// Attempts to retrieve cached data for the specified user, if any. This does not - /// affect the current user/flags state. + /// Attempts to retrieve cached data for the specified context, if any. This does not + /// affect the current context/flags state. /// - /// a user - /// that user's data from the persistent store, or null if none - public FullDataSet? GetCachedData(User user) => - _persistentStore is null ? null : _persistentStore.GetUserData(UserIdFor(user)); + /// an evaluation context + /// that context's data from the persistent store, or null if none + public FullDataSet? GetCachedData(Context context) => + _persistentStore is null ? null : _persistentStore.GetContextData(ContextIdFor(context)); /// - /// Replaces the current flag data and updates the current-user state, optionally + /// Replaces the current flag data and updates the current-context state, optionally /// updating persistent storage as well. /// - /// the user who should become the current user + /// the context that should become the current context /// the full flag data /// true to also update the flag data in /// persistent storage (if persistent storage is enabled) - public void Init(User user, FullDataSet data, bool updatePersistentStorage) + public void Init(Context context, FullDataSet data, bool updatePersistentStorage) { var newFlags = data.Items.ToImmutableDictionary(); IEnumerable removedUserIds = null; - var userId = UserIdFor(user); + var contextId = ContextIdFor(context); var updatedIndex = _storeIndex; lock (_writerLock) @@ -96,12 +96,12 @@ public void Init(User user, FullDataSet data, bool updatePersistentStorage) if (_storeIndex != null) { - updatedIndex = _storeIndex.UpdateTimestamp(userId, UnixMillisecondTime.Now) + updatedIndex = _storeIndex.UpdateTimestamp(contextId, UnixMillisecondTime.Now) .Prune(_maxCachedUsers, out removedUserIds); _storeIndex = updatedIndex; } - _currentUserId = userId; + _currentContextId = contextId; } if (_persistentStore != null) @@ -112,12 +112,12 @@ public void Init(User user, FullDataSet data, bool updatePersistentStorage) { foreach (var oldId in removedUserIds) { - _persistentStore.RemoveUserData(oldId); + _persistentStore.RemoveContextData(oldId); } } if (updatePersistentStorage) { - _persistentStore.SetUserData(userId, data); + _persistentStore.SetContextData(contextId, data); } _persistentStore.SetIndex(updatedIndex); } @@ -162,7 +162,7 @@ public void Init(User user, FullDataSet data, bool updatePersistentStorage) public bool Upsert(string key, ItemDescriptor data) { var updatedFlags = _flags; - string userId = null; + string contextId = null; lock (_writerLock) { @@ -172,15 +172,15 @@ public bool Upsert(string key, ItemDescriptor data) } updatedFlags = _flags.SetItem(key, data); _flags = updatedFlags; - userId = _currentUserId; + contextId = _currentContextId; } - _persistentStore?.SetUserData(userId, new FullDataSet(updatedFlags)); + _persistentStore?.SetContextData(contextId, new FullDataSet(updatedFlags)); return true; } public void Dispose() => _persistentStore?.Dispose(); - internal static string UserIdFor(User user) => Base64.UrlSafeSha256Hash(user.Key); + internal static string ContextIdFor(Context context) => Base64.UrlSafeSha256Hash(context.FullyQualifiedKey); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs index 571f4b07..ea7a7854 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs @@ -1,5 +1,4 @@ -using System; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index 1a9a6442..0d1ae236 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -25,9 +25,9 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores internal sealed class PersistentDataStoreWrapper : IDisposable { private const string NamespacePrefix = "LaunchDarkly"; - private const string GlobalAnonUserKey = "anonUser"; + private const string GlobalAnonContextKey = "anonUser"; private const string EnvironmentMetadataKey = "index"; - private const string EnvironmentUserDataKeyPrefix = "flags_"; + private const string EnvironmentContextDataKeyPrefix = "flags_"; private readonly IPersistentDataStore _persistentStore; private readonly string _globalNamespace; @@ -50,9 +50,9 @@ Logger log _environmentNamespace = NamespacePrefix + "_" + Base64.UrlSafeSha256Hash(mobileKey); } - public FullDataSet? GetUserData(string userId) + public FullDataSet? GetContextData(string contextId) { - var serializedData = HandleErrorsAndLock(() => _persistentStore.GetValue(_environmentNamespace, KeyForUserId(userId))); + var serializedData = HandleErrorsAndLock(() => _persistentStore.GetValue(_environmentNamespace, KeyForContextId(contextId))); if (serializedData is null) { return null; @@ -68,12 +68,12 @@ Logger log } } - public void SetUserData(string userId, FullDataSet data) => - HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForUserId(userId), + public void SetContextData(string contextId, FullDataSet data) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForContextId(contextId), DataModelSerialization.SerializeAll(data))); - public void RemoveUserData(string userId) => - HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForUserId(userId), null)); + public void RemoveContextData(string contextId) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForContextId(contextId), null)); public UserIndex GetIndex() { @@ -97,15 +97,15 @@ public void SetIndex(UserIndex index) => HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, EnvironmentMetadataKey, index.Serialize())); public string GetAnonymousUserKey() => - HandleErrorsAndLock(() => _persistentStore.GetValue(_globalNamespace, GlobalAnonUserKey)); + HandleErrorsAndLock(() => _persistentStore.GetValue(_globalNamespace, GlobalAnonContextKey)); public void SetAnonymousUserKey(string value) => - HandleErrorsAndLock(() => _persistentStore.SetValue(_globalNamespace, GlobalAnonUserKey, value)); + HandleErrorsAndLock(() => _persistentStore.SetValue(_globalNamespace, GlobalAnonContextKey, value)); public void Dispose() => _persistentStore.Dispose(); - private static string KeyForUserId(string userId) => EnvironmentUserDataKeyPrefix + userId; + private static string KeyForContextId(string contextId) => EnvironmentContextDataKeyPrefix + contextId; private void MaybeLogStoreError(Exception e) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs index 38c1dd1e..c42c3818 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -12,12 +12,12 @@ internal DefaultEventProcessorWrapper(EventProcessor eventProcessor) _eventProcessor = eventProcessor; } - public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) + public void RecordEvaluationEvent(in EventProcessorTypes.EvaluationEvent e) { _eventProcessor.RecordEvaluationEvent(new EventTypes.EvaluationEvent { Timestamp = e.Timestamp, - User = e.User, + Context = e.Context, FlagKey = e.FlagKey, FlagVersion = e.FlagVersion, Variation = e.Variation, @@ -29,21 +29,21 @@ public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) }); } - public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) + public void RecordIdentifyEvent(in EventProcessorTypes.IdentifyEvent e) { _eventProcessor.RecordIdentifyEvent(new EventTypes.IdentifyEvent { Timestamp = e.Timestamp, - User = e.User + Context = e.Context }); } - public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) + public void RecordCustomEvent(in EventProcessorTypes.CustomEvent e) { _eventProcessor.RecordCustomEvent(new EventTypes.CustomEvent { Timestamp = e.Timestamp, - User = e.User, + Context = e.Context, EventKey = e.EventKey, Data = e.Data, MetricValue = e.MetricValue diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs index 73a96f42..31362342 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs @@ -19,7 +19,7 @@ internal EventFactory(bool withReasons) internal EvaluationEvent NewEvaluationEvent( string flagKey, FeatureFlag flag, - User user, + Context context, EvaluationDetail result, LdValue defaultValue ) @@ -32,7 +32,7 @@ LdValue defaultValue return new EvaluationEvent { Timestamp = UnixMillisecondTime.Now, - User = user, + Context = context, FlagKey = flagKey, FlagVersion = flag.FlagVersion ?? flag.Version, Variation = result.VariationIndex, @@ -47,7 +47,7 @@ LdValue defaultValue internal EvaluationEvent NewDefaultValueEvaluationEvent( string flagKey, FeatureFlag flag, - User user, + Context context, LdValue defaultValue, EvaluationErrorKind errorKind ) @@ -55,7 +55,7 @@ EvaluationErrorKind errorKind return new EvaluationEvent { Timestamp = UnixMillisecondTime.Now, - User = user, + Context = context, FlagKey = flagKey, FlagVersion = flag.FlagVersion ?? flag.Version, Value = defaultValue, @@ -68,7 +68,7 @@ EvaluationErrorKind errorKind internal EvaluationEvent NewUnknownFlagEvaluationEvent( string flagKey, - User user, + Context context, LdValue defaultValue, EvaluationErrorKind errorKind ) @@ -76,7 +76,7 @@ EvaluationErrorKind errorKind return new EvaluationEvent { Timestamp = UnixMillisecondTime.Now, - User = user, + Context = context, FlagKey = flagKey, Value = defaultValue, Default = defaultValue, diff --git a/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs index d994c312..60d212a4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs @@ -27,37 +27,37 @@ PersistentDataStoreWrapper store _osName = PlatformSpecific.UserMetadata.OSName; } - public User DecorateUser(User user) + public Context DecorateContext(Context context) { - IUserBuilder buildUser = null; + ContextBuilder builder = null; if (_deviceName != null) { - if (buildUser is null) + if (builder is null) { - buildUser = User.Builder(user); + builder = Context.BuilderFromContext(context); } - buildUser.Custom("device", _deviceName); + builder.Set("device", _deviceName); } if (_osName != null) { - if (buildUser is null) + if (builder is null) { - buildUser = User.Builder(user); + builder = Context.BuilderFromContext(context); } - buildUser.Custom("os", _osName); + builder.Set("os", _osName); } - // If you pass in a user with a null or blank key, one will be assigned to them. - if (String.IsNullOrEmpty(user.Key)) + // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key + if (context.Key == Constants.AutoKeyMagicValue) { - if (buildUser is null) + if (builder is null) { - buildUser = User.Builder(user); + builder = Context.BuilderFromContext(context); } var anonUserKey = GetOrCreateAnonUserKey(); - buildUser.Key(anonUserKey).Anonymous(true); + builder.Key(anonUserKey).Transient(true); } - return buildUser is null ? user : buildUser.Build(); + return builder is null ? context : builder.Build(); } private string GetOrCreateAnonUserKey() diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index d3f9b089..ef92b9ca 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,9 +40,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index c96e2440..17be5a40 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -31,7 +31,7 @@ public sealed class LdClient : ILdClient // Immutable client state readonly Configuration _config; - readonly LdClientContext _context; + readonly LdClientContext _clientContext; readonly IDataSourceStatusProvider _dataSourceStatusProvider; readonly IDataSourceUpdateSink _dataSourceUpdateSink; readonly FlagDataManager _dataStore; @@ -47,15 +47,15 @@ public sealed class LdClient : ILdClient // Mutable client state (some state is also in the ConnectionManager) readonly ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim(); - volatile User _user; + private Context _context; /// /// The singleton instance used by your application throughout its lifetime. Once this exists, you cannot /// create a new client instance unless you first call on this one. /// /// - /// Use the static factory methods or - /// to set this instance. + /// Use the static factory methods or + /// to set this instance. /// public static LdClient Instance => _instance; @@ -70,14 +70,14 @@ public sealed class LdClient : ILdClient public Configuration Config => _config; /// - /// The current user for all SDK operations. + /// The current evaluation context for all SDK operations. /// /// - /// This is initially the user specified for or - /// , but can be changed later with - /// or . + /// This is initially the context specified for or + /// , but can be changed later with + /// or . /// - public User User => LockUtils.WithReadLock(_stateLock, () => _user); + public Context Context => LockUtils.WithReadLock(_stateLock, () => _context); /// public bool Offline => _connectionManager.ForceOffline; @@ -119,28 +119,23 @@ public sealed class LdClient : ILdClient // without using WithConfigAnduser(config, user) LdClient() { } - LdClient(Configuration configuration, User user, TimeSpan startWaitTime) + LdClient(Configuration configuration, Context initialContext, TimeSpan startWaitTime) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); var diagnosticStore = _config.DiagnosticOptOut ? null : new ClientDiagnosticStore(_config, startWaitTime); var diagnosticDisabler = _config.DiagnosticOptOut ? null : new DiagnosticDisablerImpl(); - _context = new LdClientContext(configuration, this, diagnosticStore, diagnosticDisabler); - _log = _context.BaseLogger; - _taskExecutor = _context.TaskExecutor; - diagnosticStore?.SetContext(_context); + _clientContext = new LdClientContext(configuration, this, diagnosticStore, diagnosticDisabler); + _log = _clientContext.BaseLogger; + _taskExecutor = _clientContext.TaskExecutor; + diagnosticStore?.SetContext(_clientContext); _log.Info("Starting LaunchDarkly Client {0}", Version); var persistenceConfiguration = (configuration.PersistenceConfigurationBuilder ?? Components.Persistence()) - .CreatePersistenceConfiguration(_context); + .CreatePersistenceConfiguration(_clientContext); _dataStore = new FlagDataManager( configuration.MobileKey, persistenceConfiguration, @@ -149,16 +144,16 @@ public sealed class LdClient : ILdClient _userDecorator = new UserDecorator(configuration.DeviceInfo ?? new DefaultDeviceInfo(), _dataStore.PersistentStore); - _user = _userDecorator.DecorateUser(user); + _context = _userDecorator.DecorateContext(initialContext); - // If we had cached data for the new user, set the current in-memory flag data state to use + // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the // last known values. - var cachedData = _dataStore.GetCachedData(_user); + var cachedData = _dataStore.GetCachedData(_context); if (cachedData != null) { - _log.Debug("Cached flag data is available for this user"); - _dataStore.Init(_user, cachedData.Value, false); // false means "don't rewrite the flags to persistent storage" + _log.Debug("Cached flag data is available for this context"); + _dataStore.Init(_context, cachedData.Value, false); // false means "don't rewrite the flags to persistent storage" } var dataSourceUpdateSink = new DataSourceUpdateSinkImpl( @@ -180,17 +175,17 @@ public sealed class LdClient : ILdClient diagnosticDisabler?.SetDisabled(!isConnected || configuration.Offline); _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) - .CreateEventProcessor(_context); + .CreateEventProcessor(_clientContext); _eventProcessor.SetOffline(configuration.Offline || !isConnected); _connectionManager = new ConnectionManager( - _context, + _clientContext, dataSourceFactory, _dataSourceUpdateSink, _eventProcessor, diagnosticDisabler, configuration.EnableBackgroundUpdating, - _user, + _context, _log ); _connectionManager.SetForceOffline(configuration.Offline); @@ -213,7 +208,7 @@ public sealed class LdClient : ILdClient _eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { Timestamp = UnixMillisecondTime.Now, - User = user + Context = _context }); } @@ -247,9 +242,9 @@ async Task StartAsync() /// an property of . /// /// - /// If you would rather this happen asynchronously, use . To + /// If you would rather this happen asynchronously, use . To /// specify additional configuration options rather than just the mobile key, use - /// or . + /// or . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -258,15 +253,15 @@ async Task StartAsync() /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the user needed for client operations (must not be ); + /// the user needed for client operations (must not be ); /// if the user's is and /// is , it will be assigned a key that uniquely identifies this device /// the maximum length of time to wait for the client to initialize - public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) + public static LdClient Init(string mobileKey, Context initialContext, TimeSpan maxWaitTime) { var config = Configuration.Default(mobileKey); - return Init(config, user, maxWaitTime); + return Init(config, initialContext, maxWaitTime); } /// @@ -284,14 +279,14 @@ public static LdClient Init(string mobileKey, User user, TimeSpan maxWaitTime) /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the user needed for client operations (must not be ); + /// the user needed for client operations; /// if the user's is and /// is , it will be assigned a key that uniquely identifies this device - public static async Task InitAsync(string mobileKey, User user) + public static async Task InitAsync(string mobileKey, Context initialContext) { var config = Configuration.Default(mobileKey); - return await InitAsync(config, user); + return await InitAsync(config, initialContext); } /// @@ -306,9 +301,9 @@ public static async Task InitAsync(string mobileKey, User user) /// an property of . /// /// - /// If you would rather this happen asynchronously, use . + /// If you would rather this happen asynchronously, use . /// If you do not need to specify configuration options other than the mobile key, you can use - /// or . + /// or . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -317,19 +312,19 @@ public static async Task InitAsync(string mobileKey, User user) /// /// The singleton LdClient instance. /// The client configuration object - /// The user needed for client operations. Must not be null. + /// The initial evaluation context. /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. /// The maximum length of time to wait for the client to initialize. /// If this time elapses, the method will not throw an exception but will return the client in /// an uninitialized state. - public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTime) + public static LdClient Init(Configuration config, Context initialContext, TimeSpan maxWaitTime) { if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) { throw new ArgumentOutOfRangeException(nameof(maxWaitTime)); } - var c = CreateInstance(config, user, maxWaitTime); + var c = CreateInstance(config, initialContext, maxWaitTime); c.Start(maxWaitTime); return c; } @@ -341,21 +336,21 @@ public static LdClient Init(Configuration config, User user, TimeSpan maxWaitTim /// from the LaunchDarkly service. /// /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient + /// to instantiate the single instance of LdClient /// for the lifetime of your application. /// /// The singleton LdClient instance. - /// The client configuration object - /// The user needed for client operations. Must not be null. + /// The client configuration object. + /// The initial evaluation context. /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - public static async Task InitAsync(Configuration config, User user) + public static async Task InitAsync(Configuration config, Context initialContext) { - var c = CreateInstance(config, user, TimeSpan.Zero); + var c = CreateInstance(config, initialContext, TimeSpan.Zero); await c.StartAsync(); return c; } - static LdClient CreateInstance(Configuration configuration, User user, TimeSpan maxWaitTime) + static LdClient CreateInstance(Configuration configuration, Context initialContext, TimeSpan maxWaitTime) { lock (_createInstanceLock) { @@ -364,7 +359,7 @@ static LdClient CreateInstance(Configuration configuration, User user, TimeSpan throw new Exception("LdClient instance already exists."); } - var c = new LdClient(configuration, user, maxWaitTime); + var c = new LdClient(configuration, initialContext, maxWaitTime); _instance = c; return c; } @@ -468,14 +463,14 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (!Initialized) { _log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); - SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, User, defaultJson, + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, defaultJson, EvaluationErrorKind.ClientNotReady)); return errorResult(EvaluationErrorKind.ClientNotReady); } else { _log.Info("Unknown feature flag {0}; returning default value", featureKey); - SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, User, defaultJson, + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, defaultJson, EvaluationErrorKind.FlagNotFound)); return errorResult(EvaluationErrorKind.FlagNotFound); } @@ -508,7 +503,7 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => result = new EvaluationDetail(converter.ToType(flag.Value), flag.Variation, flag.Reason ?? EvaluationReason.OffReason); } } - var featureEvent = eventFactory.NewEvaluationEvent(featureKey, flag, User, + var featureEvent = eventFactory.NewEvaluationEvent(featureKey, flag, Context, new EvaluationDetail(valueJson, flag.Variation, flag.Reason ?? EvaluationReason.OffReason), defaultJson); SendEvaluationEventIfOnline(featureEvent); return result; @@ -538,7 +533,7 @@ public void Track(string eventName, LdValue data, double metricValue) { Timestamp = UnixMillisecondTime.Now, EventKey = eventName, - User = User, + Context = Context, Data = data, MetricValue = metricValue }); @@ -551,7 +546,7 @@ public void Track(string eventName, LdValue data) { Timestamp = UnixMillisecondTime.Now, EventKey = eventName, - User = User, + Context = Context, Data = data }); } @@ -569,47 +564,37 @@ public void Flush() } /// - public bool Identify(User user, TimeSpan maxWaitTime) + public bool Identify(Context context, TimeSpan maxWaitTime) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - return AsyncUtils.WaitSafely(() => IdentifyAsync(user), maxWaitTime); + return AsyncUtils.WaitSafely(() => IdentifyAsync(context), maxWaitTime); } /// - public async Task IdentifyAsync(User user) + public async Task IdentifyAsync(Context context) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - User newUser = _userDecorator.DecorateUser(user); - User oldUser = newUser; // this initialization is overwritten below, it's only here to satisfy the compiler + Context newContext = _userDecorator.DecorateContext(context); + Context oldContext = newContext; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => { - oldUser = _user; - _user = newUser; + oldContext = _context; + _context = newContext; }); - // If we had cached data for the new user, set the current in-memory flag data state to use + // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the // last known values. If we did not have cached data, then we update the current in-memory // state to reflect that there is no flag data, so that Variation calls done before completion - // will receive default values rather than the previous user's values. This does not modify + // will receive default values rather than the previous context's values. This does not modify // any flags in persistent storage, and (currently) it does *not* trigger any FlagValueChanged // events from FlagTracker. - var cachedData = _dataStore.GetCachedData(newUser); + var cachedData = _dataStore.GetCachedData(newContext); if (cachedData != null) { - _log.Debug("Identify found cached flag data for the new user"); + _log.Debug("Identify found cached flag data for the new context"); } _dataStore.Init( - newUser, + newContext, cachedData ?? new DataStoreTypes.FullDataSet(null), false // false means "don't rewrite the flags to persistent storage" ); @@ -617,10 +602,10 @@ public async Task IdentifyAsync(User user) EventProcessorIfEnabled().RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { Timestamp = UnixMillisecondTime.Now, - User = user + Context = newContext }); - return await _connectionManager.SetUser(newUser); + return await _connectionManager.SetContext(newContext); } /// diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs index ae1a66f4..7f24efd1 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs @@ -238,7 +238,7 @@ public ConnectivityBroadcastReceiver() public ConnectivityBroadcastReceiver(Action onChanged) => this.onChanged = onChanged; - public override async void OnReceive(Context context, Intent intent) + public override async void OnReceive(Android.Content.Context context, Intent intent) { if (intent.Action != ConnectivityManager.ConnectivityAction) return; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs index f2a1dbde..7972425f 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs @@ -40,7 +40,7 @@ internal static partial class Platform { //static ActivityLifecycleContextListener lifecycleListener; - internal static Context AppContext => + internal static Android.Content.Context AppContext => Application.Context; //internal static Activity GetCurrentActivity(bool throwOnNull) @@ -92,7 +92,7 @@ internal static bool HasApiLevel(BuildVersionCodes versionCode) => // AppContext.GetSystemService(Context.CameraService) as CameraManager; internal static ConnectivityManager ConnectivityManager => - AppContext.GetSystemService(Context.ConnectivityService) as ConnectivityManager; + AppContext.GetSystemService(Android.Content.Context.ConnectivityService) as ConnectivityManager; //internal static Vibrator Vibrator => // AppContext.GetSystemService(Context.VibratorService) as Vibrator; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs index 93ba998d..86764cbb 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -1,4 +1,6 @@ -using LaunchDarkly.Logging; +using System; +using System.Text; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Json; using Xunit; using Xunit.Sdk; @@ -20,21 +22,21 @@ public static void DataItemsEqual(ItemDescriptor expected, ItemDescriptor actual Assert.Equal(expected.Version, actual.Version); } - public static void UsersEqual(User expected, User actual) => + public static void ContextsEqual(Context expected, Context actual) => AssertJsonEqual(LdJsonSerialization.SerializeObject(expected), LdJsonSerialization.SerializeObject(actual)); - public static void UsersEqualExcludingAutoProperties(User expected, User actual) + public static void ContextsEqualExcludingAutoProperties(Context expected, Context actual) { - var builder = User.Builder(expected); + var builder = Context.BuilderFromContext(expected); foreach (var autoProp in new string[] { "device", "os" }) { - if (!actual.GetAttribute(UserAttribute.ForName(autoProp)).IsNull) + if (!actual.GetValue(autoProp).IsNull) { - builder.Custom(autoProp, actual.GetAttribute(UserAttribute.ForName(autoProp))); + builder.Set(autoProp, actual.GetValue(autoProp)); } } - UsersEqual(builder.Build(), actual); + ContextsEqual(builder.Build(), actual); } public static void LogMessageRegex(LogCapture logCapture, bool shouldHave, LogLevel level, string pattern) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 9ff892de..6d90fa43 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -1,7 +1,6 @@ using System; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Internal; using Xunit; using Xunit.Abstractions; @@ -12,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client public class BaseTest : IDisposable { protected const string BasicMobileKey = "mobile-key"; - protected static readonly User BasicUser = User.WithKey("user-key"); + protected static readonly Context BasicUser = Context.New("user-key"); protected readonly LoggingConfigurationBuilder testLogging; protected readonly Logger testLogger; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 924dd83a..821cba4e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -116,7 +116,7 @@ public void Persistence() { var prop = _tester.Property(c => c.PersistenceConfigurationBuilder, (b, v) => b.Persistence(v)); prop.AssertDefault(null); - prop.AssertCanSet(Components.Persistence().MaxCachedUsers(2)); + prop.AssertCanSet(Components.Persistence().MaxCachedContexts(2)); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs index 6ea24bbc..95d2cfe4 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ILdClientExtensionsTest.cs @@ -146,10 +146,10 @@ public EvaluationDetail DoubleVariationDetail(string key, double default public void Flush() { } - public bool Identify(User user, System.TimeSpan maxWaitTime) => + public bool Identify(Context context, System.TimeSpan maxWaitTime) => throw new System.NotImplementedException(); - public Task IdentifyAsync(User user) => + public Task IdentifyAsync(Context context) => throw new System.NotImplementedException(); public int IntVariation(string key, int defaultValue = 0) => diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs index 1b5ed029..f9a8e857 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/EventProcessorBuilderTest.cs @@ -56,13 +56,12 @@ public void PrivateAttributes() { var b = _tester.New(); Assert.Empty(b._privateAttributes); - b.PrivateAttributes(UserAttribute.Name); - b.PrivateAttributes(UserAttribute.Email, UserAttribute.ForName("other")); - b.PrivateAttributeNames("country"); + b.PrivateAttributes("name"); + b.PrivateAttributes("/address/street", "other"); Assert.Equal( - new HashSet + new HashSet { - UserAttribute.Name, UserAttribute.Email, UserAttribute.Country, UserAttribute.ForName("other") + AttributeRef.FromPath("name"), AttributeRef.FromPath("/address/street"), AttributeRef.FromPath("other") }, b._privateAttributes); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index 20242309..f5675342 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations { public class TestDataTest : BaseTest { - private static readonly User _initialUser = User.WithKey("user0"); + private static readonly Context _initialUser = Context.New("user0"); private readonly TestData _td = TestData.DataSource(); private readonly MockDataSourceUpdateSink _updates = new MockDataSourceUpdateSink(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs index 6692be60..09b100b5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs @@ -8,7 +8,7 @@ public class TestDataWithClientTest : BaseTest { private readonly TestData _td = TestData.DataSource(); private readonly Configuration _config; - private readonly User _user = User.WithKey("userkey"); + private readonly Context _user = Context.New("userkey"); public TestDataWithClientTest(ITestOutputHelper testOutput) : base(testOutput) { @@ -60,11 +60,11 @@ public void CanSetValuePerUser() .VariationForUser("user1", LdValue.Of("green")) .VariationForUser("user2", LdValue.Of("blue")) .VariationFunc(user => - user.GetAttribute(UserAttribute.ForName("favoriteColor")) + user.GetValue("favoriteColor") )); - var user1 = User.WithKey("user1"); - var user2 = User.WithKey("user2"); - var user3 = User.Builder("user3").Custom("favoriteColor", "green").Build(); + var user1 = Context.New("user1"); + var user2 = Context.New("user2"); + var user3 = Context.Builder("user3").Set("favoriteColor", "green").Build(); using (var client = LdClient.Init(_config, user1, TimeSpan.FromSeconds(1))) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs index ce69369c..6384b1ea 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataModelSerializationTest.cs @@ -9,12 +9,12 @@ namespace LaunchDarkly.Sdk.Client.Internal public class DataModelSerializationTest { [Fact] - public void SerializeUser() + public void SerializeContext() { - var user = User.Builder("user-key") - .FirstName("Lucy").LastName("Cat").Build(); + var user = Context.Builder("user-key") + .Set("firstName", "Lucy").Set("lastName", "Cat").Build(); AssertJsonEqual(LdJsonSerialization.SerializeObject(user), - DataModelSerialization.SerializeUser(user)); + DataModelSerialization.SerializeContext(user)); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 02f15aae..83699fae 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -13,8 +13,8 @@ public class DataSourceUpdateSinkImplTest : BaseTest private readonly FlagDataManager _store; private readonly FlagTrackerImpl _flagTracker; private readonly DataSourceUpdateSinkImpl _updateSink; - private readonly User _basicUser = User.WithKey("user-key"); - private readonly User _otherUser = User.WithKey("other-key"); + private readonly Context _basicUser = Context.New("user-key"); + private readonly Context _otherUser = Context.New("other-key"); public DataSourceUpdateSinkImplTest(ITestOutputHelper testOutput) : base(testOutput) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 82fe09fd..750e505e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -22,8 +22,7 @@ public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput // User key constructed to test base64 encoding that differs between the standard and "URL and Filename safe" // base64 encodings from RFC4648. We need to use the URL safe encoding for flag requests. - private static readonly User _user = User.WithKey("foo_bar__?"); - private const string _encodedUser = "eyJrZXkiOiJmb29fYmFyX18_In0="; + private static readonly Context _context = Context.New("foo_bar__?"); // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. @@ -51,7 +50,7 @@ string expectedQuery using (var requestor = new FeatureFlagRequestor( baseUri, - _user, + _context, withReasons, new LdClientContext(config).Http, testLogger)) @@ -62,7 +61,7 @@ string expectedQuery var req = server.Recorder.RequireRequest(); Assert.Equal("GET", req.Method); - Assert.Equal(expectedPathWithoutUser + _encodedUser, req.Path); + AssertHelpers.ContextsEqual(_context, TestUtil.Base64ContextFromUrlPath(req.Path, expectedPathWithoutUser)); Assert.Equal(expectedQuery, req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); Assert.Equal("", req.Body); @@ -96,7 +95,7 @@ string expectedQuery using (var requestor = new FeatureFlagRequestor( baseUri, - _user, + _context, withReasons, new LdClientContext(config).Http, testLogger)) @@ -110,8 +109,7 @@ string expectedQuery Assert.Equal(expectedPath, req.Path); Assert.Equal(expectedQuery, req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); - AssertJsonEqual(LdJsonSerialization.SerializeObject(_user), - TestUtil.NormalizeJsonUser(LdValue.Parse(req.Body))); + AssertJsonEqual(LdJsonSerialization.SerializeObject(_context), req.Body); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 1233a0e4..cf919cfa 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -19,12 +19,11 @@ public class PollingDataSourceTest : BaseTest private static FullDataSet AllData => new DataSetBuilder().Add("flag1", Flag).Build(); private static readonly TimeSpan BriefInterval = TimeSpan.FromMilliseconds(20); - private static readonly User simpleUser = User.WithKey("me"); - private const string encodedSimpleUser = "eyJrZXkiOiJtZSJ9"; + private static readonly Context simpleUser = Context.New("me"); private readonly MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); - private IDataSource MakeDataSource(Uri baseUri, User user, Action modConfig = null) + private IDataSource MakeDataSource(Uri baseUri, Context context, Action modConfig = null) { var builder = BasicConfig() .DataSource(Components.PollingDataSource()) @@ -32,7 +31,7 @@ private IDataSource MakeDataSource(Uri baseUri, User user, Action modConfig = null) + private IDataSource MakeDataSource(Uri baseUri, Context context, Action modConfig = null) { var builder = BasicConfig() .DataSource(Components.StreamingDataSource().InitialReconnectDelay(BriefReconnectDelay)) @@ -34,17 +33,17 @@ private IDataSource MakeDataSource(Uri baseUri, User user, Action action) @@ -81,7 +80,7 @@ string expectedQuery { dataSource.Start(); var req = server.Recorder.RequireRequest(); - Assert.Equal(expectedPathWithoutUser + encodedSimpleUser, req.Path); + AssertHelpers.ContextsEqual(simpleUser, TestUtil.Base64ContextFromUrlPath(req.Path, expectedPathWithoutUser)); Assert.Equal(expectedQuery, req.Query); Assert.Equal("GET", req.Method); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index 8fa1296e..219cd5ce 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -9,7 +9,7 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores { public class FlagDataManagerWithPersistenceTest : BaseTest { - private static readonly User OtherUser = User.WithKey("other-user"); + private static readonly Context OtherUser = Context.New("other-user"); private static readonly FullDataSet DataSet1 = new DataSetBuilder() .Add("flag1", 1, LdValue.Of(true), 0) .Add("flag2", 2, LdValue.Of(false), 1) @@ -124,7 +124,7 @@ public void InitEvictsLeastRecentUser() var dataSet3 = new DataSetBuilder() .Add("flag4", 4, LdValue.Of(false), 1) .Build(); - var user3 = User.WithKey("third-user"); + var user3 = Context.New("third-user"); var store = MakeStore(2); store.Init(BasicUser, DataSet1, true); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs index 3a69265c..b7e0aadd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -33,21 +33,21 @@ public PersistentDataStoreWrapperTest(ITestOutputHelper testOutput) : base(testO } [Fact] - public void GetUserDataForUnknownUser() + public void GetContextDataForUnknownContext() { - var data = _wrapper.GetUserData(UserKey); + var data = _wrapper.GetContextData(UserKey); Assert.Null(data); Assert.Empty(logCapture.GetMessages()); } [Fact] - public void GetUserDataForKnownUserWithValidData() + public void GetContextDataForKnownContextWithValidData() { var expectedData = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); var serializedData = expectedData.ToJsonString(); _persistentStore.SetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey, serializedData); - var data = _wrapper.GetUserData(UserHash); + var data = _wrapper.GetContextData(UserHash); Assert.NotNull(data); AssertHelpers.DataSetsEqual(expectedData, data.Value); Assert.Empty(logCapture.GetMessages()); @@ -58,7 +58,7 @@ public void SetUserData() { var data = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); - _wrapper.SetUserData(UserHash, data); + _wrapper.SetContextData(UserHash, data); var serializedData = _persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey); AssertJsonEqual(data.ToJsonString(), serializedData); @@ -69,10 +69,10 @@ public void RemoveUserData() { var data = new DataSetBuilder().Add("flagkey", 1, LdValue.Of(true), 0).Build(); - _wrapper.SetUserData(UserHash, data); + _wrapper.SetContextData(UserHash, data); Assert.NotNull(_persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey)); - _wrapper.RemoveUserData(UserHash); + _wrapper.RemoveContextData(UserHash); Assert.Null(_persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedUserFlagsKey)); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index 2a295b1c..ac4f9ca6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -21,8 +21,8 @@ namespace LaunchDarkly.Sdk.Client // expected ways. public class LdClientEndToEndTests : BaseTest { - private static readonly User _user = User.WithKey("foo"); - private static readonly User _otherUser = User.WithKey("bar"); + private static readonly Context _user = Context.New("foo"); + private static readonly Context _otherUser = Context.New("bar"); private static readonly FullDataSet _flagData1 = new DataSetBuilder() .Add("flag1", 1, LdValue.Of("value1"), 0) @@ -163,7 +163,7 @@ public void IdentifySwitchesUserAndGetsFlagsSync(UpdateMode mode) var success = client.Identify(_otherUser, TimeSpan.FromSeconds(5)); Assert.True(success); Assert.True(client.Initialized); - Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes + Assert.Equal(_otherUser.Key, client.Context.Key); // don't compare entire user, because SDK may have added device/os attributes var req2 = VerifyRequest(server.Recorder, mode); Assert.NotEqual(user1RequestPath, req2.Path); @@ -192,7 +192,7 @@ public async Task IdentifySwitchesUserAndGetsFlagsAsync(UpdateMode mode) var success = await client.IdentifyAsync(_otherUser); Assert.True(success); Assert.True(client.Initialized); - Assert.Equal(_otherUser.Key, client.User.Key); // don't compare entire user, because SDK may have added device/os attributes + Assert.Equal(_otherUser.Key, client.Context.Key); // don't compare entire user, because SDK may have added device/os attributes var req2 = VerifyRequest(server.Recorder, mode); Assert.NotEqual(user1RequestPath, req2.Path); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs index d01f09ec..780ae8d0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDiagnosticEventTest.cs @@ -383,7 +383,6 @@ public static LdValue.ObjectBuilder Base() => .Add("evaluationReasonsRequested", false) .Add("eventsCapacity", EventProcessorBuilder.DefaultCapacity) .Add("eventsFlushIntervalMillis", EventProcessorBuilder.DefaultFlushInterval.TotalMilliseconds) - .Add("inlineUsersInEvents", false) .Add("reconnectTimeMillis", StreamingDataSourceBuilder.DefaultInitialReconnectDelay.TotalMilliseconds) .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultResponseStartTimeout.TotalMilliseconds) .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs index a4c70c64..5bed3469 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEvaluationTests.cs @@ -9,7 +9,7 @@ public class LdClientEvaluationTests : BaseTest { const string flagKey = "flag-key"; const string nonexistentFlagKey = "some flag key"; - static readonly User user = User.WithKey("userkey"); + static readonly Context user = Context.New("userkey"); private readonly TestData _testData = TestData.DataSource(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 9b7c7eef..ea40e2cf 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -4,14 +4,13 @@ using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client { public class LdClientEventTests : BaseTest { - private static readonly User user = User.WithKey("userkey"); + private static readonly Context user = Context.New("userkey"); private readonly TestData _testData = TestData.DataSource(); private MockEventProcessor eventProcessor = new MockEventProcessor(); private IEventProcessorFactory _factory; @@ -21,16 +20,16 @@ public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) _factory = eventProcessor.AsSingletonFactory(); } - private LdClient MakeClient(User u) => + private LdClient MakeClient(Context c) => LdClient.Init(BasicConfig().DataSource(_testData).Events(_factory).Build(), - u, TimeSpan.FromSeconds(1)); + c, TimeSpan.FromSeconds(1)); [Fact] public void IdentifySendsIdentifyEvent() { using (LdClient client = MakeClient(user)) { - User user1 = User.WithKey("userkey1"); + Context user1 = Context.New("userkey1"); client.Identify(user1, TimeSpan.FromSeconds(1)); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), // there's always an initial identify event @@ -49,7 +48,7 @@ public void TrackSendsCustomEvent() e => { CustomEvent ce = Assert.IsType(e); Assert.Equal("eventkey", ce.EventKey); - Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(user.Key, ce.Context.Key); Assert.Equal(LdValue.Null, ce.Data); Assert.Null(ce.MetricValue); Assert.NotEqual(0, ce.Timestamp.Value); @@ -69,7 +68,7 @@ public void TrackWithDataSendsCustomEvent() e => { CustomEvent ce = Assert.IsType(e); Assert.Equal("eventkey", ce.EventKey); - Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(user.Key, ce.Context.Key); Assert.Equal(data, ce.Data); Assert.Null(ce.MetricValue); Assert.NotEqual(0, ce.Timestamp.Value); @@ -90,7 +89,7 @@ public void TrackWithMetricValueSendsCustomEvent() e => { CustomEvent ce = Assert.IsType(e); Assert.Equal("eventkey", ce.EventKey); - Assert.Equal(user.Key, ce.User.Key); + Assert.Equal(user.Key, ce.Context.Key); Assert.Equal(data, ce.Data); Assert.Equal(metricValue, ce.MetricValue); Assert.NotEqual(0, ce.Timestamp.Value); @@ -334,10 +333,10 @@ public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotIni } } - private void CheckIdentifyEvent(object e, User u) + private void CheckIdentifyEvent(object e, Context c) { IdentifyEvent ie = Assert.IsType(e); - Assert.Equal(u.Key, ie.User.Key); + Assert.Equal(c.Key, ie.Context.Key); Assert.NotEqual(0, ie.Timestamp.Value); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 1c6fb32c..158e8500 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -8,11 +8,9 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest { - private static readonly User KeylessAnonUser = - User.Builder((string)null) - .Anonymous(true) - .Email("example").AsPrivateAttribute() // give it some more attributes so we can verify they are preserved - .Custom("other", 3) + private static readonly Context KeylessAnonUser = TestUtil.BuildAutoContext() + .Set("email", "example") + .Set("other", 3) .Build(); public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } @@ -23,13 +21,6 @@ public void CannotCreateClientWithNullConfig() Assert.Throws(() => LdClient.Init((Configuration)null, BasicUser, TimeSpan.Zero)); } - [Fact] - public void CannotCreateClientWithNullUser() - { - var config = BasicConfig().Build(); - Assert.Throws(() => LdClient.Init(config, null, TimeSpan.Zero)); - } - [Fact] public void CannotCreateClientWithNegativeWaitTime() { @@ -56,9 +47,9 @@ public async void InitPassesUserToDataSource() using (var client = await LdClient.InitAsync(config, BasicUser)) { - var actualUser = client.User; // may have been transformed e.g. to add device/OS properties + var actualUser = client.Context; // may have been transformed e.g. to add device/OS properties Assert.Equal(BasicUser.Key, actualUser.Key); - Assert.Equal(actualUser, stub.ReceivedUser); + Assert.Equal(actualUser, stub.ReceivedContext); } } @@ -71,10 +62,10 @@ public async Task InitWithKeylessAnonUserAddsKey() using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - Assert.Equal("fake-device-id", client.User.Key); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + Assert.Equal("fake-device-id", client.Context.Key); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } } @@ -87,16 +78,16 @@ public async Task InitWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() string generatedKey = null; using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - generatedKey = client.User.Key; + generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + AssertHelpers.ContextsEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - Assert.Equal(generatedKey, client.User.Key); + Assert.Equal(generatedKey, client.Context.Key); } } #endif @@ -112,24 +103,24 @@ public async Task InitWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatfor string generatedKey = null; using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - generatedKey = client.User.Key; + generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - Assert.Equal(generatedKey, client.User.Key); + Assert.Equal(generatedKey, client.Context.Key); } // Now use a configuration where persistence is disabled - a different key is generated var configWithoutPersistence = BasicConfig().Persistence(Components.NoPersistence).Build(); using (var client = await TestUtil.CreateClientAsync(configWithoutPersistence, KeylessAnonUser)) { - Assert.NotNull(client.User.Key); - Assert.NotEqual(generatedKey, client.User.Key); + Assert.NotNull(client.Context.Key); + Assert.NotEqual(generatedKey, client.Context.Key); } } #endif @@ -145,9 +136,9 @@ public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() using (var client = await LdClient.InitAsync(config, KeylessAnonUser)) { - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(stub.ReceivedUser.Key).Build(), - stub.ReceivedUser); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(stub.ReceivedContext.Key).Build(), + stub.ReceivedContext); } } @@ -156,25 +147,25 @@ public void IdentifyUpdatesTheUser() { using (var client = TestUtil.CreateClient(BasicConfig().Build(), BasicUser)) { - var updatedUser = User.WithKey("some new key"); + var updatedUser = Context.New("some new key"); var success = client.Identify(updatedUser, TimeSpan.FromSeconds(1)); Assert.True(success); - Assert.Equal(client.User.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes + Assert.Equal(client.Context.Key, updatedUser.Key); // don't compare entire user, because SDK may have added device/os attributes } } [Fact] public Task IdentifyAsyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => client.IdentifyAsync(user)); + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, context) => client.IdentifyAsync(context)); [Fact] public Task IdentifySyncCompletesOnlyWhenNewFlagsAreAvailable() - => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, user) => Task.Run(() => client.Identify(user, TimeSpan.FromSeconds(4)))); + => IdentifyCompletesOnlyWhenNewFlagsAreAvailable((client, context) => Task.Run(() => client.Identify(context, TimeSpan.FromSeconds(4)))); - private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) + private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func identifyTask) { - var userA = User.WithKey("a"); - var userB = User.WithKey("b"); + var userA = Context.New("a"); + var userB = Context.New("b"); var flagKey = "flag"; var userAFlags = new DataSetBuilder() @@ -186,19 +177,19 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func - new MockDataSourceFromLambda(user, async () => + var dataSourceFactory = new MockDataSourceFactoryFromLambda((ctx, updates, context, bg) => + new MockDataSourceFromLambda(context, async () => { - switch (user.Key) + switch (context.Key) { case "a": - updates.Init(user, userAFlags); + updates.Init(context, userAFlags); break; case "b": startedIdentifyUserB.Release(); await canFinishIdentifyUserB.WaitAsync(); - updates.Init(user, userBFlags); + updates.Init(context, userBFlags); break; } })); @@ -231,30 +222,11 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func(() => client.Identify(null, TimeSpan.Zero)); - } - } - - [Fact] - public void IdentifyAsyncWithNullUserThrowsException() - { - using (var client = TestUtil.CreateClient(BasicConfig().Build(), BasicUser)) - { - Assert.ThrowsAsync(async () => await client.IdentifyAsync(null)); - // note that exceptions thrown out of an async task are always wrapped in AggregateException - } - } - [Fact] public async void IdentifyPassesUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); - User newUser = User.WithKey("new-user"); + Context newUser = Context.New("new-user"); var config = BasicConfig() .DataSource(stub.AsFactory()) @@ -262,13 +234,13 @@ public async void IdentifyPassesUserToDataSource() using (var client = await LdClient.InitAsync(config, BasicUser)) { - AssertHelpers.UsersEqualExcludingAutoProperties(BasicUser, client.User); - Assert.Equal(client.User, stub.ReceivedUser); + AssertHelpers.ContextsEqualExcludingAutoProperties(BasicUser, client.Context); + Assert.Equal(client.Context, stub.ReceivedContext); await client.IdentifyAsync(newUser); - AssertHelpers.UsersEqualExcludingAutoProperties(newUser, client.User); - Assert.Equal(client.User, stub.ReceivedUser); + AssertHelpers.ContextsEqualExcludingAutoProperties(newUser, client.Context); + Assert.Equal(client.Context, stub.ReceivedContext); } } @@ -282,10 +254,10 @@ public async Task IdentifyWithKeylessAnonUserAddsKey() { await client.IdentifyAsync(KeylessAnonUser); - Assert.Equal("fake-device-id", client.User.Key); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + Assert.Equal("fake-device-id", client.Context.Key); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } } @@ -300,18 +272,18 @@ public async Task IdentifyWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms { await client.IdentifyAsync(KeylessAnonUser); - generatedKey = client.User.Key; + generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + AssertHelpers.ContextsEqualExcludingAutoProperties( + User.Builder(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { client.Identify(KeylessAnonUser, TimeSpan.FromSeconds(1)); - Assert.Equal(generatedKey, client.User.Key); + Assert.Equal(generatedKey, client.Context.Key); } } #endif @@ -329,18 +301,18 @@ public async Task IdentifyWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePla { await client.IdentifyAsync(KeylessAnonUser); - generatedKey = client.User.Key; + generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); } using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { await client.IdentifyAsync(KeylessAnonUser); - Assert.Equal(generatedKey, client.User.Key); + Assert.Equal(generatedKey, client.Context.Key); } // Now use a configuration where persistence is disabled - a different key is generated @@ -349,8 +321,8 @@ public async Task IdentifyWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePla { await client.IdentifyAsync(KeylessAnonUser); - Assert.NotNull(client.User.Key); - Assert.NotEqual(generatedKey, client.User.Key); + Assert.NotNull(client.Context.Key); + Assert.NotEqual(generatedKey, client.Context.Key); } } #endif @@ -369,10 +341,10 @@ public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() { await client.IdentifyAsync(KeylessAnonUser); - AssertHelpers.UsersEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.User.Key).Build(), - client.User); - Assert.Equal(client.User, stub.ReceivedUser); + AssertHelpers.ContextsEqualExcludingAutoProperties( + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + client.Context); + Assert.Equal(client.Context, stub.ReceivedContext); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 10a7cdbe..85bc5fdc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -38,7 +38,7 @@ private class SingleDataSourceFactory : IDataSourceFactory { public IDataSource Instance { get; set; } public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, - User currentUser, bool inBackground) => Instance; + Context currentContext, bool inBackground) => Instance; } private class SingleEventProcessorFactory : IEventProcessorFactory @@ -99,14 +99,14 @@ internal class MockDataSourceUpdateSink : IDataSourceUpdateSink internal class ReceivedInit { public FullDataSet Data { get; set; } - public User User { get; set; } + public Context Context{ get; set; } } internal class ReceivedUpsert { public string Key { get; set; } public ItemDescriptor Data { get; set; } - public User User { get; set; } + public Context Context { get; set; } } internal class ReceivedStatusUpdate @@ -117,26 +117,26 @@ internal class ReceivedStatusUpdate public readonly EventSink Actions = new EventSink(); - public void Init(User user, FullDataSet data) => - Actions.Enqueue(new ReceivedInit { Data = data, User = user }); + public void Init(Context context, FullDataSet data) => + Actions.Enqueue(new ReceivedInit { Data = data, Context = context}); - public void Upsert(User user, string key, ItemDescriptor data) => - Actions.Enqueue(new ReceivedUpsert { Key = key, Data = data, User = user }); + public void Upsert(Context context, string key, ItemDescriptor data) => + Actions.Enqueue(new ReceivedUpsert { Key = key, Data = data, Context = context }); public void UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInfo? newError) => Actions.Enqueue(new ReceivedStatusUpdate { State = newState, Error = newError }); - public FullDataSet ExpectInit(User user) + public FullDataSet ExpectInit(Context context) { var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); - AssertHelpers.UsersEqual(user, action.User); + AssertHelpers.ContextsEqual(context, action.Context); return action.Data; } - public ItemDescriptor ExpectUpsert(User user, string key) + public ItemDescriptor ExpectUpsert(Context context, string key) { var action = Assert.IsType(Actions.ExpectValue(TimeSpan.FromSeconds(5))); - Assert.Equal(user, action.User); + AssertHelpers.ContextsEqual(context, action.Context); Assert.Equal(key, action.Key); return action.Data; } @@ -212,13 +212,13 @@ public void Flush() { } public void Dispose() { } - public void RecordEvaluationEvent(EventProcessorTypes.EvaluationEvent e) => + public void RecordEvaluationEvent(in EventProcessorTypes.EvaluationEvent e) => Events.Add(e); - public void RecordIdentifyEvent(EventProcessorTypes.IdentifyEvent e) => + public void RecordIdentifyEvent(in EventProcessorTypes.IdentifyEvent e) => Events.Add(e); - public void RecordCustomEvent(EventProcessorTypes.CustomEvent e) => + public void RecordCustomEvent(in EventProcessorTypes.CustomEvent e) => Events.Add(e); } @@ -313,11 +313,11 @@ public ImmutableList GetKeys(string storageNamespace) => private PersistentDataStoreWrapper WithWrapper(string mobileKey) => new PersistentDataStoreWrapper(this, mobileKey, Logs.None.Logger("")); - internal void SetupUserData(string mobileKey, string userKey, FullDataSet data) => - WithWrapper(mobileKey).SetUserData(Base64.UrlSafeSha256Hash(userKey), data); + internal void SetupUserData(string mobileKey, string contextKey, FullDataSet data) => + WithWrapper(mobileKey).SetContextData(Base64.UrlSafeSha256Hash(contextKey), data); - internal FullDataSet? InspectUserData(string mobileKey, string userKey) => - WithWrapper(mobileKey).GetUserData(Base64.UrlSafeSha256Hash(userKey)); + internal FullDataSet? InspectUserData(string mobileKey, string contextKey) => + WithWrapper(mobileKey).GetContextData(Base64.UrlSafeSha256Hash(contextKey)); internal UserIndex InspectUserIndex(string mobileKey) => WithWrapper(mobileKey).GetIndex(); @@ -327,7 +327,7 @@ internal class CapturingDataSourceFactory : IDataSourceFactory { internal IDataSourceUpdateSink UpdateSink; - public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, User currentUser, bool inBackground) + public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, Context currentContext, bool inBackground) { UpdateSink = updateSink; return new MockDataSourceThatNeverInitializes(); @@ -336,43 +336,43 @@ public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSi internal class MockDataSourceFactoryFromLambda : IDataSourceFactory { - private readonly Func _fn; + private readonly Func _fn; - internal MockDataSourceFactoryFromLambda(Func fn) + internal MockDataSourceFactoryFromLambda(Func fn) { _fn = fn; } - public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, User currentUser, bool inBackground) => - _fn(context, updateSink, currentUser, inBackground); + public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, Context currentContext, bool inBackground) => + _fn(context, updateSink, currentContext, inBackground); } internal class MockPollingProcessor : IDataSource { private IDataSourceUpdateSink _updateSink; - private User _user; + private Context _context; private FullDataSet? _data; - public User ReceivedUser => _user; + public Context ReceivedContext => _context; - public MockPollingProcessor(FullDataSet? data) : this(null, null, data) { } + public MockPollingProcessor(FullDataSet? data) : this(null, new Context(), data) { } - private MockPollingProcessor(IDataSourceUpdateSink updateSink, User user, FullDataSet? data) + private MockPollingProcessor(IDataSourceUpdateSink updateSink, Context context, FullDataSet? data) { _updateSink = updateSink; - _user = user; + _context = context; _data = data; } public static IDataSourceFactory Factory(FullDataSet? data) => - new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => - new MockPollingProcessor(updates, user, data)); + new MockDataSourceFactoryFromLambda((ctx, updates, context, bg) => + new MockPollingProcessor(updates, context, data)); public IDataSourceFactory AsFactory() => - new MockDataSourceFactoryFromLambda((ctx, updates, user, bg) => + new MockDataSourceFactoryFromLambda((ctx, updates, context, bg) => { this._updateSink = updates; - this._user = user; + this._context = context; return this; }); @@ -394,7 +394,7 @@ public Task Start() IsRunning = true; if (_updateSink != null && _data != null) { - _updateSink.Init(_user, _data.Value); + _updateSink.Init(_context, _data.Value); } return Task.FromResult(true); } @@ -402,13 +402,13 @@ public Task Start() internal class MockDataSourceFromLambda : IDataSource { - private readonly User _user; + private readonly Context _context; private readonly Func _startFn; private bool _initialized; - public MockDataSourceFromLambda(User user, Func startFn) + public MockDataSourceFromLambda(Context context, Func startFn) { - _user = user; + _context = context; _startFn = startFn; } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index af8acfe5..81e15104 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -1,8 +1,12 @@ using System; +using System.Text; using System.Threading; using System.Threading.Tasks; using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Json; +using Xunit; using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; @@ -19,6 +23,17 @@ public static class TestUtil public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key")); + public static Context Base64ContextFromUrlPath(string path, string pathPrefix) + { + Assert.StartsWith(pathPrefix, path); + var base64String = path.Substring(pathPrefix.Length); + var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(base64String)); + return LdJsonSerialization.DeserializeObject(decoded); + } + + public static ContextBuilder BuildAutoContext() => + Context.Builder(Constants.AutoKeyMagicValue).Transient(true); + public static T WithClientLock(Func f) { // This cumbersome combination of a ThreadLocal and a SemaphoreSlim is simply because 1. we have to use @@ -83,12 +98,12 @@ public static async Task WithClientLockAsync(Func> f) // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can // instantiate their own independent clients. Application code cannot do this because // the LdClient.Instance setter has internal scope. - public static LdClient CreateClient(Configuration config, User user, TimeSpan? timeout = null) + public static LdClient CreateClient(Configuration config, Context context, TimeSpan? timeout = null) { return WithClientLock(() => { ClearClient(); - LdClient client = LdClient.Init(config, user, timeout ?? TimeSpan.FromSeconds(1)); + LdClient client = LdClient.Init(config, context, timeout ?? TimeSpan.FromSeconds(1)); client.DetachInstance(); return client; }); @@ -97,12 +112,12 @@ public static LdClient CreateClient(Configuration config, User user, TimeSpan? t // Calls LdClient.Init, but then sets LdClient.Instance to null so other tests can // instantiate their own independent clients. Application code cannot do this because // the LdClient.Instance setter has internal scope. - public static async Task CreateClientAsync(Configuration config, User user) + public static async Task CreateClientAsync(Configuration config, Context context) { return await WithClientLockAsync(async () => { ClearClient(); - LdClient client = await LdClient.InitAsync(config, user); + LdClient client = await LdClient.InitAsync(config, context); client.DetachInstance(); return client; }); @@ -131,26 +146,5 @@ internal static string MakeJsonData(FullDataSet data) } return w.GetString(); } - - public static string NormalizeJsonUser(LdValue json) - { - // It's undefined whether a user with no custom attributes will have "custom":{} or not - if (json.Type == LdValueType.Object && json.Get("custom").Type == LdValueType.Object) - { - if (json.Count == 0) - { - var o1 = LdValue.BuildObject(); - foreach (var kv in json.AsDictionary(LdValue.Convert.Json)) - { - if (kv.Key != "custom") - { - o1.Add(kv.Key, kv.Value); - } - } - return o1.Build().ToJsonString(); - } - } - return json.ToJsonString(); - } } } From ac1781abe1a25111334b2c6e5033c25f8c06a089 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 14:23:23 -0700 Subject: [PATCH 397/499] fix tests --- .../AndroidSpecificTests.cs | 19 +++++++++---------- .../IOsSpecificTests.cs | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index 6ab62eb6..46667102 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -17,29 +17,28 @@ public void SdkReturnsAndroidPlatformType() [Fact] public void UserHasOSAndDeviceAttributesForPlatform() { - var baseUser = User.WithKey("key"); + var baseUser = Context.New("key"); var config = BasicConfig().Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { - var user = client.User; - Assert.Equal(baseUser.Key, user.Key); - Assert.Contains("os", user.Custom.Keys); - Assert.StartsWith("Android ", user.Custom["os"].AsString); - Assert.Contains("device", user.Custom.Keys); + var context = client.Context; + Assert.Equal(baseUser.Key, context.Key); + Assert.StartsWith("Android ", context.GetValue("os").AsString); + Assert.NotEqual(LdValue.Null, context.GetValue("device")); } } [Fact] public void CanGetUniqueUserKey() { - var anonUser = User.Builder((string)null).Anonymous(true).Build(); + var anonUser = Context.Builder(Internal.Constants.AutoKeyMagicValue).Transient(true).Build(); var config = BasicConfig() .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { - var user = client.User; - Assert.NotNull(user.Key); - Assert.NotEqual("", user.Key); + var context = client.Context; + Assert.NotNull(context.Key); + Assert.NotEqual("", context.Key); } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index 97228c5c..c7d93878 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -17,29 +17,28 @@ public void SdkReturnsIOsPlatformType() [Fact] public void UserHasOSAndDeviceAttributesForPlatform() { - var baseUser = User.WithKey("key"); + var baseUser = Context.New("key"); var config = BasicConfig().Build(); using (var client = TestUtil.CreateClient(config, baseUser)) { - var user = client.User; - Assert.Equal(baseUser.Key, user.Key); - Assert.Contains("os", user.Custom.Keys); - Assert.StartsWith("iOS ", user.Custom["os"].AsString); - Assert.Contains("device", user.Custom.Keys); + var context = client.Context; + Assert.Equal(baseUser.Key, context.Key); + Assert.StartsWith("iOS ", context.GetValue("os").AsString); + Assert.NotEqual(LdValue.Null, context.GetValue("device")); } } [Fact] public void CanGetUniqueUserKey() { - var anonUser = User.Builder((string)null).Anonymous(true).Build(); + var anonUser = Context.Builder(Internal.Constants.AutoKeyMagicValue).Transient(true).Build(); var config = BasicConfig() .DeviceInfo(null).Build(); using (var client = TestUtil.CreateClient(config, anonUser)) { - var user = client.User; - Assert.NotNull(user.Key); - Assert.NotEqual("", user.Key); + var context = client.Context; + Assert.NotNull(context.Key); + Assert.NotEqual("", context.Key); } } From 2a1fadd87d9c76ba8849a8477c36bd0bfc17dbcb Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 14:28:29 -0700 Subject: [PATCH 398/499] fix more tests --- tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 158e8500..38b03f04 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -81,7 +81,7 @@ public async Task InitWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() generatedKey = client.Context.Key; Assert.NotNull(generatedKey); AssertHelpers.ContextsEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -275,7 +275,7 @@ public async Task IdentifyWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms generatedKey = client.Context.Key; Assert.NotNull(generatedKey); AssertHelpers.ContextsEqualExcludingAutoProperties( - User.Builder(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } From 6d32fb817f8f75dd8b5fa4a6774b49fb5762c089 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 16:24:18 -0700 Subject: [PATCH 399/499] use correct context key for flag store + clean up context decorator implementation --- .../{UserDecorator.cs => ContextDecorator.cs} | 36 +++++++-------- .../{UserIndex.cs => ContextIndex.cs} | 46 +++++++++---------- .../Internal/DataStores/FlagDataManager.cs | 2 +- .../DataStores/PersistentDataStoreWrapper.cs | 10 ++-- src/LaunchDarkly.ClientSdk/LdClient.cs | 8 ++-- .../{UserIndexTest.cs => ContextIndexTest.cs} | 28 +++++------ .../FlagDataManagerWithPersistenceTest.cs | 8 ++-- .../PersistentDataStoreWrapperTest.cs | 4 +- .../MockComponents.cs | 2 +- 9 files changed, 72 insertions(+), 72 deletions(-) rename src/LaunchDarkly.ClientSdk/Internal/{UserDecorator.cs => ContextDecorator.cs} (72%) rename src/LaunchDarkly.ClientSdk/Internal/DataStores/{UserIndex.cs => ContextIndex.cs} (62%) rename tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/{UserIndexTest.cs => ContextIndexTest.cs} (82%) diff --git a/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs similarity index 72% rename from src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs rename to src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index 60d212a4..e1a4f1ef 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/UserDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -4,7 +4,7 @@ namespace LaunchDarkly.Sdk.Client.Internal { - internal class UserDecorator + internal class ContextDecorator { private readonly IDeviceInfo _deviceInfo; private readonly PersistentDataStoreWrapper _store; @@ -14,7 +14,7 @@ internal class UserDecorator private string _cachedAnonUserKey = null; private object _anonUserKeyLock = new object(); - public UserDecorator( + public ContextDecorator( IDeviceInfo deviceInfo, PersistentDataStoreWrapper store ) @@ -31,36 +31,36 @@ public Context DecorateContext(Context context) { ContextBuilder builder = null; - if (_deviceName != null) + Func lazyBuilder = () => { if (builder is null) { builder = Context.BuilderFromContext(context); } - builder.Set("device", _deviceName); + return builder; + }; + + if (_deviceName != null) + { + lazyBuilder().Set("device", _deviceName); } if (_osName != null) { - if (builder is null) - { - builder = Context.BuilderFromContext(context); - } - builder.Set("os", _osName); + lazyBuilder().Set("os", _osName); } - // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key - if (context.Key == Constants.AutoKeyMagicValue) + if (IsAutoContext(context)) { - if (builder is null) - { - builder = Context.BuilderFromContext(context); - } - var anonUserKey = GetOrCreateAnonUserKey(); - builder.Key(anonUserKey).Transient(true); + var anonKey = GetOrCreateAutoContextKey(); + lazyBuilder().Key(anonKey).Transient(true); } return builder is null ? context : builder.Build(); } - private string GetOrCreateAnonUserKey() + private bool IsAutoContext(Context context) => + context.Transient && context.Key == Constants.AutoKeyMagicValue; + // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key + + private string GetOrCreateAutoContextKey() { lock (_anonUserKeyLock) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs similarity index 62% rename from src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs rename to src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs index 36eeabbb..4193d8c6 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/UserIndex.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs @@ -7,34 +7,34 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores { /// - /// Used internally to track which users have flag data in the persistent store. + /// Used internally to track which contexts have flag data in the persistent store. /// - internal class UserIndex + internal class ContextIndex { internal ImmutableList Data { get; } internal struct IndexEntry { - public string UserId { get; set; } + public string ContextId { get; set; } public UnixMillisecondTime Timestamp { get; set; } } - internal UserIndex(ImmutableList data = null) + internal ContextIndex(ImmutableList data = null) { Data = data ?? ImmutableList.Empty; } - public UserIndex UpdateTimestamp(string userId, UnixMillisecondTime timestamp) + public ContextIndex UpdateTimestamp(string contextId, UnixMillisecondTime timestamp) { var builder = ImmutableList.CreateBuilder(); - builder.AddRange(Data.Where(e => e.UserId != userId)); - builder.Add(new IndexEntry { UserId = userId, Timestamp = timestamp }); - return new UserIndex(builder.ToImmutable()); + builder.AddRange(Data.Where(e => e.ContextId != contextId)); + builder.Add(new IndexEntry { ContextId = contextId, Timestamp = timestamp }); + return new ContextIndex(builder.ToImmutable()); } - public UserIndex Prune(int maxUsersToRetain, out IEnumerable removedUserIds) + public ContextIndex Prune(int maxContextsToRetain, out IEnumerable removedUserIds) { - if (Data.Count <= maxUsersToRetain) + if (Data.Count <= maxContextsToRetain) { removedUserIds = ImmutableList.Empty; return this; @@ -42,13 +42,13 @@ public UserIndex Prune(int maxUsersToRetain, out IEnumerable removedUser // The data will normally already be in ascending timestamp order, in which case this Sort // won't do anything, but this is just in case unsorted data somehow got persisted. var sorted = Data.Sort((e1, e2) => e1.Timestamp.CompareTo(e2.Timestamp)); - var numDrop = Data.Count - maxUsersToRetain; - removedUserIds = ImmutableList.CreateRange(sorted.Take(numDrop).Select(e => e.UserId)); - return new UserIndex(ImmutableList.CreateRange(sorted.Skip(numDrop))); + var numDrop = Data.Count - maxContextsToRetain; + removedUserIds = ImmutableList.CreateRange(sorted.Take(numDrop).Select(e => e.ContextId)); + return new ContextIndex(ImmutableList.CreateRange(sorted.Skip(numDrop))); } /// - /// Returns a JSON representation of the user index. + /// Returns a JSON representation of the context index. /// /// the JSON representation public string Serialize() @@ -60,7 +60,7 @@ public string Serialize() { using (var aw1 = aw0.Array()) { - aw1.String(e.UserId); + aw1.String(e.ContextId); aw1.Long(e.Timestamp.Value); } } @@ -69,17 +69,17 @@ public string Serialize() } /// - /// Parses the user index from a JSON representation. If the JSON string is null or - /// empty, it returns an empty user index. + /// Parses the context index from a JSON representation. If the JSON string is null or + /// empty, it returns an empty index. /// /// the JSON representation /// the parsed data /// if the JSON is malformed - public static UserIndex Deserialize(string json) + public static ContextIndex Deserialize(string json) { if (string.IsNullOrEmpty(json)) { - return new UserIndex(); + return new ContextIndex(); } var builder = ImmutableList.CreateBuilder(); try @@ -90,11 +90,11 @@ public static UserIndex Deserialize(string json) var ar1 = r.Array(); if (ar1.Next(ref r)) { - var userId = r.String(); + var contextId = r.String(); if (ar1.Next(ref r)) { var timeMillis = r.Long(); - builder.Add(new IndexEntry { UserId = userId, Timestamp = UnixMillisecondTime.OfMillis(timeMillis) }); + builder.Add(new IndexEntry { ContextId = contextId, Timestamp = UnixMillisecondTime.OfMillis(timeMillis) }); ar1.Next(ref r); } } @@ -102,9 +102,9 @@ public static UserIndex Deserialize(string json) } catch (Exception e) { - throw new FormatException("invalid stored user index", e); + throw new FormatException("invalid stored context index", e); } - return new UserIndex(builder.ToImmutable()); + return new ContextIndex(builder.ToImmutable()); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs index 0b100433..061e757a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs @@ -35,7 +35,7 @@ internal sealed class FlagDataManager : IDisposable private volatile ImmutableDictionary _flags = ImmutableDictionary.Empty; - private volatile UserIndex _storeIndex = null; + private volatile ContextIndex _storeIndex = null; private string _currentContextId = null; public PersistentDataStoreWrapper PersistentStore => _persistentStore; diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index 0d1ae236..f0bfa4da 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -75,25 +75,25 @@ public void SetContextData(string contextId, FullDataSet data) => public void RemoveContextData(string contextId) => HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, KeyForContextId(contextId), null)); - public UserIndex GetIndex() + public ContextIndex GetIndex() { string data = HandleErrorsAndLock(() => _persistentStore.GetValue(_environmentNamespace, EnvironmentMetadataKey)); if (data is null) { - return new UserIndex(); + return new ContextIndex(); } try { - return UserIndex.Deserialize(data); + return ContextIndex.Deserialize(data); } catch (Exception) { _log.Warn("Discarding invalid data from persistent store index"); - return new UserIndex(); + return new ContextIndex(); } } - public void SetIndex(UserIndex index) => + public void SetIndex(ContextIndex index) => HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, EnvironmentMetadataKey, index.Serialize())); public string GetAnonymousUserKey() => diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 17be5a40..b1590acf 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -41,7 +41,7 @@ public sealed class LdClient : ILdClient readonly IEventProcessor _eventProcessor; readonly IFlagTracker _flagTracker; readonly TaskExecutor _taskExecutor; - readonly UserDecorator _userDecorator; + readonly ContextDecorator _contextDecorator; private readonly Logger _log; @@ -142,9 +142,9 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataStoreSubLog) ); - _userDecorator = new UserDecorator(configuration.DeviceInfo ?? new DefaultDeviceInfo(), + _contextDecorator = new ContextDecorator(configuration.DeviceInfo ?? new DefaultDeviceInfo(), _dataStore.PersistentStore); - _context = _userDecorator.DecorateContext(initialContext); + _context = _contextDecorator.DecorateContext(initialContext); // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the @@ -572,7 +572,7 @@ public bool Identify(Context context, TimeSpan maxWaitTime) /// public async Task IdentifyAsync(Context context) { - Context newContext = _userDecorator.DecorateContext(context); + Context newContext = _contextDecorator.DecorateContext(context); Context oldContext = newContext; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/ContextIndexTest.cs similarity index 82% rename from tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs rename to tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/ContextIndexTest.cs index 818c4411..94e46c79 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/UserIndexTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/ContextIndexTest.cs @@ -6,12 +6,12 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores { - public class UserIndexTest + public class ContextIndexTest { [Fact] public void EmptyConstructor() { - var ui = new UserIndex(); + var ui = new ContextIndex(); Assert.NotNull(ui.Data); Assert.Empty(ui.Data); } @@ -19,7 +19,7 @@ public void EmptyConstructor() [Fact] public void Serialize() { - var ui = new UserIndex() + var ui = new ContextIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); @@ -33,7 +33,7 @@ public void Serialize() public void Deserialize() { var json = @"[[""user1"",1000],[""user2"",2000]]"; - var ui = UserIndex.Deserialize(json); + var ui = ContextIndex.Deserialize(json); Assert.NotNull(ui.Data); Assert.Collection(ui.Data, @@ -45,25 +45,25 @@ public void Deserialize() public void DeserializeMalformedJson() { Assert.ThrowsAny(() => - UserIndex.Deserialize("}")); + ContextIndex.Deserialize("}")); Assert.ThrowsAny(() => - UserIndex.Deserialize("[")); + ContextIndex.Deserialize("[")); Assert.ThrowsAny(() => - UserIndex.Deserialize("[[true,1000]]")); + ContextIndex.Deserialize("[[true,1000]]")); Assert.ThrowsAny(() => - UserIndex.Deserialize(@"[[""user1"",false]]")); + ContextIndex.Deserialize(@"[[""user1"",false]]")); Assert.ThrowsAny(() => - UserIndex.Deserialize("[3]")); + ContextIndex.Deserialize("[3]")); } [Fact] public void UpdateTimestampForExistingUser() { - var ui = new UserIndex() + var ui = new ContextIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); @@ -77,7 +77,7 @@ public void UpdateTimestampForExistingUser() [Fact] public void PruneRemovesLeastRecentUsers() { - var ui = new UserIndex() + var ui = new ContextIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)) .UpdateTimestamp("user3", UnixMillisecondTime.OfMillis(1111)) // deliberately out of order @@ -95,7 +95,7 @@ public void PruneRemovesLeastRecentUsers() [Fact] public void PruneWhenLimitIsNotExceeded() { - var ui = new UserIndex() + var ui = new ContextIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); @@ -106,10 +106,10 @@ public void PruneWhenLimitIsNotExceeded() Assert.Empty(removed2); } - private Action AssertEntry(string id, int millis) => + private Action AssertEntry(string id, int millis) => e => { - Assert.Equal(id, e.UserId); + Assert.Equal(id, e.ContextId); Assert.Equal(UnixMillisecondTime.OfMillis(millis), e.Timestamp); }; } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index 219cd5ce..cef0afe7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -111,10 +111,10 @@ public void InitUpdatesIndex() store.Init(BasicUser, DataSet1, true); store.Init(OtherUser, DataSet2, true); - var index = _persistentStore.InspectUserIndex(BasicMobileKey); + var index = _persistentStore.InspectContextIndex(BasicMobileKey); Assert.Equal( ImmutableList.Create(Base64.UrlSafeSha256Hash(BasicUser.Key), Base64.UrlSafeSha256Hash(OtherUser.Key)), - index.Data.Select(e => e.UserId).ToImmutableList() + index.Data.Select(e => e.ContextId).ToImmutableList() ); } @@ -131,10 +131,10 @@ public void InitEvictsLeastRecentUser() store.Init(OtherUser, DataSet2, true); store.Init(user3, dataSet3, true); - var index = _persistentStore.InspectUserIndex(BasicMobileKey); + var index = _persistentStore.InspectContextIndex(BasicMobileKey); Assert.Equal( ImmutableList.Create(Base64.UrlSafeSha256Hash(OtherUser.Key), Base64.UrlSafeSha256Hash(user3.Key)), - index.Data.Select(e => e.UserId).ToImmutableList() + index.Data.Select(e => e.ContextId).ToImmutableList() ); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs index b7e0aadd..eede63e3 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -79,7 +79,7 @@ public void RemoveUserData() [Fact] public void GetIndex() { - var expectedIndex = new UserIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); + var expectedIndex = new ContextIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); _persistentStore.SetValue(ExpectedEnvironmentNamespace, ExpectedIndexKey, expectedIndex.Serialize()); var index = _wrapper.GetIndex(); @@ -89,7 +89,7 @@ public void GetIndex() [Fact] public void SetIndex() { - var index = new UserIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); + var index = new ContextIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); _wrapper.SetIndex(index); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 85bc5fdc..501884ef 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -319,7 +319,7 @@ internal void SetupUserData(string mobileKey, string contextKey, FullDataSet dat internal FullDataSet? InspectUserData(string mobileKey, string contextKey) => WithWrapper(mobileKey).GetContextData(Base64.UrlSafeSha256Hash(contextKey)); - internal UserIndex InspectUserIndex(string mobileKey) => + internal ContextIndex InspectContextIndex(string mobileKey) => WithWrapper(mobileKey).GetIndex(); } From 359961c7e6054e73a0fc04edf1850b86130c9c1e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 16:35:01 -0700 Subject: [PATCH 400/499] add test for flag storage with single/multi-kind contexts --- .../FlagDataManagerWithPersistenceTest.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index cef0afe7..33e3f893 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -219,5 +219,35 @@ public void UpsertDoesNotUpdatePersistentStoreIfUpdateIsUnsuccessful() Assert.NotNull(data); AssertHelpers.DataSetsEqual(data1a, data.Value); } + + [Fact] + public void FlagsAreStoredByFullyQualifiedKeyForSingleAndMultiKindContexts() + { + // This tests that we are correctly disambiguating contexts based on their FullyQualifiedKey, + // which includes both key and kind information (and, for multi-kind contexts, is based on + // concatenating the individual kinds). If we were using only the Key property, these users + // would collide. + var contexts = new Context[] + { + Context.New("key1"), + Context.New("key2"), + Context.NewWithKind("kind2", "key1"), + Context.NewMulti(Context.NewWithKind("kind1", "key1"), Context.NewWithKind("kind2", "key2")), + Context.NewMulti(Context.NewWithKind("kind1", "key1"), Context.NewWithKind("kind2", "key3")) + }; + var store = MakeStore(contexts.Length); + for (var i = 0; i < contexts.Length; i++) + { + var initData = new DataSetBuilder().Add("flag", 1, LdValue.Of(i), 0).Build(); + store.Init(contexts[i], initData, true); + } + for (var i = 0; i < contexts.Length; i++) + { + var data = store.GetCachedData(contexts[i]); + Assert.NotNull(data); + var flagValue = data.Value.Items[0].Value.Item.Value; + Assert.Equal(LdValue.Of(i), flagValue); + } + } } } From dcd1f607983aface8976171da2a171b5f2f65acc Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 17:11:14 -0700 Subject: [PATCH 401/499] remove automatic "device" and "os" attributes --- .../Internal/ContextDecorator.cs | 29 ++----------------- .../PlatformSpecific/UserMetadata.android.cs | 6 ---- .../PlatformSpecific/UserMetadata.ios.cs | 23 --------------- .../UserMetadata.netstandard.cs | 4 --- .../PlatformSpecific/UserMetadata.shared.cs | 12 -------- .../AndroidSpecificTests.cs | 14 --------- .../AssertHelpers.cs | 13 --------- .../LdClientTests.cs | 20 ++++++------- .../IOsSpecificTests.cs | 14 --------- 9 files changed, 12 insertions(+), 123 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index e1a4f1ef..10f01936 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -8,8 +8,6 @@ internal class ContextDecorator { private readonly IDeviceInfo _deviceInfo; private readonly PersistentDataStoreWrapper _store; - private readonly string _deviceName; - private readonly string _osName; private string _cachedAnonUserKey = null; private object _anonUserKeyLock = new object(); @@ -21,39 +19,16 @@ PersistentDataStoreWrapper store { _deviceInfo = deviceInfo; _store = store; - - // Store platform-specific values in static fields to avoid having to compute them repeatedly - _deviceName = PlatformSpecific.UserMetadata.DeviceName; - _osName = PlatformSpecific.UserMetadata.OSName; } public Context DecorateContext(Context context) { - ContextBuilder builder = null; - - Func lazyBuilder = () => - { - if (builder is null) - { - builder = Context.BuilderFromContext(context); - } - return builder; - }; - - if (_deviceName != null) - { - lazyBuilder().Set("device", _deviceName); - } - if (_osName != null) - { - lazyBuilder().Set("os", _osName); - } if (IsAutoContext(context)) { var anonKey = GetOrCreateAutoContextKey(); - lazyBuilder().Key(anonKey).Transient(true); + return Context.BuilderFromContext(context).Key(anonKey).Transient(true).Build(); } - return builder is null ? context : builder.Build(); + return context; } private bool IsAutoContext(Context context) => diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs index af862191..1c1b6925 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs @@ -4,12 +4,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class UserMetadata { - private static string PlatformDevice => - Build.Model + " " + Build.Product; - - private static string PlatformOS => - "Android " + Build.VERSION.SdkInt; - private static PlatformType PlatformPlatformType => PlatformType.Android; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs index 8a67a79c..2bbeaa02 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs @@ -4,29 +4,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class UserMetadata { - private static string PlatformDevice - { - get - { - switch (UIDevice.CurrentDevice.UserInterfaceIdiom) - { - case UIUserInterfaceIdiom.CarPlay: - return "CarPlay"; - case UIUserInterfaceIdiom.Pad: - return "iPad"; - case UIUserInterfaceIdiom.Phone: - return "iPhone"; - case UIUserInterfaceIdiom.TV: - return "Apple TV"; - default: - return "unknown"; - } - } - } - - private static string PlatformOS => - "iOS " + UIDevice.CurrentDevice.SystemVersion; - private static PlatformType PlatformPlatformType => PlatformType.IOs; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs index 4c8fcebd..c792015e 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs @@ -3,10 +3,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class UserMetadata { - private static string PlatformDevice => null; - - private static string PlatformOS => null; - private static PlatformType PlatformPlatformType => PlatformType.Standard; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs index fa91742b..8e024056 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs @@ -5,18 +5,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class UserMetadata { - /// - /// Returns the string that should be passed in the "device" property for all users. - /// - /// The value for "device", or null if none. - internal static string DeviceName => PlatformDevice; - - /// - /// Returns the string that should be passed in the "os" property for all users. - /// - /// The value for "os", or null if none. - internal static string OSName => PlatformOS; - internal static PlatformType PlatformType => PlatformPlatformType; } } diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index 46667102..b4173e55 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -14,20 +14,6 @@ public void SdkReturnsAndroidPlatformType() Assert.Equal(PlatformType.Android, LdClient.PlatformType); } - [Fact] - public void UserHasOSAndDeviceAttributesForPlatform() - { - var baseUser = Context.New("key"); - var config = BasicConfig().Build(); - using (var client = TestUtil.CreateClient(config, baseUser)) - { - var context = client.Context; - Assert.Equal(baseUser.Key, context.Key); - Assert.StartsWith("Android ", context.GetValue("os").AsString); - Assert.NotEqual(LdValue.Null, context.GetValue("device")); - } - } - [Fact] public void CanGetUniqueUserKey() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs index 86764cbb..86b337c0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -26,19 +26,6 @@ public static void ContextsEqual(Context expected, Context actual) => AssertJsonEqual(LdJsonSerialization.SerializeObject(expected), LdJsonSerialization.SerializeObject(actual)); - public static void ContextsEqualExcludingAutoProperties(Context expected, Context actual) - { - var builder = Context.BuilderFromContext(expected); - foreach (var autoProp in new string[] { "device", "os" }) - { - if (!actual.GetValue(autoProp).IsNull) - { - builder.Set(autoProp, actual.GetValue(autoProp)); - } - } - ContextsEqual(builder.Build(), actual); - } - public static void LogMessageRegex(LogCapture logCapture, bool shouldHave, LogLevel level, string pattern) { if (logCapture.HasMessageWithRegex(level, pattern) != shouldHave) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 38b03f04..bea973e9 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -63,7 +63,7 @@ public async Task InitWithKeylessAnonUserAddsKey() using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { Assert.Equal("fake-device-id", client.Context.Key); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -80,7 +80,7 @@ public async Task InitWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() { generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -105,7 +105,7 @@ public async Task InitWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatfor { generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -136,7 +136,7 @@ public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() using (var client = await LdClient.InitAsync(config, KeylessAnonUser)) { - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(stub.ReceivedContext.Key).Build(), stub.ReceivedContext); } @@ -234,12 +234,12 @@ public async void IdentifyPassesUserToDataSource() using (var client = await LdClient.InitAsync(config, BasicUser)) { - AssertHelpers.ContextsEqualExcludingAutoProperties(BasicUser, client.Context); + AssertHelpers.ContextsEqual(BasicUser, client.Context); Assert.Equal(client.Context, stub.ReceivedContext); await client.IdentifyAsync(newUser); - AssertHelpers.ContextsEqualExcludingAutoProperties(newUser, client.Context); + AssertHelpers.ContextsEqual(newUser, client.Context); Assert.Equal(client.Context, stub.ReceivedContext); } } @@ -255,7 +255,7 @@ public async Task IdentifyWithKeylessAnonUserAddsKey() await client.IdentifyAsync(KeylessAnonUser); Assert.Equal("fake-device-id", client.Context.Key); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -274,7 +274,7 @@ public async Task IdentifyWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -303,7 +303,7 @@ public async Task IdentifyWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePla generatedKey = client.Context.Key; Assert.NotNull(generatedKey); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); } @@ -341,7 +341,7 @@ public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() { await client.IdentifyAsync(KeylessAnonUser); - AssertHelpers.ContextsEqualExcludingAutoProperties( + AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), client.Context); Assert.Equal(client.Context, stub.ReceivedContext); diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index c7d93878..08db3010 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -14,20 +14,6 @@ public void SdkReturnsIOsPlatformType() Assert.Equal(PlatformType.IOs, LdClient.PlatformType); } - [Fact] - public void UserHasOSAndDeviceAttributesForPlatform() - { - var baseUser = Context.New("key"); - var config = BasicConfig().Build(); - using (var client = TestUtil.CreateClient(config, baseUser)) - { - var context = client.Context; - Assert.Equal(baseUser.Key, context.Key); - Assert.StartsWith("iOS ", context.GetValue("os").AsString); - Assert.NotEqual(LdValue.Null, context.GetValue("device")); - } - } - [Fact] public void CanGetUniqueUserKey() { From 92634f9a3e8134852bfcf866d17f18edb516eddc Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 17:22:06 -0700 Subject: [PATCH 402/499] simplify usage of LdClientContext in client initialization --- .../Integrations/HttpConfigurationBuilder.cs | 12 +-- .../Interfaces/BasicConfiguration.cs | 23 ---- .../Interfaces/HttpConfiguration.cs | 3 + .../Interfaces/LdClientContext.cs | 100 +++++++++++++----- .../Internal/Events/ClientDiagnosticStore.cs | 17 +-- src/LaunchDarkly.ClientSdk/LdClient.cs | 7 +- .../HttpConfigurationBuilderTest.cs | 21 ++-- .../DataSources/StreamingDataSourceTest.cs | 2 +- 8 files changed, 103 insertions(+), 82 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 83965a02..da9d433f 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -233,11 +233,11 @@ public HttpConfigurationBuilder Wrapper(string wrapperName, string wrapperVersio /// Called internally by the SDK to create an implementation instance. Applications do not need /// to call this method. /// - /// provides the basic SDK configuration properties + /// provides SDK configuration data /// an - public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfiguration) => + public HttpConfiguration CreateHttpConfiguration(LdClientContext context) => new HttpConfiguration( - MakeHttpProperties(basicConfiguration), + MakeHttpProperties(context), _messageHandler, _responseStartTimeout, _useReport @@ -246,14 +246,14 @@ public HttpConfiguration CreateHttpConfiguration(BasicConfiguration basicConfigu /// public LdValue DescribeConfiguration(LdClientContext context) => LdValue.BuildObject() - .WithHttpProperties(MakeHttpProperties(context.Basic)) + .WithHttpProperties(MakeHttpProperties(context)) .Add("useReport", _useReport) .Set("socketTimeoutMillis", _responseStartTimeout.TotalMilliseconds) // WithHttpProperties normally sets socketTimeoutMillis to the ReadTimeout value, // which is more correct, but we can't really set ReadTimeout in this SDK .Build(); - private HttpProperties MakeHttpProperties(BasicConfiguration basic) + private HttpProperties MakeHttpProperties(LdClientContext context) { Func handlerFn; if (_messageHandler is null) @@ -266,7 +266,7 @@ private HttpProperties MakeHttpProperties(BasicConfiguration basic) } var httpProperties = HttpProperties.Default - .WithAuthorizationKey(basic.MobileKey) + .WithAuthorizationKey(context.MobileKey) .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs deleted file mode 100644 index 7a5298ea..00000000 --- a/src/LaunchDarkly.ClientSdk/Interfaces/BasicConfiguration.cs +++ /dev/null @@ -1,23 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.Interfaces -{ - /// - /// The most basic properties of the SDK client that are available to all SDK components. - /// - public sealed class BasicConfiguration - { - /// - /// The configured mobile key. - /// - public string MobileKey { get; } - - /// - /// Creates an instance. - /// - /// the configured mobile key - public BasicConfiguration(string mobileKey) - { - MobileKey = mobileKey; - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs index dbbac1c0..cb914072 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs @@ -181,5 +181,8 @@ IWebProxy proxy } return ret; } + + internal static HttpConfiguration Default() => + new HttpConfiguration(HttpProperties.Default, null, HttpConfigurationBuilder.DefaultResponseStartTimeout, false); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs index 609217e5..80a04a17 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs @@ -15,17 +15,13 @@ namespace LaunchDarkly.Sdk.Client.Interfaces /// may also include non-public properties that are relevant only when creating one of the built-in /// component types and are not accessible to custom components. /// - /// - /// Some properties are in instead because they are required in - /// situations where the has not been fully constructed yet. - /// /// public sealed class LdClientContext { /// - /// The basic properties common to all components. + /// The configured mobile key. /// - public BasicConfiguration Basic { get; } + public string MobileKey { get; } /// /// The configured logger for the SDK. @@ -64,35 +60,89 @@ public sealed class LdClientContext /// the SDK configuration public LdClientContext( Configuration configuration - ) : this(configuration, null, null, null) { } + ) : this(configuration, null) { } internal LdClientContext( - Configuration configuration, - object eventSender, + string mobileKey, + Logger baseLogger, + bool enableBackgroundUpdating, + bool evaluationReasons, + HttpConfiguration http, + ServiceEndpoints serviceEndpoints, + IDiagnosticDisabler diagnosticDisabler, IDiagnosticStore diagnosticStore, - IDiagnosticDisabler diagnosticDisabler + TaskExecutor taskExecutor ) { - this.Basic = new BasicConfiguration(configuration.MobileKey); + MobileKey = mobileKey; + BaseLogger = baseLogger; + EnableBackgroundUpdating = enableBackgroundUpdating; + EvaluationReasons = evaluationReasons; + Http = http; + ServiceEndpoints = serviceEndpoints ?? Components.ServiceEndpoints().Build(); + DiagnosticDisabler = diagnosticDisabler; + DiagnosticStore = diagnosticStore; + TaskExecutor = taskExecutor ?? new TaskExecutor(null, + PlatformSpecific.AsyncScheduler.ScheduleAction, + baseLogger + ); + } + + internal LdClientContext( + Configuration configuration, + object eventSender + ) : + this( + configuration.MobileKey, + MakeLogger(configuration), + configuration.EnableBackgroundUpdating, + configuration.EvaluationReasons, + (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) + .CreateHttpConfiguration(MakeMinimalContext(configuration.MobileKey)), + configuration.ServiceEndpoints, + null, + null, + new TaskExecutor( + eventSender, + PlatformSpecific.AsyncScheduler.ScheduleAction, + MakeLogger(configuration) + ) + ) { } + internal static Logger MakeLogger(Configuration configuration) + { var logConfig = (configuration.LoggingConfigurationBuilder ?? Components.Logging()) .CreateLoggingConfiguration(); var logAdapter = logConfig.LogAdapter ?? Logs.None; - this.BaseLogger = logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); + return logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); + } - this.EnableBackgroundUpdating = configuration.EnableBackgroundUpdating; - this.EvaluationReasons = configuration.EvaluationReasons; - this.Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) - .CreateHttpConfiguration(this.Basic); - this.ServiceEndpoints = configuration.ServiceEndpoints; + internal static LdClientContext MakeMinimalContext(string mobileKey) => + new LdClientContext( + mobileKey, + Logs.None.Logger(""), + false, + false, + HttpConfiguration.Default(), + null, + null, + null, + null + ); - this.DiagnosticStore = diagnosticStore; - this.DiagnosticDisabler = diagnosticDisabler; - this.TaskExecutor = new TaskExecutor( - eventSender, - PlatformSpecific.AsyncScheduler.ScheduleAction, - this.BaseLogger - ); - } + internal LdClientContext WithDiagnostics( + IDiagnosticDisabler newDiagnosticDisabler, + IDiagnosticStore newDiagnosticStore + ) => + new LdClientContext( + MobileKey, + BaseLogger, + EnableBackgroundUpdating, + EvaluationReasons, + Http, + ServiceEndpoints, + newDiagnosticDisabler, + newDiagnosticStore, + TaskExecutor); } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index 1bb455ef..759e3409 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -10,20 +10,20 @@ namespace LaunchDarkly.Sdk.Client.Internal.Events { internal class ClientDiagnosticStore : DiagnosticStoreBase { + private readonly LdClientContext _context; private readonly Configuration _config; private readonly TimeSpan _startWaitTime; - private LdClientContext _context; - - protected override string SdkKeyOrMobileKey => _context.Basic.MobileKey; + protected override string SdkKeyOrMobileKey => _context.MobileKey; protected override string SdkName => "dotnet-client-sdk"; protected override IEnumerable ConfigProperties => GetConfigProperties(); protected override string DotNetTargetFramework => GetDotNetTargetFramework(); protected override HttpProperties HttpProperties => _context.Http.HttpProperties; protected override Type TypeOfLdClient => typeof(LdClient); - internal ClientDiagnosticStore(Configuration config, TimeSpan startWaitTime) + internal ClientDiagnosticStore(LdClientContext context, Configuration config, TimeSpan startWaitTime) { + _context = context; _config = config; _startWaitTime = startWaitTime; // We pass in startWaitTime separately because in the client-side SDK, it is not @@ -35,15 +35,6 @@ internal ClientDiagnosticStore(Configuration config, TimeSpan startWaitTime) // data will be zero, since there is no meaningful value for it). } - internal void SetContext(LdClientContext context) - { - // This is done as a separate step, called from the LdClient constructor, because - // the DiagnosticStore object has to be created before the LdClientContext - since - // the LdClientContext includes a reference to the DiagnosticStore (for components - // like StreamingDataSource to use). - _context = context; - } - private IEnumerable GetConfigProperties() { yield return LdValue.BuildObject() diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index b1590acf..7beb4b84 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -122,15 +122,16 @@ public sealed class LdClient : ILdClient LdClient(Configuration configuration, Context initialContext, TimeSpan startWaitTime) { _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); + var baseContext = new LdClientContext(configuration, this); + var diagnosticStore = _config.DiagnosticOptOut ? null : - new ClientDiagnosticStore(_config, startWaitTime); + new ClientDiagnosticStore(baseContext, _config, startWaitTime); var diagnosticDisabler = _config.DiagnosticOptOut ? null : new DiagnosticDisablerImpl(); + _clientContext = baseContext.WithDiagnostics(diagnosticDisabler, diagnosticStore); - _clientContext = new LdClientContext(configuration, this, diagnosticStore, diagnosticDisabler); _log = _clientContext.BaseLogger; _taskExecutor = _clientContext.TaskExecutor; - diagnosticStore?.SetContext(_clientContext); _log.Info("Starting LaunchDarkly Client {0}", Version); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index b3d0cf7c..75999558 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -11,12 +11,11 @@ namespace LaunchDarkly.Sdk.Client.Integrations { public class HttpConfigurationBuilderTest { - private static readonly BasicConfiguration basicConfig = - new BasicConfiguration("mobile-key"); + private static readonly LdClientContext basicContext = LdClientContext.MakeMinimalContext("mobile-key"); private readonly BuilderBehavior.BuildTester _tester = BuilderBehavior.For(() => Components.HttpConfiguration(), - b => b.CreateHttpConfiguration(basicConfig)); + b => b.CreateHttpConfiguration(basicContext)); [Fact] public void ConnectTimeout() @@ -32,7 +31,7 @@ public void CustomHeaders() var config = Components.HttpConfiguration() .CustomHeader("header1", "value1") .CustomHeader("header2", "value2") - .CreateHttpConfiguration(basicConfig); + .CreateHttpConfiguration(basicContext); Assert.Equal("value1", HeadersAsMap(config.DefaultHeaders)["header1"]); Assert.Equal("value2", HeadersAsMap(config.DefaultHeaders)["header2"]); } @@ -48,8 +47,8 @@ public void MessageHandler() [Fact] public void MobileKeyHeader() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); - Assert.Equal(basicConfig.MobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); + Assert.Equal(basicContext.MobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); } [Fact] @@ -61,7 +60,7 @@ public void ResponseStartTimeout() prop.AssertCanSet(value); var config = Components.HttpConfiguration().ResponseStartTimeout(value) - .CreateHttpConfiguration(basicConfig); + .CreateHttpConfiguration(basicContext); using (var client = config.NewHttpClient()) { Assert.Equal(value, client.Timeout); @@ -79,7 +78,7 @@ public void UseReport() [Fact] public void UserAgentHeader() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); Assert.Equal("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)), HeadersAsMap(config.DefaultHeaders)["user-agent"]); // not configurable } @@ -87,7 +86,7 @@ public void UserAgentHeader() [Fact] public void WrapperDefaultNone() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicConfig); + var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); Assert.False(HeadersAsMap(config.DefaultHeaders).ContainsKey("x-launchdarkly-wrapper")); } @@ -95,7 +94,7 @@ public void WrapperDefaultNone() public void WrapperNameOnly() { var config = Components.HttpConfiguration().Wrapper("w", null) - .CreateHttpConfiguration(basicConfig); + .CreateHttpConfiguration(basicContext); Assert.Equal("w", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); } @@ -103,7 +102,7 @@ public void WrapperNameOnly() public void WrapperNameAndVersion() { var config = Components.HttpConfiguration().Wrapper("w", "1.0") - .CreateHttpConfiguration(basicConfig); + .CreateHttpConfiguration(basicContext); Assert.Equal("w/1.0", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 4cfb81f1..6d4fd1d7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -41,7 +41,7 @@ private IDataSource MakeDataSourceWithDiagnostics(Uri baseUri, Context context, var config = BasicConfig() .ServiceEndpoints(Components.ServiceEndpoints().Streaming(baseUri).Polling(baseUri)) .Build(); - var clientContext = new LdClientContext(config, null, diagnosticStore, null); + var clientContext = new LdClientContext(config, null).WithDiagnostics(null, diagnosticStore); return Components.StreamingDataSource().InitialReconnectDelay(BriefReconnectDelay) .CreateDataSource(clientContext, _updateSink, context, false); } From a8b79d5a27ff9a1b3b1be0555374a04a4ba29724 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Fri, 10 Jun 2022 17:49:54 -0700 Subject: [PATCH 403/499] move component API types into Subsystems --- .../LaunchDarkly.Sdk.Client.Interfaces.md | 8 ++-- .../LaunchDarkly.Sdk.Client.Subsystems.md | 5 ++ src/LaunchDarkly.ClientSdk/Components.cs | 2 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 1 + .../ConfigurationBuilder.cs | 5 +- src/LaunchDarkly.ClientSdk/DataModel.cs | 2 +- .../Integrations/EventProcessorBuilder.cs | 2 +- .../Integrations/HttpConfigurationBuilder.cs | 2 +- .../LoggingConfigurationBuilder.cs | 2 +- .../PersistenceConfigurationBuilder.cs | 4 +- .../Integrations/PollingDataSourceBuilder.cs | 2 +- .../Integrations/ServiceEndpointsBuilder.cs | 1 - .../StreamingDataSourceBuilder.cs | 2 +- .../Integrations/TestData.cs | 3 +- .../Interfaces/DataSourceStatus.cs | 2 +- .../Interfaces/IDataSourceStatusProvider.cs | 2 +- .../Internal/ComponentsImpl.cs | 3 +- .../Internal/DataModelSerialization.cs | 2 +- .../Internal/DataSources/ConnectionManager.cs | 1 + .../DataSourceStatusProviderImpl.cs | 1 + .../DataSources/DataSourceUpdateSinkImpl.cs | 3 +- .../DataSources/FeatureFlagRequestor.cs | 2 +- .../Internal/DataSources/PollingDataSource.cs | 1 + .../DataSources/StreamingDataSource.cs | 3 +- .../Internal/DataStores/FlagDataManager.cs | 4 +- .../DataStores/NullPersistentDataStore.cs | 3 +- .../DataStores/PersistenceConfiguration.cs | 2 +- .../DataStores/PersistentDataStoreWrapper.cs | 4 +- .../Internal/Events/ClientDiagnosticStore.cs | 2 +- .../Events/DefaultEventProcessorWrapper.cs | 2 +- .../Internal/Events/EventFactory.cs | 2 +- .../LaunchDarkly.ClientSdk.csproj | 48 +++++++++++++++++++ src/LaunchDarkly.ClientSdk/LdClient.cs | 1 + .../PlatformSpecific/LocalStorage.android.cs | 2 +- .../PlatformSpecific/LocalStorage.ios.cs | 2 +- .../LocalStorage.netstandard.cs | 2 +- .../PlatformSpecific/LocalStorage.shared.cs | 2 +- .../DataStoreTypes.cs | 2 +- .../EventProcessorTypes.cs | 2 +- .../HttpConfiguration.cs | 2 +- .../{Interfaces => Subsystems}/IDataSource.cs | 2 +- .../IDataSourceFactory.cs | 2 +- .../IDataSourceUpdateSink.cs | 6 ++- .../IDiagnosticDescription.cs | 2 +- .../IEventProcessor.cs | 2 +- .../IEventProcessorFactory.cs | 2 +- .../IPersistentDataStore.cs | 2 +- .../IPersistentDataStoreFactory.cs | 2 +- .../LdClientContext.cs | 3 +- .../LoggingConfiguration.cs | 2 +- .../AssertHelpers.cs | 6 +-- .../HttpConfigurationBuilderTest.cs | 2 +- .../Integrations/TestDataTest.cs | 4 +- .../DataSourceUpdateSinkImplTest.cs | 2 +- .../DataSources/FeatureFlagRequestorTests.cs | 2 +- .../DataSources/PollingDataSourceTest.cs | 4 +- .../DataSources/StreamingDataSourceTest.cs | 2 +- .../DataStores/FlagDataManagerTest.cs | 2 +- .../FlagDataManagerWithPersistenceTest.cs | 2 +- .../DataStores/PlatformLocalStorageTest.cs | 2 +- .../LDClientEndToEndTests.cs | 3 +- .../LdClientEventTests.cs | 4 +- .../MockComponents.cs | 3 +- .../MockResponses.cs | 2 +- .../ModelBuilders.cs | 2 +- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 4 +- 66 files changed, 137 insertions(+), 77 deletions(-) create mode 100644 docs-src/namespaces/LaunchDarkly.Sdk.Client.Subsystems.md rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/DataStoreTypes.cs (98%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/EventProcessorTypes.cs (98%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/HttpConfiguration.cs (99%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IDataSource.cs (96%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IDataSourceFactory.cs (96%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IDataSourceUpdateSink.cs (95%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IDiagnosticDescription.cs (97%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IEventProcessor.cs (98%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IEventProcessorFactory.cs (93%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IPersistentDataStore.cs (98%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/IPersistentDataStoreFactory.cs (95%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/LdClientContext.cs (98%) rename src/LaunchDarkly.ClientSdk/{Interfaces => Subsystems}/LoggingConfiguration.cs (96%) diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md index 5ae08ab9..3e3991e7 100644 --- a/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Interfaces.md @@ -1,5 +1,7 @@ -Interfaces that provide advanced SDK features or allow customization of LaunchDarkly components. +Interfaces and types that are part of the public API, but not needed for basic use of the SDK. -Most applications will not need to refer to these types. You will use them if you are creating a plug-in component, such as a data store integration, or if you use advanced features such as . +Types in this namespace include: -The namespace also includes concrete types that are used as parameters within these interfaces. +* , which allows the SDK client to be referenced via an interface rather than the concrete type (if for instance you want to create a mock implementation for testing). +* Types like that provide a facade for some part of the SDK API; these are returned by properties like . +* Concrete types that are used as parameters within these interfaces, like . diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Client.Subsystems.md b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Subsystems.md new file mode 100644 index 00000000..2cbf81de --- /dev/null +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Client.Subsystems.md @@ -0,0 +1,5 @@ +Interfaces for implementation of custom LaunchDarkly components. + +Most applications will not need to refer to these types. You will use them if you are creating a plugin component, such as a database integration. They are also used as interfaces for the built-in SDK components, so that plugin components can be used interchangeably with those: for instance, the configuration method references as an abstraction for the data source component. + +The namespace also includes concrete types that are used as parameters within these interfaces. diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index 4dd276cf..30eb5b30 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -1,8 +1,8 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client { diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 281384e2..b5a752de 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client { diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 65d91c32..0caef8bd 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -1,9 +1,8 @@ -using System; -using System.Net.Http; +using System.Net.Http; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client { diff --git a/src/LaunchDarkly.ClientSdk/DataModel.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs index 14dcf08d..bcec0c0b 100644 --- a/src/LaunchDarkly.ClientSdk/DataModel.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -2,7 +2,7 @@ using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Json; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index acfbe3ba..3b5bb3d7 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.Events; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index da9d433f..8006f9d4 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -4,7 +4,7 @@ using System.Net.Http; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs index 241ea6e6..946c8f97 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/LoggingConfigurationBuilder.cs @@ -1,6 +1,6 @@ using System; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Integrations { diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs index 568adc6a..1e61e814 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs @@ -1,5 +1,5 @@ -using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Integrations { diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 6a3a6ec7..123ab7b2 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs index 91064d1c..f0e0ded5 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/ServiceEndpointsBuilder.cs @@ -1,5 +1,4 @@ using System; -using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index b9a1bbbf..53978a16 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -1,7 +1,7 @@ using System; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Internal.DataSources; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs index 6bfb1e95..d2233123 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -5,9 +5,10 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Integrations { diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs index 4083a9ff..cd90d3b5 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/DataSourceStatus.cs @@ -170,7 +170,7 @@ public enum ErrorKind /// /// /// Data source implementations do not need to report this kind of error; it will be automatically - /// reported by the SDK whenever one of the update methods of throws an + /// reported by the SDK whenever one of the update methods of throws an /// exception. /// StoreError diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs index cbe0e0f7..42d74cb5 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceStatusProvider.cs @@ -29,7 +29,7 @@ public interface IDataSourceStatusProvider /// /// /// For a custom data source implementation, it is the responsibility of the data source to report its - /// status via ; if it does not do so, the status will always be reported + /// status via ; if it does not do so, the status will always be reported /// as . /// /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index 1c10e70b..0c32cee8 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; -using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs index 560a6cb2..6d98a168 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs @@ -6,7 +6,7 @@ using LaunchDarkly.Sdk.Json; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index 503de66d..189a3fcd 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -5,6 +5,7 @@ using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs index de840108..c1a8dffc 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceStatusProviderImpl.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Concurrent; namespace LaunchDarkly.Sdk.Client.Internal.DataSources diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs index a038e1cd..57402c9a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DataSourceUpdateSinkImpl.cs @@ -7,9 +7,10 @@ using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Concurrent; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs index 76d761dd..c12d8c69 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/FeatureFlagRequestor.cs @@ -6,9 +6,9 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index 45c38a47..d9ca1612 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -7,6 +7,7 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index 2cea4609..e650025d 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -10,8 +10,9 @@ using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Http; +using LaunchDarkly.Sdk.Client.Subsystems; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs index 061e757a..37caff83 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/FlagDataManager.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; +using LaunchDarkly.Sdk.Client.Subsystems; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs index d5d644ef..8dbd43a8 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs index ea7a7854..ed66bfab 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistenceConfiguration.cs @@ -1,4 +1,4 @@ -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index f0bfa4da..3db9a31d 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -1,10 +1,10 @@ using System; using LaunchDarkly.Logging; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Concurrent; +using LaunchDarkly.Sdk.Client.Subsystems; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index 759e3409..9030bfd7 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Http; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs index c42c3818..6cf2efbf 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/DefaultEventProcessorWrapper.cs @@ -1,4 +1,4 @@ -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Events; namespace LaunchDarkly.Sdk.Client.Internal.Events diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs index 31362342..933bccd4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/EventFactory.cs @@ -1,6 +1,6 @@  using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client.Internal.Events { diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ef92b9ca..da4af80b 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -47,6 +47,7 @@ + @@ -73,6 +74,53 @@ + + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + ../../LaunchDarkly.ClientSdk.snk true diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 7beb4b84..6ecfac52 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -12,6 +12,7 @@ using LaunchDarkly.Sdk.Client.Internal.Events; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Concurrent; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs index ee586312..1519b9ed 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.android.cs @@ -1,7 +1,7 @@ using Android.App; using Android.Content; using Android.Preferences; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs index 57f79a83..6059499c 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.ios.cs @@ -1,5 +1,5 @@ using Foundation; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs index c6117891..beee33e7 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.IO.IsolatedStorage; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs index c34cdbf1..4576eaa1 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.shared.cs @@ -1,4 +1,4 @@ -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/DataStoreTypes.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/DataStoreTypes.cs index 45c12091..2b3ddc0b 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/DataStoreTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/DataStoreTypes.cs @@ -5,7 +5,7 @@ using static LaunchDarkly.Sdk.Client.DataModel; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Types that are used by the data store and related interfaces. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/EventProcessorTypes.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/EventProcessorTypes.cs index 8b96bedb..d1c20279 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/EventProcessorTypes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/EventProcessorTypes.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Parameter types for use by implementations. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs b/src/LaunchDarkly.ClientSdk/Subsystems/HttpConfiguration.cs similarity index 99% rename from src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/HttpConfiguration.cs index cb914072..286e697d 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/HttpConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/HttpConfiguration.cs @@ -5,7 +5,7 @@ using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Client.Integrations; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Encapsulates top-level HTTP configuration that applies to all SDK components. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs similarity index 96% rename from src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs index 14007233..93e80dd6 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for an object that receives updates to feature flags from LaunchDarkly. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs similarity index 96% rename from src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs index bf439c53..7ff0fc68 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for a factory that creates some implementation of . diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceUpdateSink.cs similarity index 95% rename from src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceUpdateSink.cs index 2fb5f360..5ce71d0f 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDataSourceUpdateSink.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceUpdateSink.cs @@ -1,6 +1,8 @@ -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using LaunchDarkly.Sdk.Client.Interfaces; -namespace LaunchDarkly.Sdk.Client.Interfaces +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; + +namespace LaunchDarkly.Sdk.Client.Subsystems { // Note: In .NET server-side SDK 6.x, Java SDK 5.x, and Go SDK 5.x, where this component was added, it // is called "DataSourceUpdates". This name was thought to be a bit confusing, since it receives updates diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs similarity index 97% rename from src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs index 51ce362a..8d061897 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IDiagnosticDescription.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Optional interface for components to describe their own configuration. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessor.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessor.cs index 1e18d755..322825dd 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessor.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessor.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for an object that can send or store analytics events. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs similarity index 93% rename from src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs index b75f3eff..4478fd97 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IEventProcessorFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs @@ -1,5 +1,5 @@  -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for a factory that creates some implementation of . diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs index d363d298..f3d9dc94 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs @@ -1,6 +1,6 @@ using System; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for a data store that holds feature flag data and other SDK properties in a diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs similarity index 95% rename from src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs index b6cca53c..71fcfa18 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/IPersistentDataStoreFactory.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs @@ -1,6 +1,6 @@ using LaunchDarkly.Sdk.Client.Integrations; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Interface for a factory that creates some implementation of . diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs similarity index 98% rename from src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 80a04a17..8ad90271 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -1,9 +1,10 @@ using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Encapsulates SDK client context when creating components. diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/LoggingConfiguration.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LoggingConfiguration.cs similarity index 96% rename from src/LaunchDarkly.ClientSdk/Interfaces/LoggingConfiguration.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/LoggingConfiguration.cs index b57ad754..b4f7f002 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/LoggingConfiguration.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LoggingConfiguration.cs @@ -1,7 +1,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; -namespace LaunchDarkly.Sdk.Client.Interfaces +namespace LaunchDarkly.Sdk.Client.Subsystems { /// /// Encapsulates the SDK's general logging configuration. diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs index 86b337c0..5f0cb129 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -1,11 +1,9 @@ -using System; -using System.Text; -using LaunchDarkly.Logging; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Json; using Xunit; using Xunit.Sdk; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; using static LaunchDarkly.TestHelpers.JsonAssertions; namespace LaunchDarkly.Sdk.Client diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index 75999558..e4299ac7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Net.Http; using LaunchDarkly.Sdk.Internal; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.TestHelpers; using Xunit; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index f5675342..0dd752ad 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Integrations { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs index 83699fae..0ba5e6a5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/DataSourceUpdateSinkImplTest.cs @@ -4,7 +4,7 @@ using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataSources { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 750e505e..13651cb7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using LaunchDarkly.Sdk.Json; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index cf919cfa..25172598 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -1,13 +1,13 @@ using System; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; using static LaunchDarkly.Sdk.Client.MockResponses; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; using static LaunchDarkly.Sdk.Client.TestHttpUtils; namespace LaunchDarkly.Sdk.Client.Internal.DataSources diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 6d4fd1d7..0b6c0a9a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Concurrent; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Json; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs index eba99da4..d2cd44d6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerTest.cs @@ -1,7 +1,7 @@ using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index 33e3f893..db874725 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -3,7 +3,7 @@ using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs index a9a57057..7fcccf4f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PlatformLocalStorageTest.cs @@ -1,5 +1,5 @@ using System.Linq; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using Xunit; using Xunit.Abstractions; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index ac4f9ca6..e63f0192 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -4,12 +4,11 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; -using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; using static LaunchDarkly.Sdk.Client.MockResponses; namespace LaunchDarkly.Sdk.Client diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index ea40e2cf..14db9724 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -1,10 +1,10 @@ using System; using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using Xunit; using Xunit.Abstractions; -using static LaunchDarkly.Sdk.Client.Interfaces.EventProcessorTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.EventProcessorTypes; namespace LaunchDarkly.Sdk.Client { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 501884ef..91c51c9a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -11,11 +11,12 @@ using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Internal.Interfaces; using LaunchDarkly.Sdk.Client.PlatformSpecific; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.TestHelpers; using Xunit; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs index 67cc6485..f6d30b48 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockResponses.cs @@ -1,6 +1,6 @@ using LaunchDarkly.TestHelpers.HttpTest; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs index f0923769..a32ffb92 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ModelBuilders.cs @@ -2,7 +2,7 @@ using LaunchDarkly.Sdk.Client.Internal; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 81e15104..f801cb91 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -3,13 +3,13 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.JsonStream; -using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Json; using Xunit; using static LaunchDarkly.Sdk.Client.DataModel; -using static LaunchDarkly.Sdk.Client.Interfaces.DataStoreTypes; +using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; namespace LaunchDarkly.Sdk.Client { From 4854267c82cbcba8794f8dbde4964ac13907cec9 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 14 Jun 2022 10:16:25 -0700 Subject: [PATCH 404/499] rm unused --- tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs index 86764cbb..b550dca8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/AssertHelpers.cs @@ -1,6 +1,4 @@ -using System; -using System.Text; -using LaunchDarkly.Logging; +using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Json; using Xunit; using Xunit.Sdk; From 018d9f3c2f76c00d00a08545b0691443ce0d4542 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 14 Jun 2022 16:11:17 -0700 Subject: [PATCH 405/499] update to latest alpha packages --- Makefile | 5 +---- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- src/LaunchDarkly.ClientSdk/LdClient.cs | 8 ++++---- .../DataStores/FlagDataManagerWithPersistenceTest.cs | 6 +++--- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index e731fb30..3b8eb791 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,7 @@ BUILDFRAMEWORKS ?= netcoreapp2.1 TESTFRAMEWORK ?= netcoreapp2.1 # temporary skips for contract tests that can't pass till more U2C work is done -TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \ - -skip events/requests/method -# events/request/method only fails because the latest alpha LaunchDarkly.InternalSdk -# doesn't set the correct schema version +# TEST_HARNESS_PARAMS := build-contract-tests: @cd contract-tests && dotnet build TestService.csproj diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index da4af80b..9933c531 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,9 +40,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 6ecfac52..63767284 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -255,8 +255,8 @@ async Task StartAsync() /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the user needed for client operations (must not be ); - /// if the user's is and + /// the context needed for client operations (must not be ); + /// if the user's is and /// is , it will be assigned a key that uniquely identifies this device /// the maximum length of time to wait for the client to initialize public static LdClient Init(string mobileKey, Context initialContext, TimeSpan maxWaitTime) @@ -281,8 +281,8 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the user needed for client operations; - /// if the user's is and + /// the context needed for client operations; + /// if the user's is and /// is , it will be assigned a key that uniquely identifies this device public static async Task InitAsync(string mobileKey, Context initialContext) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs index db874725..11971e57 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/FlagDataManagerWithPersistenceTest.cs @@ -231,9 +231,9 @@ public void FlagsAreStoredByFullyQualifiedKeyForSingleAndMultiKindContexts() { Context.New("key1"), Context.New("key2"), - Context.NewWithKind("kind2", "key1"), - Context.NewMulti(Context.NewWithKind("kind1", "key1"), Context.NewWithKind("kind2", "key2")), - Context.NewMulti(Context.NewWithKind("kind1", "key1"), Context.NewWithKind("kind2", "key3")) + Context.New(ContextKind.Of("kind2"), "key1"), + Context.NewMulti(Context.New(ContextKind.Of("kind1"), "key1"), Context.New(ContextKind.Of("kind2"), "key2")), + Context.NewMulti(Context.New(ContextKind.Of("kind1"), "key1"), Context.New(ContextKind.Of("kind2"), "key3")) }; var store = MakeStore(contexts.Length); for (var i = 0; i < contexts.Length; i++) From fb49620a92f7f49d371d12fd513ec82d7255a095 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jun 2022 15:57:57 -0700 Subject: [PATCH 406/499] never use device ID, always UUID for generated keys --- src/LaunchDarkly.ClientSdk/Configuration.cs | 2 - .../ConfigurationBuilder.cs | 7 - .../Interfaces/ILdClient.cs | 10 +- .../Internal/ContextDecorator.cs | 20 +-- .../Internal/DefaultDeviceInfo.cs | 13 -- .../Internal/Interfaces/IDeviceInfo.cs | 7 - src/LaunchDarkly.ClientSdk/LdClient.cs | 87 +++++++--- .../ClientIdentifier.android.cs | 36 ---- .../PlatformSpecific/ClientIdentifier.ios.cs | 10 -- .../ClientIdentifier.netstandard.cs | 10 -- .../ClientIdentifier.shared.cs | 7 - .../AndroidSpecificTests.cs | 14 -- .../LdClientTests.cs | 162 ++++++++---------- .../MockComponents.cs | 19 -- .../IOsSpecificTests.cs | 14 -- 15 files changed, 142 insertions(+), 276 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index b5a752de..ddd49ff1 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -31,7 +31,6 @@ public sealed class Configuration // Settable only for testing internal IBackgroundModeManager BackgroundModeManager { get; } internal IConnectivityStateManager ConnectivityStateManager { get; } - internal IDeviceInfo DeviceInfo { get; } /// /// A factory object that creates an implementation of , which will @@ -167,7 +166,6 @@ internal Configuration(ConfigurationBuilder builder) BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; - DeviceInfo = builder._deviceInfo; } } } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 0caef8bd..4a4b14b6 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -45,7 +45,6 @@ public sealed class ConfigurationBuilder // Internal properties only settable for testing internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; - internal IDeviceInfo _deviceInfo; internal ConfigurationBuilder(string mobileKey) { @@ -352,11 +351,5 @@ internal ConfigurationBuilder ConnectivityStateManager(IConnectivityStateManager _connectivityStateManager = connectivityStateManager; return this; } - - internal ConfigurationBuilder DeviceInfo(IDeviceInfo deviceInfo) - { - _deviceInfo = deviceInfo; - return this; - } } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index 462cd7ce..3ff2e0c0 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -322,14 +322,15 @@ public interface ILdClient : IDisposable /// If you do not want to wait, you can either set maxWaitTime to zero or call . /// /// - /// the new context + /// the new evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it /// the maximum time to wait for the new flag values /// true if new flag values were obtained bool Identify(Context context, TimeSpan maxWaitTime); /// - /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, and generates - /// an analytics event to tell LaunchDarkly about the user. + /// Changes the current evaluation context, requests flags for that context from LaunchDarkly if we are online, + /// and generates an analytics event to tell LaunchDarkly about the context. /// /// /// @@ -342,7 +343,8 @@ public interface ILdClient : IDisposable /// and yields . /// /// - /// the new context + /// the new evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it /// a task that yields true if new flag values were obtained Task IdentifyAsync(Context context); diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index 10f01936..55689bfc 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -1,23 +1,19 @@ using System; using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.Client.Internal.Interfaces; namespace LaunchDarkly.Sdk.Client.Internal { internal class ContextDecorator { - private readonly IDeviceInfo _deviceInfo; private readonly PersistentDataStoreWrapper _store; private string _cachedAnonUserKey = null; private object _anonUserKeyLock = new object(); public ContextDecorator( - IDeviceInfo deviceInfo, PersistentDataStoreWrapper store ) { - _deviceInfo = deviceInfo; _store = store; } @@ -43,18 +39,14 @@ private string GetOrCreateAutoContextKey() { return _cachedAnonUserKey; } - var deviceId = _deviceInfo.UniqueDeviceId(); - if (deviceId is null) + var uniqueId = _store?.GetAnonymousUserKey(); + if (uniqueId is null) { - deviceId = _store?.GetAnonymousUserKey(); - if (deviceId is null) - { - deviceId = Guid.NewGuid().ToString(); - _store?.SetAnonymousUserKey(deviceId); - } + uniqueId = Guid.NewGuid().ToString(); + _store?.SetAnonymousUserKey(uniqueId); } - _cachedAnonUserKey = deviceId; - return deviceId; + _cachedAnonUserKey = uniqueId; + return uniqueId; } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs b/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs deleted file mode 100644 index bcc0d6ec..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/DefaultDeviceInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using LaunchDarkly.Sdk.Client.Internal.Interfaces; - -namespace LaunchDarkly.Sdk.Client.Internal -{ - // This delegates to the conditionally-compiled code in LaunchDarkly.Sdk.Client.PlatformSpecific - // to get the device identifier. The only reason it is a pluggable component is for unit tests; - // we don't currently expose IDeviceInfo. - - internal sealed class DefaultDeviceInfo : IDeviceInfo - { - public string UniqueDeviceId() => PlatformSpecific.ClientIdentifier.Value; - } -} diff --git a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs b/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs deleted file mode 100644 index aa94bf54..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/Interfaces/IDeviceInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LaunchDarkly.Sdk.Client.Internal.Interfaces -{ - internal interface IDeviceInfo - { - string UniqueDeviceId(); - } -} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 63767284..0e83b5cb 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -22,6 +22,25 @@ namespace LaunchDarkly.Sdk.Client /// A client for the LaunchDarkly API. Client instances are thread-safe. Your application should instantiate /// a single LdClient for the lifetime of their application. /// + /// + /// + /// Like all client-side LaunchDarkly SDKs, the LdClient always has a single current . + /// You specify this context at initialization time, and you can change it later with + /// or . All subsequent calls to evaluation methods like + /// refer to the flag values for the current context. + /// + /// + /// Normally, the SDK uses the exact context that you have specified in the . However, + /// you can also tell the SDK to generate a randomized identifier and use this as the context's + /// . To do this, set the context's + /// property to and set its key to . The generated key will + /// be a pseudo-random UUID. If you subsequently set the current context to another context like this + /// during the lifetime of the LdClient, it will reuse the same generated key; and if persistent + /// storage is available (see , it will cache the key so that it will + /// also be reused even if the app is restarted. If persistent storage is not available, then the SDK would + /// generate a different key after a restart. + /// + /// public sealed class LdClient : ILdClient { static readonly EventFactory _eventFactoryDefault = EventFactory.Default; @@ -144,8 +163,7 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataStoreSubLog) ); - _contextDecorator = new ContextDecorator(configuration.DeviceInfo ?? new DefaultDeviceInfo(), - _dataStore.PersistentStore); + _contextDecorator = new ContextDecorator(_dataStore.PersistentStore); _context = _contextDecorator.DecorateContext(initialContext); // If we had cached data for the new context, set the current in-memory flag data state to use @@ -255,9 +273,8 @@ async Task StartAsync() /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the context needed for client operations (must not be ); - /// if the user's is and - /// is , it will be assigned a key that uniquely identifies this device + /// the initial evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize public static LdClient Init(string mobileKey, Context initialContext, TimeSpan maxWaitTime) { @@ -267,7 +284,8 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m } /// - /// Creates a new singleton instance and attempts to initialize feature flags asynchronously. + /// Creates a new singleton instance and attempts to initialize feature flags + /// asynchronously. /// /// /// @@ -275,15 +293,19 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m /// the LaunchDarkly service is returned (or immediately if it is in offline mode). /// /// + /// If you would rather this happen synchronously, use . To + /// specify additional configuration options rather than just the mobile key, you can use + /// or . + /// + /// /// You must use one of these static factory methods to instantiate the single instance of LdClient /// for the lifetime of your application. /// /// /// the singleton instance /// the mobile key given to you by LaunchDarkly - /// the context needed for client operations; - /// if the user's is and - /// is , it will be assigned a key that uniquely identifies this device + /// the initial evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it public static async Task InitAsync(string mobileKey, Context initialContext) { var config = Configuration.Default(mobileKey); @@ -312,13 +334,13 @@ public static async Task InitAsync(string mobileKey, Context initialCo /// for the lifetime of your application. /// /// - /// The singleton LdClient instance. - /// The client configuration object - /// The initial evaluation context. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. - /// The maximum length of time to wait for the client to initialize. - /// If this time elapses, the method will not throw an exception but will return the client in - /// an uninitialized state. + /// the singleton LdClient instance + /// the client configuration + /// the initial evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it + /// the maximum length of time to wait for the client to initialize; + /// if this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state public static LdClient Init(Configuration config, Context initialContext, TimeSpan maxWaitTime) { if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) @@ -332,19 +354,28 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp } /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for - /// fetching Feature Flags. This constructor should be used if you do not want to wait - /// for the IUpdateProcessor instance to finish initializing and receive the first response - /// from the LaunchDarkly service. - /// - /// This is the creation point for LdClient, you must use this static method or the more basic - /// to instantiate the single instance of LdClient - /// for the lifetime of your application. + /// Creates a new singleton instance and attempts to initialize feature flags + /// asynchronously. /// - /// The singleton LdClient instance. - /// The client configuration object. - /// The initial evaluation context. - /// If the user's Key is null, it will be assigned a key that uniquely identifies this device. + /// + /// + /// The returned task will yield the instance once the first response from + /// the LaunchDarkly service is returned (or immediately if it is in offline mode). + /// + /// + /// If you would rather this happen synchronously, use . + /// If you do not need to specify configuration options other than the mobile key, you can use + /// or . + /// + /// + /// You must use one of these static factory methods to instantiate the single instance of LdClient + /// for the lifetime of your application. + /// + /// + /// the singleton LdClient instance + /// the client configuration + /// the initial evaluation context; see for more + /// about setting the context and optionally requesting a unique key for it public static async Task InitAsync(Configuration config, Context initialContext) { var c = CreateInstance(config, initialContext, TimeSpan.Zero); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs deleted file mode 100644 index 13dd8158..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.android.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Android.Provider; -using Android.OS; -using Android.Runtime; -using Java.Interop; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class ClientIdentifier - { - private static JniPeerMembers buildMembers = new XAPeerMembers("android/os/Build", typeof(Build)); - - public static string Value - { - get - { - // Based on: https://github.com/jamesmontemagno/DeviceInfoPlugin/blob/master/src/DeviceInfo.Plugin/DeviceInfo.android.cs - string serialField; - try - { - var value = buildMembers.StaticFields.GetObjectValue("SERIAL.Ljava/lang/String;"); - serialField = JNIEnv.GetString(value.Handle, JniHandleOwnership.TransferLocalRef); - } - catch - { - serialField = ""; - } - if (string.IsNullOrWhiteSpace(serialField) || serialField == Build.Unknown || serialField == "0") - { - var context = Android.App.Application.Context; - return Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId); - } - return serialField; - } - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs deleted file mode 100644 index 986d8c4a..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.ios.cs +++ /dev/null @@ -1,10 +0,0 @@ -using UIKit; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class ClientIdentifier - { - public static string Value => - UIDevice.CurrentDevice.IdentifierForVendor.AsString(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs deleted file mode 100644 index eae8b93d..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.netstandard.cs +++ /dev/null @@ -1,10 +0,0 @@ -using LaunchDarkly.Logging; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class ClientIdentifier - { - // Unlike mobile platforms, .NET standard doesn't have an OS-based notion of a device identifier. - public static string Value => null; - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs deleted file mode 100644 index dfe8a883..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/ClientIdentifier.shared.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class ClientIdentifier - { - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs index b4173e55..21ccd7af 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs @@ -14,20 +14,6 @@ public void SdkReturnsAndroidPlatformType() Assert.Equal(PlatformType.Android, LdClient.PlatformType); } - [Fact] - public void CanGetUniqueUserKey() - { - var anonUser = Context.Builder(Internal.Constants.AutoKeyMagicValue).Transient(true).Build(); - var config = BasicConfig() - .DeviceInfo(null).Build(); - using (var client = TestUtil.CreateClient(config, anonUser)) - { - var context = client.Context; - Assert.NotNull(context.Key); - Assert.NotEqual("", context.Key); - } - } - [Fact] public void EventHandlerIsCalledOnUIThread() { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index bea973e9..c6e33eb7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -54,77 +54,65 @@ public async void InitPassesUserToDataSource() } [Fact] - public async Task InitWithKeylessAnonUserAddsKey() + public async Task InitWithKeylessAnonUserAddsRandomizedKey() { // Note, we don't care about polling mode vs. streaming mode for this functionality. - var mockDeviceInfo = new MockDeviceInfo("fake-device-id"); - var config = BasicConfig().DeviceInfo(mockDeviceInfo).Persistence(Components.NoPersistence).Build(); + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + string key1; using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - Assert.Equal("fake-device-id", client.Context.Key); + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), client.Context); } - } - -#if __MOBILE__ - [Fact] - public async Task InitWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() - { - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); - string generatedKey = null; + // Starting again should generate a new key, since we've turned off persistence using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - generatedKey = client.Context.Key; - Assert.NotNull(generatedKey); + var key2 = client.Context.Key; + Assert.NotNull(key2); + Assert.NotEqual("", key2); + Assert.NotEqual(key1, key2); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(key2).Build(), client.Context); } - - using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) - { - Assert.Equal(generatedKey, client.Context.Key); - } } -#endif -#if !__MOBILE__ [Fact] - public async Task InitWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatforms() + public async Task InitWithKeylessAnonUserCanReusePreviousRandomizedKey() { - var config = BasicConfig() - .Persistence(Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory())) - .Build(); + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var store = new MockPersistentDataStore(); + var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())).Build(); + + string key1; - string generatedKey = null; using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - generatedKey = client.Context.Key; - Assert.NotNull(generatedKey); + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), client.Context); } + // Starting again should reuse the persisted key using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) { - Assert.Equal(generatedKey, client.Context.Key); - } - - // Now use a configuration where persistence is disabled - a different key is generated - var configWithoutPersistence = BasicConfig().Persistence(Components.NoPersistence).Build(); - using (var client = await TestUtil.CreateClientAsync(configWithoutPersistence, KeylessAnonUser)) - { - Assert.NotNull(client.Context.Key); - Assert.NotEqual(generatedKey, client.Context.Key); + Assert.Equal(key1, client.Context.Key); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + client.Context); } } -#endif - + [Fact] public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() { @@ -136,6 +124,8 @@ public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() using (var client = await LdClient.InitAsync(config, KeylessAnonUser)) { + Assert.NotEqual(KeylessAnonUser, stub.ReceivedContext); + Assert.Equal(client.Context, stub.ReceivedContext); AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(stub.ReceivedContext.Key).Build(), stub.ReceivedContext); @@ -245,87 +235,77 @@ public async void IdentifyPassesUserToDataSource() } [Fact] - public async Task IdentifyWithKeylessAnonUserAddsKey() + public async Task IdentifyWithKeylessAnonUserAddsRandomizedKey() { - var mockDeviceInfo = new MockDeviceInfo("fake-device-id"); - var config = BasicConfig().DeviceInfo(mockDeviceInfo).Persistence(Components.NoPersistence).Build(); + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + string key1; using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { await client.IdentifyAsync(KeylessAnonUser); - Assert.Equal("fake-device-id", client.Context.Key); + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), client.Context); - } - } - -#if __MOBILE__ - [Fact] - public async Task IdentifyWithKeylessAnonUserUsesStableDeviceIDOnMobilePlatforms() - { - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); - - string generatedKey = null; - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(KeylessAnonUser); - generatedKey = client.Context.Key; - Assert.NotNull(generatedKey); + var anonUser2 = TestUtil.BuildAutoContext().Name("other").Build(); + await client.IdentifyAsync(anonUser2); + var key2 = client.Context.Key; + Assert.Equal(key1, key2); // Even though persistence is disabled, the key is stable during the lifetime of the SDK client. AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(anonUser2).Key(key2).Build(), client.Context); } using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { - client.Identify(KeylessAnonUser, TimeSpan.FromSeconds(1)); + await client.IdentifyAsync(KeylessAnonUser); - Assert.Equal(generatedKey, client.Context.Key); + var key3 = client.Context.Key; + Assert.NotNull(key3); + Assert.NotEqual("", key3); + Assert.NotEqual(key1, key3); // The previously generated key was discarded with the previous client. + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(KeylessAnonUser).Key(key3).Build(), + client.Context); } } -#endif -#if !__MOBILE__ [Fact] - public async Task IdentifyWithKeylessAnonUserGeneratesRandomizedIdOnNonMobilePlatforms() + public async Task IdentifyWithKeylessAnonUserCanReusePersistedRandomizedKey() { - var config = BasicConfig() - .Persistence(Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory())) - .Build(); + var store = new MockPersistentDataStore(); + var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())).Build(); + + string key1; - string generatedKey = null; using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { await client.IdentifyAsync(KeylessAnonUser); - generatedKey = client.Context.Key; - Assert.NotNull(generatedKey); + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), client.Context); - } - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(KeylessAnonUser); - - Assert.Equal(generatedKey, client.Context.Key); } - // Now use a configuration where persistence is disabled - a different key is generated - var configWithoutPersistence = BasicConfig().Persistence(Components.NoPersistence).Build(); - using (var client = await TestUtil.CreateClientAsync(configWithoutPersistence, BasicUser)) + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { await client.IdentifyAsync(KeylessAnonUser); - Assert.NotNull(client.Context.Key); - Assert.NotEqual(generatedKey, client.Context.Key); + var key2 = client.Context.Key; + Assert.Equal(key1, key2); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(KeylessAnonUser).Key(key2).Build(), + client.Context); } } -#endif [Fact] public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() @@ -334,17 +314,17 @@ public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() var config = BasicConfig() .DataSource(stub.AsFactory()) - .DeviceInfo(new MockDeviceInfo()) .Build(); using (var client = await LdClient.InitAsync(config, BasicUser)) { await client.IdentifyAsync(KeylessAnonUser); + Assert.NotEqual(KeylessAnonUser, stub.ReceivedContext); + Assert.Equal(client.Context, stub.ReceivedContext); AssertHelpers.ContextsEqual( Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), - client.Context); - Assert.Equal(client.Context, stub.ReceivedContext); + stub.ReceivedContext); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 91c51c9a..bd08cc6d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -151,25 +151,6 @@ public DataSourceStatus ExpectStatusUpdate() public void ExpectNoMoreActions() => Actions.ExpectNoValue(); } - internal class MockDeviceInfo : IDeviceInfo - { - internal const string GeneratedId = "fake-generated-id"; - - private readonly string key; - - public MockDeviceInfo() : this(GeneratedId) { } - - public MockDeviceInfo(string key) - { - this.key = key; - } - - public string UniqueDeviceId() - { - return key; - } - } - internal class MockDiagnosticStore : IDiagnosticStore { internal struct StreamInit diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs index 08db3010..2cd29bf0 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs @@ -12,20 +12,6 @@ public class IOsSpecificTests : BaseTest public void SdkReturnsIOsPlatformType() { Assert.Equal(PlatformType.IOs, LdClient.PlatformType); - } - - [Fact] - public void CanGetUniqueUserKey() - { - var anonUser = Context.Builder(Internal.Constants.AutoKeyMagicValue).Transient(true).Build(); - var config = BasicConfig() - .DeviceInfo(null).Build(); - using (var client = TestUtil.CreateClient(config, anonUser)) - { - var context = client.Context; - Assert.NotNull(context.Key); - Assert.NotEqual("", context.Key); - } } [Fact] From e8354f856c83f357b69b2b14074e8cdfed6d9aa4 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jun 2022 15:59:18 -0700 Subject: [PATCH 407/499] actually in .NET it's a GUID, not a UUID --- src/LaunchDarkly.ClientSdk/LdClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 0e83b5cb..610c370b 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -34,7 +34,7 @@ namespace LaunchDarkly.Sdk.Client /// you can also tell the SDK to generate a randomized identifier and use this as the context's /// . To do this, set the context's /// property to and set its key to . The generated key will - /// be a pseudo-random UUID. If you subsequently set the current context to another context like this + /// be a pseudo-random GUID. If you subsequently set the current context to another context like this /// during the lifetime of the LdClient, it will reuse the same generated key; and if persistent /// storage is available (see , it will cache the key so that it will /// also be reused even if the app is restarted. If persistent storage is not available, then the SDK would From 81d4b811a066fd7bcfa3dbefe3a6df0a02709265 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 16 Jun 2022 16:50:16 -0700 Subject: [PATCH 408/499] generate & cache randomized keys per context kind --- .../Internal/ContextDecorator.cs | 42 ++-- .../DataStores/PersistentDataStoreWrapper.cs | 12 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 4 + .../Internal/ContextDecoratorTest.cs | 179 ++++++++++++++++++ .../PersistentDataStoreWrapperTest.cs | 18 +- 5 files changed, 231 insertions(+), 24 deletions(-) create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index 55689bfc..7b5525d4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using LaunchDarkly.Sdk.Client.Internal.DataStores; namespace LaunchDarkly.Sdk.Client.Internal @@ -7,8 +9,8 @@ internal class ContextDecorator { private readonly PersistentDataStoreWrapper _store; - private string _cachedAnonUserKey = null; - private object _anonUserKeyLock = new object(); + private Dictionary _cachedGeneratedKey = new Dictionary(); + private object _generatedKeyLock = new object(); public ContextDecorator( PersistentDataStoreWrapper store @@ -19,33 +21,47 @@ PersistentDataStoreWrapper store public Context DecorateContext(Context context) { - if (IsAutoContext(context)) + if (context.Multiple) { - var anonKey = GetOrCreateAutoContextKey(); - return Context.BuilderFromContext(context).Key(anonKey).Transient(true).Build(); + if (context.MultiKindContexts.Any(ContextNeedsGeneratedKey)) + { + var builder = Context.MultiBuilder(); + foreach (var c in context.MultiKindContexts) + { + builder.Add(ContextNeedsGeneratedKey(c) ? SingleKindContextWithGeneratedKey(c) : c); + } + return builder.Build(); + } + } + else if (ContextNeedsGeneratedKey(context)) + { + return SingleKindContextWithGeneratedKey(context); } return context; } - private bool IsAutoContext(Context context) => + private Context SingleKindContextWithGeneratedKey(Context context) => + Context.BuilderFromContext(context).Key(GetOrCreateAutoContextKey(context.Kind)).Build(); + + private bool ContextNeedsGeneratedKey(Context context) => context.Transient && context.Key == Constants.AutoKeyMagicValue; // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key - private string GetOrCreateAutoContextKey() + private string GetOrCreateAutoContextKey(ContextKind contextKind) { - lock (_anonUserKeyLock) + lock (_generatedKeyLock) { - if (_cachedAnonUserKey != null) + if (_cachedGeneratedKey.TryGetValue(contextKind, out var key)) { - return _cachedAnonUserKey; + return key; } - var uniqueId = _store?.GetAnonymousUserKey(); + var uniqueId = _store?.GetGeneratedContextKey(contextKind); if (uniqueId is null) { uniqueId = Guid.NewGuid().ToString(); - _store?.SetAnonymousUserKey(uniqueId); + _store?.SetGeneratedContextKey(contextKind, uniqueId); } - _cachedAnonUserKey = uniqueId; + _cachedGeneratedKey[contextKind] = uniqueId; return uniqueId; } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs index 3db9a31d..ee164d99 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/PersistentDataStoreWrapper.cs @@ -96,17 +96,21 @@ public ContextIndex GetIndex() public void SetIndex(ContextIndex index) => HandleErrorsAndLock(() => _persistentStore.SetValue(_environmentNamespace, EnvironmentMetadataKey, index.Serialize())); - public string GetAnonymousUserKey() => - HandleErrorsAndLock(() => _persistentStore.GetValue(_globalNamespace, GlobalAnonContextKey)); + public string GetGeneratedContextKey(ContextKind contextKind) => + HandleErrorsAndLock(() => _persistentStore.GetValue(_globalNamespace, KeyForGeneratedContextKey(contextKind))); - public void SetAnonymousUserKey(string value) => - HandleErrorsAndLock(() => _persistentStore.SetValue(_globalNamespace, GlobalAnonContextKey, value)); + public void SetGeneratedContextKey(ContextKind contextKind, string value) => + HandleErrorsAndLock(() => _persistentStore.SetValue(_globalNamespace, + KeyForGeneratedContextKey(contextKind), value)); public void Dispose() => _persistentStore.Dispose(); private static string KeyForContextId(string contextId) => EnvironmentContextDataKeyPrefix + contextId; + private static string KeyForGeneratedContextKey(ContextKind contextKind) => + contextKind.IsDefault ? GlobalAnonContextKey : (GlobalAnonContextKey + ":" + contextKind.Value); + private void MaybeLogStoreError(Exception e) { if (!_loggedStorageError.GetAndSet(true)) diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 610c370b..ad9e4b6e 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -40,6 +40,10 @@ namespace LaunchDarkly.Sdk.Client /// also be reused even if the app is restarted. If persistent storage is not available, then the SDK would /// generate a different key after a restart. /// + /// + /// If you use more than one in your evaluation contexts, and you request a + /// randomized key as described above, a different key is generated for each kind. + /// /// public sealed class LdClient : ILdClient { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs new file mode 100644 index 00000000..37164d27 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs @@ -0,0 +1,179 @@ +using System; +using System.Linq; +using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Subsystems; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + public class ContextDecoratorTest : BaseTest + { + private static readonly ContextKind Kind1 = ContextKind.Of("kind1"); + private static readonly ContextKind Kind2 = ContextKind.Of("kind2"); + + [Fact] + public void SingleKindNonTransientContextIsUnchanged() + { + var context = Context.Builder("key1").Name("name").Build(); + + AssertHelpers.ContextsEqual(context, + MakeDecoratorWithoutPersistence().DecorateContext(context)); + } + + [Fact] + public void SingleKindTransientContextWithSpecificKeyIsUnchanged() + { + var context = Context.Builder("key1").Transient(true).Name("name").Build(); + + AssertHelpers.ContextsEqual(context, + MakeDecoratorWithoutPersistence().DecorateContext(context)); + } + + [Fact] + public void SingleKindContextGetsGeneratedKey() + { + var context = TestUtil.BuildAutoContext().Name("name").Build(); + + var transformed = MakeDecoratorWithoutPersistence().DecorateContext(context); + + AssertContextHasBeenTransformedWithNewKey(context, transformed); + } + + [Fact] + public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() + { + var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); + var c2 = Context.Builder("key2").Kind(Kind2).Transient(true).Name("name2").Build(); + + var multiContext = Context.NewMulti(c1, c2); + + AssertHelpers.ContextsEqual(multiContext, + MakeDecoratorWithoutPersistence().DecorateContext(multiContext)); + } + + [Fact] + public void MultiKindContextGetsGeneratedKeyForIndividualContext() + { + var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var multiContext = Context.NewMulti(c1, c2); + var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); + + Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), + transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); + + transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); + AssertHelpers.ContextsEqual(c1, c1Transformed); + + transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); + AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + + AssertHelpers.ContextsEqual(Context.NewMulti(c1, c2Transformed), transformedMulti); + } + + [Fact] + public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() + { + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var multiContext = Context.NewMulti(c1, c2); + var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); + + Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), + transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); + + transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); + AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + + transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); + AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + + Assert.NotEqual(c1Transformed.Key, c2Transformed.Key); + + AssertHelpers.ContextsEqual(Context.NewMulti(c1Transformed, c2Transformed), transformedMulti); + } + + [Fact] + public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() + { + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var multiContext = Context.NewMulti(c1, c2); + + var store = new MockPersistentDataStore(); + + var decorator1 = MakeDecoratorWithPersistence(store); + + var transformedMultiA = decorator1.DecorateContext(multiContext); + transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); + AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); + AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + + var decorator2 = MakeDecoratorWithPersistence(store); + + var transformedMultiB = decorator2.DecorateContext(multiContext); + AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); + } + + [Fact] + public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDisabled() + { + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var multiContext = Context.NewMulti(c1, c2); + + var store = new MockPersistentDataStore(); + + var decorator = MakeDecoratorWithoutPersistence(); + + var transformedMultiA = decorator.DecorateContext(multiContext); + transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); + AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); + AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + + var transformedMultiB = decorator.DecorateContext(multiContext); + AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); + } + + [Fact] + public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled() + { + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var multiContext = Context.NewMulti(c1, c2); + + var decorator1 = MakeDecoratorWithoutPersistence(); + + var transformedMultiA = decorator1.DecorateContext(multiContext); + transformedMultiA.TryGetContextByKind(c1.Kind, out var c1TransformedA); + AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedA); + transformedMultiA.TryGetContextByKind(c2.Kind, out var c2TransformedA); + AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedA); + + var decorator2 = MakeDecoratorWithoutPersistence(); + + var transformedMultiB = decorator2.DecorateContext(multiContext); + transformedMultiB.TryGetContextByKind(c1.Kind, out var c1TransformedB); + AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedB); + Assert.NotEqual(c1TransformedA.Key, c1TransformedB.Key); + transformedMultiB.TryGetContextByKind(c2.Kind, out var c2TransformedB); + AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedB); + Assert.NotEqual(c2TransformedA.Key, c2TransformedB.Key); + } + + private ContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store) => + new ContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger)); + + private ContextDecorator MakeDecoratorWithoutPersistence() => + MakeDecoratorWithPersistence(new NullPersistentDataStore()); + + private void AssertContextHasBeenTransformedWithNewKey(Context original, Context transformed) + { + Assert.NotEqual(original.Key, transformed.Key); + AssertHelpers.ContextsEqual(Context.BuilderFromContext(original).Key(transformed.Key).Build(), + transformed); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs index eede63e3..c6bd888a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataStores/PersistentDataStoreWrapperTest.cs @@ -17,7 +17,7 @@ public class PersistentDataStoreWrapperTest : BaseTest private static readonly string UserHash = Base64.UrlSafeSha256Hash(UserKey); private static readonly string ExpectedUserFlagsKey = "flags_" + UserHash; private static readonly string ExpectedIndexKey = "index"; - private static readonly string ExpectedAnonUserKey = "anonUser"; + private static readonly string ExpectedGeneratedContextKey = "anonUser"; private readonly MockPersistentDataStore _persistentStore; private readonly PersistentDataStoreWrapper _wrapper; @@ -98,17 +98,21 @@ public void SetIndex() } [Fact] - public void GetAnonymousUserKey() + public void GetGeneratedContextKey() { - _persistentStore.SetValue(ExpectedGlobalNamespace, ExpectedAnonUserKey, "user1"); - Assert.Equal("user1", _wrapper.GetAnonymousUserKey()); + _persistentStore.SetValue(ExpectedGlobalNamespace, ExpectedGeneratedContextKey, "key1"); + _persistentStore.SetValue(ExpectedGlobalNamespace, ExpectedGeneratedContextKey + ":org", "key2"); + Assert.Equal("key1", _wrapper.GetGeneratedContextKey(ContextKind.Default)); + Assert.Equal("key2", _wrapper.GetGeneratedContextKey(ContextKind.Of("org"))); } [Fact] - public void SetAnonymousUserKey() + public void SetGeneratedContextKey() { - _wrapper.SetAnonymousUserKey("user1"); - Assert.Equal("user1", _persistentStore.GetValue(ExpectedGlobalNamespace, ExpectedAnonUserKey)); + _wrapper.SetGeneratedContextKey(ContextKind.Default, "key1"); + _wrapper.SetGeneratedContextKey(ContextKind.Of("org"), "key2"); + Assert.Equal("key1", _persistentStore.GetValue(ExpectedGlobalNamespace, ExpectedGeneratedContextKey)); + Assert.Equal("key2", _persistentStore.GetValue(ExpectedGlobalNamespace, ExpectedGeneratedContextKey + ":org")); } } } From 88dfc73f6e3fa54f0163b0996a35c2270ed47aff Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 15 Aug 2022 17:02:19 -0700 Subject: [PATCH 409/499] add new config option for auto-generating keys --- src/LaunchDarkly.ClientSdk/Configuration.cs | 17 +++ .../ConfigurationBuilder.cs | 40 +++++++ .../Internal/Constants.cs | 3 - .../Internal/ContextDecorator.cs | 19 ++-- .../LaunchDarkly.ClientSdk.csproj | 4 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 10 +- .../Resources/Resource.designer.cs | 2 +- .../Internal/ContextDecoratorTest.cs | 55 +++++----- .../LdClientTests.cs | 102 +++++++++++------- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 3 +- 10 files changed, 167 insertions(+), 88 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index ddd49ff1..5bbe82ef 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -36,11 +36,13 @@ public sealed class Configuration /// A factory object that creates an implementation of , which will /// receive feature flag data. /// + /// public IDataSourceFactory DataSourceFactory { get; } /// /// True if diagnostic events have been disabled. /// + /// public bool DiagnosticOptOut { get; } /// @@ -49,6 +51,7 @@ public sealed class Configuration /// /// This is only relevant on mobile platforms. /// + /// public bool EnableBackgroundUpdating { get; } /// @@ -61,22 +64,32 @@ public sealed class Configuration /// increases the size of network requests, such information is not sent unless you set this option /// to . /// + /// public bool EvaluationReasons { get; } /// /// A factory object that creates an implementation of , responsible /// for sending analytics events. /// + /// public IEventProcessorFactory EventProcessorFactory { get; } + /// + /// True if the SDK should provide unique keys for anonymous contexts. + /// + /// + public bool GenerateAnonymousKeys { get; } + /// /// HTTP configuration properties for the SDK. /// + /// public HttpConfigurationBuilder HttpConfigurationBuilder { get; } /// /// Logging configuration properties for the SDK. /// + /// public LoggingConfigurationBuilder LoggingConfigurationBuilder { get; } /// @@ -90,16 +103,19 @@ public sealed class Configuration /// /// Whether or not this client is offline. If , no calls to LaunchDarkly will be made. /// + /// public bool Offline { get; } /// /// Persistent storage configuration properties for the SDK. /// + /// public PersistenceConfigurationBuilder PersistenceConfigurationBuilder { get; } /// /// Defines the base service URIs used by SDK components. /// + /// public ServiceEndpoints ServiceEndpoints { get; } /// @@ -157,6 +173,7 @@ internal Configuration(ConfigurationBuilder builder) EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; EventProcessorFactory = builder._eventProcessorFactory; + GenerateAnonymousKeys = builder._generateAnonymousKeys; HttpConfigurationBuilder = builder._httpConfigurationBuilder; LoggingConfigurationBuilder = builder._loggingConfigurationBuilder; MobileKey = builder._mobileKey; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 4a4b14b6..edea4f8a 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -35,6 +35,7 @@ public sealed class ConfigurationBuilder internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; internal IEventProcessorFactory _eventProcessorFactory = null; + internal bool _generateAnonymousKeys = false; internal HttpConfigurationBuilder _httpConfigurationBuilder = null; internal LoggingConfigurationBuilder _loggingConfigurationBuilder = null; internal string _mobileKey; @@ -183,6 +184,45 @@ public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) return this; } + /// + /// Set to to make the SDK provide unique keys for anonymous contexts. + /// + /// + /// + /// If enabled, this option changes the SDK's behavior whenever the (as given to + /// methods like or + /// ) has an + /// property of , as follows: + /// + /// + /// The first time this happens in the application, the SDK will generate a + /// pseudo-random GUID and overwrite the context's with this string. + /// + /// The SDK will then cache this key so that the same key will be reused next time. + /// + /// This uses the same mechanism as the caching of flag values, so if persistent storage + /// is available (see ), the key will persist across restarts; otherwise, + /// it will persist only during the lifetime of the LdClient. + /// + /// + /// If you use multiple s, this behavior is per-kind: that is, a separate + /// randomized key is generated and cached for each context kind. + /// + /// + /// A must always have a key, even if the key will later be overwritten by the + /// SDK, so if you use this functionality you must still provide a placeholder key. This ensures that if + /// the SDK configuration is changed so is no longer enabled, + /// the SDK will still be able to use the context for evaluations. + /// + /// + /// true to enable automatic anonymous key generation + /// the same builder + public ConfigurationBuilder GenerateAnonymousKeys(bool generateAnonymousKeys) + { + _generateAnonymousKeys = generateAnonymousKeys; + return this; + } + /// /// Sets the SDK's networking configuration, using a configuration builder obtained from /// . The builder has methods for setting diff --git a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs index 15b52113..8775a8c2 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Constants.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Constants.cs @@ -12,8 +12,5 @@ internal static class Constants public const string DELETE = "delete"; public const string PING = "ping"; public const string UNIQUE_ID_KEY = "unique_id_key"; - - // Temporary because the current implementation of Context does not allow a null key - public const string AutoKeyMagicValue = "$$$auto"; } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index 7b5525d4..69d256cc 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -8,32 +8,39 @@ namespace LaunchDarkly.Sdk.Client.Internal internal class ContextDecorator { private readonly PersistentDataStoreWrapper _store; + private readonly bool _generateAnonymousKeys; private Dictionary _cachedGeneratedKey = new Dictionary(); private object _generatedKeyLock = new object(); public ContextDecorator( - PersistentDataStoreWrapper store + PersistentDataStoreWrapper store, + bool generateAnonymousKeys ) { _store = store; + _generateAnonymousKeys = generateAnonymousKeys; } public Context DecorateContext(Context context) { + if (!_generateAnonymousKeys) + { + return context; + } if (context.Multiple) { - if (context.MultiKindContexts.Any(ContextNeedsGeneratedKey)) + if (context.MultiKindContexts.Any(c => c.Anonymous)) { var builder = Context.MultiBuilder(); foreach (var c in context.MultiKindContexts) { - builder.Add(ContextNeedsGeneratedKey(c) ? SingleKindContextWithGeneratedKey(c) : c); + builder.Add(c.Anonymous ? SingleKindContextWithGeneratedKey(c) : c); } return builder.Build(); } } - else if (ContextNeedsGeneratedKey(context)) + else if (context.Anonymous) { return SingleKindContextWithGeneratedKey(context); } @@ -43,10 +50,6 @@ public Context DecorateContext(Context context) private Context SingleKindContextWithGeneratedKey(Context context) => Context.BuilderFromContext(context).Key(GetOrCreateAutoContextKey(context.Kind)).Build(); - private bool ContextNeedsGeneratedKey(Context context) => - context.Transient && context.Key == Constants.AutoKeyMagicValue; - // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key - private string GetOrCreateAutoContextKey(ContextKind contextKind) { lock (_generatedKeyLock) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 9933c531..5f826a91 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,9 +40,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index ad9e4b6e..8748e6a8 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -32,13 +32,7 @@ namespace LaunchDarkly.Sdk.Client /// /// Normally, the SDK uses the exact context that you have specified in the . However, /// you can also tell the SDK to generate a randomized identifier and use this as the context's - /// . To do this, set the context's - /// property to and set its key to . The generated key will - /// be a pseudo-random GUID. If you subsequently set the current context to another context like this - /// during the lifetime of the LdClient, it will reuse the same generated key; and if persistent - /// storage is available (see , it will cache the key so that it will - /// also be reused even if the app is restarted. If persistent storage is not available, then the SDK would - /// generate a different key after a restart. + /// ; see . /// /// /// If you use more than one in your evaluation contexts, and you request a @@ -167,7 +161,7 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataStoreSubLog) ); - _contextDecorator = new ContextDecorator(_dataStore.PersistentStore); + _contextDecorator = new ContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); _context = _contextDecorator.DecorateContext(initialContext); // If we had cached data for the new context, set the current in-memory flag data state to use diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index c3238a4a..f7b4580c 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.4.160")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.8.165")] public partial class Resource { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs index 37164d27..908d732a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Subsystems; using Xunit; @@ -12,7 +11,7 @@ public class ContextDecoratorTest : BaseTest private static readonly ContextKind Kind2 = ContextKind.Of("kind2"); [Fact] - public void SingleKindNonTransientContextIsUnchanged() + public void SingleKindNonAnonymousContextIsUnchanged() { var context = Context.Builder("key1").Name("name").Build(); @@ -21,20 +20,20 @@ public void SingleKindNonTransientContextIsUnchanged() } [Fact] - public void SingleKindTransientContextWithSpecificKeyIsUnchanged() + public void SingleKindAnonymousContextIsUnchangedIfConfigOptionIsNotSet() { - var context = Context.Builder("key1").Transient(true).Name("name").Build(); + var context = Context.Builder("key1").Anonymous(true).Name("name").Build(); AssertHelpers.ContextsEqual(context, MakeDecoratorWithoutPersistence().DecorateContext(context)); } [Fact] - public void SingleKindContextGetsGeneratedKey() + public void SingleKindAnonymousContextGetsGeneratedKeyIfConfigOptionIsSet() { var context = TestUtil.BuildAutoContext().Name("name").Build(); - var transformed = MakeDecoratorWithoutPersistence().DecorateContext(context); + var transformed = MakeDecoratorWithoutPersistence(true).DecorateContext(context); AssertContextHasBeenTransformedWithNewKey(context, transformed); } @@ -43,7 +42,7 @@ public void SingleKindContextGetsGeneratedKey() public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() { var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - var c2 = Context.Builder("key2").Kind(Kind2).Transient(true).Name("name2").Build(); + var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); @@ -55,9 +54,9 @@ public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() public void MultiKindContextGetsGeneratedKeyForIndividualContext() { var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); - var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); + var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); @@ -74,10 +73,10 @@ public void MultiKindContextGetsGeneratedKeyForIndividualContext() [Fact] public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); - var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); + var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); @@ -96,13 +95,13 @@ public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() [Fact] public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var store = new MockPersistentDataStore(); - var decorator1 = MakeDecoratorWithPersistence(store); + var decorator1 = MakeDecoratorWithPersistence(store, true); var transformedMultiA = decorator1.DecorateContext(multiContext); transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); @@ -110,7 +109,7 @@ public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); - var decorator2 = MakeDecoratorWithPersistence(store); + var decorator2 = MakeDecoratorWithPersistence(store, true); var transformedMultiB = decorator2.DecorateContext(multiContext); AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); @@ -119,13 +118,13 @@ public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() [Fact] public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDisabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var store = new MockPersistentDataStore(); - var decorator = MakeDecoratorWithoutPersistence(); + var decorator = MakeDecoratorWithoutPersistence(true); var transformedMultiA = decorator.DecorateContext(multiContext); transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); @@ -140,11 +139,11 @@ public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDi [Fact] public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); - var decorator1 = MakeDecoratorWithoutPersistence(); + var decorator1 = MakeDecoratorWithoutPersistence(true); var transformedMultiA = decorator1.DecorateContext(multiContext); transformedMultiA.TryGetContextByKind(c1.Kind, out var c1TransformedA); @@ -152,7 +151,7 @@ public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled transformedMultiA.TryGetContextByKind(c2.Kind, out var c2TransformedA); AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedA); - var decorator2 = MakeDecoratorWithoutPersistence(); + var decorator2 = MakeDecoratorWithoutPersistence(true); var transformedMultiB = decorator2.DecorateContext(multiContext); transformedMultiB.TryGetContextByKind(c1.Kind, out var c1TransformedB); @@ -163,11 +162,11 @@ public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled Assert.NotEqual(c2TransformedA.Key, c2TransformedB.Key); } - private ContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store) => - new ContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger)); + private ContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store, bool generateAnonymousKeys = false) => + new ContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger), generateAnonymousKeys); - private ContextDecorator MakeDecoratorWithoutPersistence() => - MakeDecoratorWithPersistence(new NullPersistentDataStore()); + private ContextDecorator MakeDecoratorWithoutPersistence(bool generateAnonymousKeys = false) => + MakeDecoratorWithPersistence(new NullPersistentDataStore(), generateAnonymousKeys); private void AssertContextHasBeenTransformedWithNewKey(Context original, Context transformed) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index c6e33eb7..58faa0c4 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -8,10 +8,11 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest { - private static readonly Context KeylessAnonUser = TestUtil.BuildAutoContext() - .Set("email", "example") - .Set("other", 3) - .Build(); + private static readonly Context AnonUser = Context.Builder("anon-placeholder-key") + .Anonymous(true) + .Set("email", "example") + .Set("other", 3) + .Build(); public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } @@ -54,80 +55,94 @@ public async void InitPassesUserToDataSource() } [Fact] - public async Task InitWithKeylessAnonUserAddsRandomizedKey() + public async Task InitWithAnonUserAddsRandomizedKey() { // Note, we don't care about polling mode vs. streaming mode for this functionality. - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); string key1; - using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) { key1 = client.Context.Key; Assert.NotNull(key1); Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + Context.BuilderFromContext(AnonUser).Key(key1).Build(), client.Context); } // Starting again should generate a new key, since we've turned off persistence - using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) { var key2 = client.Context.Key; Assert.NotNull(key2); Assert.NotEqual("", key2); Assert.NotEqual(key1, key2); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key2).Build(), + Context.BuilderFromContext(AnonUser).Key(key2).Build(), client.Context); } } [Fact] - public async Task InitWithKeylessAnonUserCanReusePreviousRandomizedKey() + public async Task InitWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + AssertHelpers.ContextsEqual(AnonUser, client.Context); + } + } + + [Fact] + public async Task InitWithAnonUserCanReusePreviousRandomizedKey() { // Note, we don't care about polling mode vs. streaming mode for this functionality. var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())).Build(); + var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())) + .GenerateAnonymousKeys(true).Build(); string key1; - using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) { key1 = client.Context.Key; Assert.NotNull(key1); Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + Context.BuilderFromContext(AnonUser).Key(key1).Build(), client.Context); } // Starting again should reuse the persisted key - using (var client = await TestUtil.CreateClientAsync(config, KeylessAnonUser)) + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) { Assert.Equal(key1, client.Context.Key); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + Context.BuilderFromContext(AnonUser).Key(key1).Build(), client.Context); } } [Fact] - public async void InitWithKeylessAnonUserPassesGeneratedUserToDataSource() + public async void InitWithAnonUserPassesGeneratedUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); var config = BasicConfig() .DataSource(stub.AsFactory()) + .GenerateAnonymousKeys(true) .Build(); - using (var client = await LdClient.InitAsync(config, KeylessAnonUser)) + using (var client = await LdClient.InitAsync(config, AnonUser)) { - Assert.NotEqual(KeylessAnonUser, stub.ReceivedContext); + Assert.NotEqual(AnonUser, stub.ReceivedContext); Assert.Equal(client.Context, stub.ReceivedContext); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(stub.ReceivedContext.Key).Build(), + Context.BuilderFromContext(AnonUser).Key(stub.ReceivedContext.Key).Build(), stub.ReceivedContext); } } @@ -235,21 +250,21 @@ public async void IdentifyPassesUserToDataSource() } [Fact] - public async Task IdentifyWithKeylessAnonUserAddsRandomizedKey() + public async Task IdentifyWithAnonUserAddsRandomizedKey() { - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); string key1; using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { - await client.IdentifyAsync(KeylessAnonUser); + await client.IdentifyAsync(AnonUser); key1 = client.Context.Key; Assert.NotNull(key1); Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + Context.BuilderFromContext(AnonUser).Key(key1).Build(), client.Context); var anonUser2 = TestUtil.BuildAutoContext().Name("other").Build(); @@ -263,67 +278,82 @@ public async Task IdentifyWithKeylessAnonUserAddsRandomizedKey() using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { - await client.IdentifyAsync(KeylessAnonUser); + await client.IdentifyAsync(AnonUser); var key3 = client.Context.Key; Assert.NotNull(key3); Assert.NotEqual("", key3); Assert.NotEqual(key1, key3); // The previously generated key was discarded with the previous client. AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key3).Build(), + Context.BuilderFromContext(AnonUser).Key(key3).Build(), client.Context); } } [Fact] - public async Task IdentifyWithKeylessAnonUserCanReusePersistedRandomizedKey() + public async Task IdentifyWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() + { + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + AssertHelpers.ContextsEqual(AnonUser, client.Context); + } + } + + [Fact] + public async Task IdentifyWithAnonUserCanReusePersistedRandomizedKey() { var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())).Build(); + var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())) + .GenerateAnonymousKeys(true).Build(); string key1; using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { - await client.IdentifyAsync(KeylessAnonUser); + await client.IdentifyAsync(AnonUser); key1 = client.Context.Key; Assert.NotNull(key1); Assert.NotEqual("", key1); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key1).Build(), + Context.BuilderFromContext(AnonUser).Key(key1).Build(), client.Context); } using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) { - await client.IdentifyAsync(KeylessAnonUser); + await client.IdentifyAsync(AnonUser); var key2 = client.Context.Key; Assert.Equal(key1, key2); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(key2).Build(), + Context.BuilderFromContext(AnonUser).Key(key2).Build(), client.Context); } } [Fact] - public async void IdentifyWithKeylessAnonUserPassesGeneratedUserToDataSource() + public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); var config = BasicConfig() .DataSource(stub.AsFactory()) + .GenerateAnonymousKeys(true) .Build(); using (var client = await LdClient.InitAsync(config, BasicUser)) { - await client.IdentifyAsync(KeylessAnonUser); + await client.IdentifyAsync(AnonUser); - Assert.NotEqual(KeylessAnonUser, stub.ReceivedContext); + Assert.NotEqual(AnonUser, stub.ReceivedContext); Assert.Equal(client.Context, stub.ReceivedContext); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(KeylessAnonUser).Key(client.Context.Key).Build(), + Context.BuilderFromContext(AnonUser).Key(client.Context.Key).Build(), stub.ReceivedContext); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index f801cb91..4307a410 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.JsonStream; -using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Json; using Xunit; @@ -32,7 +31,7 @@ public static Context Base64ContextFromUrlPath(string path, string pathPrefix) } public static ContextBuilder BuildAutoContext() => - Context.Builder(Constants.AutoKeyMagicValue).Transient(true); + Context.Builder("placeholder").Anonymous(true); public static T WithClientLock(Func f) { From e0700dc1c1fec2b6f256e5f425d2290dc7655bfe Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Aug 2022 13:31:26 -0700 Subject: [PATCH 410/499] fix Android & iOS CI builds --- .circleci/config.yml | 125 ++++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 65f69fa6..20510cbd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,15 +1,25 @@ -version: 2 +version: 2.1 + +orbs: + android: circleci/android@2.1.2 workflows: - version: 2 test: jobs: - - test-netstandard2.0 - - test-android - - test-ios - -jobs: - test-netstandard2.0: + - test_dotnet_standard + - build_android_and_ios + - test_android: + requires: + - build_android_and_ios + - test_ios: + requires: + - build_android_and_ios + +jobs: + test_dotnet_standard: + # Technically we could omit the "build the SDK" step here if we made this job depend on the build products + # from build_android_and_ios. However, setting up the Linux build is so much faster than the Mac host that + # it' better to let this one run independently so we can get quick CI feedback for any basic problems. docker: - image: ldcircleci/dotnet5-release:1 # This image is based on mcr.microsoft.com/dotnet/sdk:5.0-focal but is in a @@ -42,52 +52,83 @@ jobs: - store_test_results: path: /tmp/circle-reports - test-android: + build_android_and_ios: macos: - xcode: "12.4.0" + xcode: "13.4.1" resource_class: macos.x86.medium.gen2 - environment: - TERM: dumb - QEMU_AUDIO_DRV: none - steps: - checkout - run: name: Install .NET/Xamarin build tools - command: .circleci/scripts/macos-install-xamarin.sh android + command: .circleci/scripts/macos-install-xamarin.sh android ios - run: name: Install Android SDK command: .circleci/scripts/macos-install-android-sdk.sh 27 - run: - name: Set up emulator - command: echo no | avdmanager create avd -n ci-android-avd -f -k "system-images;android-27;default;x86" - - - run: - name: Start emulator - command: $ANDROID_HOME/emulator/emulator -avd ci-android-avd -netdelay none -netspeed full -no-audio -no-window -no-snapshot -no-boot-anim - background: true - timeout: 1200 - no_output_timeout: 2h + name: Build SDK for MonoAndroid81 + # Deliberately building only the version of MonoAndroid that we will use in the tests; + # otherwise we would have to install more SDKs + command: | + msbuild /restore /p:Configuration=debug /p:TargetFramework=MonoAndroid81 \ + src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - run: - name: Build SDK + name: Build SDK for Xamarin.iOS10 + # Unfortunately msbuild doesn't allow us to specify multiple specific target frameworks in one command command: | - msbuild /restore /p:TargetFramework=MonoAndroid81 \ + msbuild /restore /p:Configuration=debug /p:TargetFramework=Xamarin.iOS10 \ src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - run: - name: Build test project + name: Build Android test project command: | - msbuild /restore /t:SignAndroidPackage \ + msbuild /restore /p:Configuration=Debug /t:SignAndroidPackage \ tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj - run: - name: Wait for emulator - command: .circleci/scripts/circle-android wait-for-boot + name: Build iOS test project + command: | + msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator \ + tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj + # Note that we must specify Platform=iPhoneSimulator here explicitly because, when using a current + # version of msbuild with a project file that uses MSBuild.Sdk.Extras, it seems like Platform does *not* + # default to an empty string (I think it defaults to "AnyCPU"), therefore it will try to build it for a + # real iPhone, which will fail because it can't do code signing. We want a debug build that we will only + # be running in the simulator. + + - persist_to_workspace: + root: tests + paths: + - LaunchDarkly.ClientSdk.Android.Tests/bin/Debug + - LaunchDarkly.ClientSdk.iOS.Tests/bin/Debug + + test_android: + executor: + name: android/android-machine + tag: 2022.07.1 + resource-class: large + + steps: + - checkout + + - attach_workspace: + at: tests + + - android/create-avd: + avd-name: ci-android-avd + install: true + system-image: system-images;android-27;default;x86 + + - android/start-emulator: + avd-name: ci-android-avd + no-window: true + wait-for-emulator: true + restore-gradle-cache-post-emulator-launch: false # this isn't a Gradle project + post-emulator-launch-assemble-command: "" # this isn't a Gradle project - run: name: Start capturing log output @@ -117,35 +158,21 @@ jobs: # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a # JUnit-compatible test results file; you'll just have to look at the output. - test-ios: + test_ios: macos: - xcode: "12.4.0" + xcode: "13.4.1" resource_class: macos.x86.medium.gen2 steps: - checkout - - run: - name: Install .NET/Xamarin build tools - command: .circleci/scripts/macos-install-xamarin.sh ios - - - run: - name: Build SDK - command: msbuild /restore /p:Configuration=Debug /p:TargetFramework=Xamarin.iOS10 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + - attach_workspace: + at: tests - - run: - name: Build test project - command: msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj - # Note that we must specify Platform=iPhoneSimulator here explicitly because, when using a current - # version of msbuild with a project file that uses MSBuild.Sdk.Extras, it seems like Platform does *not* - # default to an empty string (I think it defaults to "AnyCPU"), therefore it will try to build it for a - # real iPhone, which will fail because it can't do code signing. We want a debug build that we will only - # be running in the simulator. - - run: name: Start simulator command: | - xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-12 com.apple.CoreSimulator.SimRuntime.iOS-14-4 + xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-12 com.apple.CoreSimulator.SimRuntime.iOS-15-5 xcrun simctl boot xm-ios - run: From aa30065e2b7c952bced6db4668dc226099f80357 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Aug 2022 13:40:53 -0700 Subject: [PATCH 411/499] typo --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 20510cbd..39b31006 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ jobs: test_dotnet_standard: # Technically we could omit the "build the SDK" step here if we made this job depend on the build products # from build_android_and_ios. However, setting up the Linux build is so much faster than the Mac host that - # it' better to let this one run independently so we can get quick CI feedback for any basic problems. + # it's better to let this one run independently so we can get quick CI feedback for any basic problems. docker: - image: ldcircleci/dotnet5-release:1 # This image is based on mcr.microsoft.com/dotnet/sdk:5.0-focal but is in a From 54328906bc3151ee4beeb176252f2aa8875387d7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 16 Aug 2022 14:05:36 -0700 Subject: [PATCH 412/499] add contract tests for contexts + update prerelease CommonSdk --- contract-tests/Representations.cs | 30 ++++++++ contract-tests/SdkClientEntity.cs | 68 +++++++++++++++++++ .../Internal/ContextDecorator.cs | 2 +- .../LaunchDarkly.ClientSdk.csproj | 4 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 2 +- .../Resources/Resource.designer.cs | 3 +- .../Internal/ContextDecoratorTest.cs | 22 +++--- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 2 +- 8 files changed, 116 insertions(+), 17 deletions(-) diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index 73b02240..aaa9eedb 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -72,6 +72,8 @@ public class CommandParams public EvaluateAllFlagsParams EvaluateAll { get; set; } public IdentifyEventParams IdentifyEvent { get; set; } public CustomEventParams CustomEvent { get; set; } + public ContextBuildParams ContextBuild { get; set; } + public ContextConvertParams ContextConvert { get; set; } } public class EvaluateFlagParams @@ -111,4 +113,32 @@ public class CustomEventParams public bool OmitNullData { get; set; } public double? MetricValue { get; set; } } + + public class ContextBuildParams + { + public ContextBuildSingleParams Single; + public ContextBuildSingleParams[] Multi; + } + + public class ContextBuildSingleParams + { + public string Kind; + public string Key; + public string Name; + public bool Anonymous; + public string Secondary; + public string[] Private; + public Dictionary Custom; + } + + public class ContextBuildResponse + { + public string Output; + public string Error; + } + + public class ContextConvertParams + { + public string Input; + } } diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index 25509f54..dd9b94d7 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -4,6 +4,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk; using LaunchDarkly.Sdk.Client; +using LaunchDarkly.Sdk.Json; namespace TestService { @@ -86,6 +87,12 @@ public void Close() _client.Flush(); return (true, null); + case "contextBuild": + return (true, DoContextBuild(command.ContextBuild)); + + case "contextConvert": + return (true, DoContextConvert(command.ContextConvert)); + default: return (false, null); } @@ -183,6 +190,67 @@ private object DoEvaluateAll(EvaluateAllFlagsParams p) }; } + private ContextBuildResponse DoContextBuild(ContextBuildParams p) + { + Context c; + if (p.Multi is null) + { + c = DoContextBuildSingle(p.Single); + } + else + { + var b = Context.MultiBuilder(); + foreach (var s in p.Multi) + { + b.Add(DoContextBuildSingle(s)); + } + c = b.Build(); + } + if (c.Valid) + { + return new ContextBuildResponse { Output = LdJsonSerialization.SerializeObject(c) }; + } + return new ContextBuildResponse { Error = c.Error }; + } + + private Context DoContextBuildSingle(ContextBuildSingleParams s) + { + var b = Context.Builder(s.Key) + .Kind(s.Kind) + .Name(s.Name) + .Secondary(s.Secondary) + .Anonymous(s.Anonymous); + if (!(s.Private is null)) + { + b.Private(s.Private); + } + if (!(s.Custom is null)) + { + foreach (var kv in s.Custom) + { + b.Set(kv.Key, kv.Value); + } + } + return b.Build(); + } + + private ContextBuildResponse DoContextConvert(ContextConvertParams p) + { + try + { + var c = LdJsonSerialization.DeserializeObject(p.Input); + if (c.Valid) + { + return new ContextBuildResponse { Output = LdJsonSerialization.SerializeObject(c) }; + } + return new ContextBuildResponse { Error = c.Error }; + } + catch (Exception e) + { + return new ContextBuildResponse { Error = e.ToString() }; + } + } + private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) { var builder = Configuration.Builder(sdkParams.Credential); diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs index 7b5525d4..7e1e3119 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs @@ -44,7 +44,7 @@ private Context SingleKindContextWithGeneratedKey(Context context) => Context.BuilderFromContext(context).Key(GetOrCreateAutoContextKey(context.Kind)).Build(); private bool ContextNeedsGeneratedKey(Context context) => - context.Transient && context.Key == Constants.AutoKeyMagicValue; + context.Anonymous && context.Key == Constants.AutoKeyMagicValue; // The use of a magic constant here is temporary because the current implementation of Context doesn't allow a null key private string GetOrCreateAutoContextKey(ContextKind contextKind) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 9933c531..5f826a91 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -40,9 +40,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index ad9e4b6e..0d7d306f 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -32,7 +32,7 @@ namespace LaunchDarkly.Sdk.Client /// /// Normally, the SDK uses the exact context that you have specified in the . However, /// you can also tell the SDK to generate a randomized identifier and use this as the context's - /// . To do this, set the context's + /// . To do this, set the context's /// property to and set its key to . The generated key will /// be a pseudo-random GUID. If you subsequently set the current context to another context like this /// during the lifetime of the LdClient, it will reuse the same generated key; and if persistent diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index c3238a4a..ef9f9779 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,6 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -14,7 +15,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.4.160")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.0.0.73")] public partial class Resource { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs index 37164d27..ee84042b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs @@ -23,7 +23,7 @@ public void SingleKindNonTransientContextIsUnchanged() [Fact] public void SingleKindTransientContextWithSpecificKeyIsUnchanged() { - var context = Context.Builder("key1").Transient(true).Name("name").Build(); + var context = Context.Builder("key1").Anonymous(true).Name("name").Build(); AssertHelpers.ContextsEqual(context, MakeDecoratorWithoutPersistence().DecorateContext(context)); @@ -43,7 +43,7 @@ public void SingleKindContextGetsGeneratedKey() public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() { var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - var c2 = Context.Builder("key2").Kind(Kind2).Transient(true).Name("name2").Build(); + var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); @@ -55,7 +55,7 @@ public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() public void MultiKindContextGetsGeneratedKeyForIndividualContext() { var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); @@ -74,8 +74,8 @@ public void MultiKindContextGetsGeneratedKeyForIndividualContext() [Fact] public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var transformedMulti = MakeDecoratorWithoutPersistence().DecorateContext(multiContext); @@ -96,8 +96,8 @@ public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() [Fact] public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var store = new MockPersistentDataStore(); @@ -119,8 +119,8 @@ public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() [Fact] public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDisabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var store = new MockPersistentDataStore(); @@ -140,8 +140,8 @@ public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDi [Fact] public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled() { - var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Transient(true).Name("name1").Build(); - var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Transient(true).Name("name2").Build(); + var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); var multiContext = Context.NewMulti(c1, c2); var decorator1 = MakeDecoratorWithoutPersistence(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index f801cb91..dd47044c 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -32,7 +32,7 @@ public static Context Base64ContextFromUrlPath(string path, string pathPrefix) } public static ContextBuilder BuildAutoContext() => - Context.Builder(Constants.AutoKeyMagicValue).Transient(true); + Context.Builder(Constants.AutoKeyMagicValue).Anonymous(true); public static T WithClientLock(Func f) { From b0aaf79ad548e6cf5183633693354f5331fe994f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Aug 2022 16:05:39 -0700 Subject: [PATCH 413/499] don't use .NET Core 3.1 or .NET 5.0 for testing --- .circleci/config.yml | 19 ++++++++++--------- Makefile | 14 ++++---------- contract-tests/TestService.csproj | 2 +- scripts/build-contract-tests.sh | 2 ++ scripts/run-contract-tests.sh | 8 ++++++++ scripts/start-contract-test-service.sh | 2 ++ 6 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 scripts/build-contract-tests.sh create mode 100644 scripts/run-contract-tests.sh create mode 100644 scripts/start-contract-test-service.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 39b31006..cb1d29cd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,17 +21,13 @@ jobs: # from build_android_and_ios. However, setting up the Linux build is so much faster than the Mac host that # it's better to let this one run independently so we can get quick CI feedback for any basic problems. docker: - - image: ldcircleci/dotnet5-release:1 - # This image is based on mcr.microsoft.com/dotnet/sdk:5.0-focal but is in a - # slightly better state for us to install the make tool (apt-get update has - # already been done). See: https://github.com/launchdarkly/sdks-ci-docker + - image: mcr.microsoft.com/dotnet/core/sdk:3.1-focal environment: ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests TEST_HARNESS_PARAMS: -junit /tmp/circle-reports/contract-tests-junit.xml - TESTFRAMEWORK: net5.0 + TESTFRAMEWORK: netcoreapp3.1 steps: - checkout - - run: apt install -y make - run: mkdir -p /tmp/circle-reports - run: dotnet restore src/LaunchDarkly.ClientSdk - run: dotnet build src/LaunchDarkly.ClientSdk -f netstandard2.0 @@ -43,11 +39,16 @@ jobs: --logger:"junit;LogFilePath=/tmp/circle-reports/unit-tests.xml" \ tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj - - run: make build-contract-tests - run: - command: make start-contract-test-service + name: build contract tests + command: ./scripts/build-contract-tests.sh + - run: + name: run contract test service + command: ./scripts/start-contract-test-service.sh background: true - - run: make run-contract-tests + - run: + name: run contract tests + command: ./scripts/run-contract-tests.sh - store_test_results: path: /tmp/circle-reports diff --git a/Makefile b/Makefile index 3b8eb791..30cc3b6c 100644 --- a/Makefile +++ b/Makefile @@ -9,25 +9,19 @@ clean: dotnet clean TEMP_TEST_OUTPUT=/tmp/sdk-contract-test-service.log -BUILDFRAMEWORKS ?= netcoreapp2.1 -TESTFRAMEWORK ?= netcoreapp2.1 - -# temporary skips for contract tests that can't pass till more U2C work is done -# TEST_HARNESS_PARAMS := build-contract-tests: - @cd contract-tests && dotnet build TestService.csproj + @./scripts/build-contract-tests.sh start-contract-test-service: - @cd contract-tests && dotnet bin/Debug/${TESTFRAMEWORK}/ContractTestService.dll + @./scripts/start-contract-test-service.sh start-contract-test-service-bg: @echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)" - @make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 & + @./scripts/start-contract-test-service.sh >$(TEMP_TEST_OUTPUT) 2>&1 & run-contract-tests: - @curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh \ - | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh + @./scripts/run-contract-tests.sh contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests diff --git a/contract-tests/TestService.csproj b/contract-tests/TestService.csproj index 132a889b..456c8ff0 100644 --- a/contract-tests/TestService.csproj +++ b/contract-tests/TestService.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp3.1 $(TESTFRAMEWORK) portable ContractTestService diff --git a/scripts/build-contract-tests.sh b/scripts/build-contract-tests.sh new file mode 100644 index 00000000..59b9d536 --- /dev/null +++ b/scripts/build-contract-tests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cd contract-tests && dotnet build TestService.csproj diff --git a/scripts/run-contract-tests.sh b/scripts/run-contract-tests.sh new file mode 100644 index 00000000..ddcfaf19 --- /dev/null +++ b/scripts/run-contract-tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# can add options here for filtering contract tests +TEST_HARNESS_EXTRA_PARAMS= + +curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh \ + | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end \ + ${TEST_HARNESS_PARAMS:-} ${TEST_HARNESS_EXTRA_PARAMS:-}" sh diff --git a/scripts/start-contract-test-service.sh b/scripts/start-contract-test-service.sh new file mode 100644 index 00000000..f33817b9 --- /dev/null +++ b/scripts/start-contract-test-service.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cd contract-tests && dotnet bin/Debug/${TESTFRAMEWORK:-netcoreapp3.1}/ContractTestService.dll From ecd51ac292c6d98749ee57e67a6d3133b8542ef5 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Aug 2022 16:11:15 -0700 Subject: [PATCH 414/499] downgrade MSBuild.Sdk.Extras to work in .NET Core 3.1 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 679841e9..554be864 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "3.0.38" + "MSBuild.Sdk.Extras": "3.0.22" } } From 2130fdbdcb30cb717ee67dfed41a4121d10bffe7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Aug 2022 16:15:00 -0700 Subject: [PATCH 415/499] fix test framework & dependencies --- .../LaunchDarkly.ClientSdk.Tests.csproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index b2330e85..3c8dd279 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -1,14 +1,16 @@ - net5.0 + netcoreapp3.1 LaunchDarkly.ClientSdk.Tests LaunchDarkly.Sdk.Client - - - + + + + + From 0dc938f3201d4791b6cd06c3c8654be41365a71b Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Aug 2022 16:19:08 -0700 Subject: [PATCH 416/499] actually let's use .NET 6.0 --- .circleci/config.yml | 4 ++-- contract-tests/TestService.csproj | 2 +- global.json | 2 +- scripts/start-contract-test-service.sh | 2 +- .../LaunchDarkly.ClientSdk.Tests.csproj | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cb1d29cd..3b8da7ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,11 +21,11 @@ jobs: # from build_android_and_ios. However, setting up the Linux build is so much faster than the Mac host that # it's better to let this one run independently so we can get quick CI feedback for any basic problems. docker: - - image: mcr.microsoft.com/dotnet/core/sdk:3.1-focal + - image: mcr.microsoft.com/dotnet/sdk:6.0-focal environment: ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests TEST_HARNESS_PARAMS: -junit /tmp/circle-reports/contract-tests-junit.xml - TESTFRAMEWORK: netcoreapp3.1 + TESTFRAMEWORK: net6.0 steps: - checkout - run: mkdir -p /tmp/circle-reports diff --git a/contract-tests/TestService.csproj b/contract-tests/TestService.csproj index 456c8ff0..b13c641e 100644 --- a/contract-tests/TestService.csproj +++ b/contract-tests/TestService.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 $(TESTFRAMEWORK) portable ContractTestService diff --git a/global.json b/global.json index 554be864..679841e9 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "3.0.22" + "MSBuild.Sdk.Extras": "3.0.38" } } diff --git a/scripts/start-contract-test-service.sh b/scripts/start-contract-test-service.sh index f33817b9..74cbc37c 100644 --- a/scripts/start-contract-test-service.sh +++ b/scripts/start-contract-test-service.sh @@ -1,2 +1,2 @@ #!/bin/bash -cd contract-tests && dotnet bin/Debug/${TESTFRAMEWORK:-netcoreapp3.1}/ContractTestService.dll +cd contract-tests && dotnet bin/Debug/${TESTFRAMEWORK:-net6.0}/ContractTestService.dll diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index 3c8dd279..9b9ba827 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -1,6 +1,6 @@ - netcoreapp3.1 + net6.0 LaunchDarkly.ClientSdk.Tests LaunchDarkly.Sdk.Client From c308cfef0d9b417b1149c23b9b847d9accf7caf8 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 17 Aug 2022 16:20:36 -0700 Subject: [PATCH 417/499] fix script permissions --- scripts/build-contract-tests.sh | 0 scripts/run-contract-tests.sh | 0 scripts/start-contract-test-service.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/build-contract-tests.sh mode change 100644 => 100755 scripts/run-contract-tests.sh mode change 100644 => 100755 scripts/start-contract-test-service.sh diff --git a/scripts/build-contract-tests.sh b/scripts/build-contract-tests.sh old mode 100644 new mode 100755 diff --git a/scripts/run-contract-tests.sh b/scripts/run-contract-tests.sh old mode 100644 new mode 100755 diff --git a/scripts/start-contract-test-service.sh b/scripts/start-contract-test-service.sh old mode 100644 new mode 100755 From 8a910e2a727787c9ff14c461ba35e9e42abe0c66 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 25 Aug 2022 14:04:47 -0700 Subject: [PATCH 418/499] (#4) use System.Text.Json directly instead of LaunchDarkly.JsonStream (#170) --- .ldrelease/config.yml | 4 +- .ldrelease/mac-test.sh | 2 +- contract-tests/Representations.cs | 28 ++-- contract-tests/TestService.cs | 3 - contract-tests/TestService.csproj | 13 +- docs-src/namespaces/LaunchDarkly.Sdk.Json.md | 3 +- src/LaunchDarkly.ClientSdk/DataModel.cs | 122 ++++++++---------- .../Internal/DataModelSerialization.cs | 27 ++-- .../Internal/DataSources/PollingDataSource.cs | 4 +- .../DataSources/StreamingDataSource.cs | 4 +- .../Internal/DataStores/ContextIndex.cs | 53 +++++--- .../Internal/JsonUtils.cs | 24 ++++ .../LaunchDarkly.ClientSdk.csproj | 53 +------- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 2 +- .../Resources/Resource.designer.cs | 3 +- .../LaunchDarkly.ClientSdk.Tests.csproj | 12 +- .../MockComponents.cs | 5 +- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 13 +- .../LaunchDarkly.ClientSdk.iOS.Tests.csproj | 2 +- 19 files changed, 183 insertions(+), 194 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Internal/JsonUtils.cs diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml index e6c77181..676e6fa6 100644 --- a/.ldrelease/config.yml +++ b/.ldrelease/config.yml @@ -13,7 +13,7 @@ jobs: # The main build happens on a Mac host - circleCI: mac: - xcode: "12.4.0" + xcode: "13.4.1" context: org-global steps: - step: prepare @@ -23,7 +23,7 @@ jobs: # Documentation is built in a Linux container - template: - name: dotnet-linux + name: dotnet6-linux skip: - test - publish diff --git a/.ldrelease/mac-test.sh b/.ldrelease/mac-test.sh index 12b5dae1..0fd2072d 100755 --- a/.ldrelease/mac-test.sh +++ b/.ldrelease/mac-test.sh @@ -4,4 +4,4 @@ set -eu # Run the .NET Standard 2.0 unit tests. (Android and iOS tests are run by regular CI jobs) -dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f net5.0 +TESTFRAMEWORK=net6.0 dotnet test tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj -f net6.0 diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index aaa9eedb..ce25890d 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -2,6 +2,10 @@ using System.Collections.Generic; using LaunchDarkly.Sdk; +// Note, in order for System.Text.Json serialization/deserialization to work correctly, the members of +// this class must be properties with get/set, rather than fields. The property names are automatically +// camelCased by System.Text.Json. + namespace TestService { public class Status @@ -116,29 +120,29 @@ public class CustomEventParams public class ContextBuildParams { - public ContextBuildSingleParams Single; - public ContextBuildSingleParams[] Multi; + public ContextBuildSingleParams Single { get; set; } + public ContextBuildSingleParams[] Multi { get; set; } } public class ContextBuildSingleParams { - public string Kind; - public string Key; - public string Name; - public bool Anonymous; - public string Secondary; - public string[] Private; - public Dictionary Custom; + public string Kind { get; set; } + public string Key { get; set; } + public string Name { get; set; } + public bool Anonymous { get; set; } + public string Secondary { get; set; } + public string[] Private { get; set; } + public Dictionary Custom { get; set; } } public class ContextBuildResponse { - public string Output; - public string Error; + public string Output { get; set; } + public string Error { get; set; } } public class ContextConvertParams { - public string Input; + public string Input { get; set; } } } diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs index f285ea03..122bb8a4 100644 --- a/contract-tests/TestService.cs +++ b/contract-tests/TestService.cs @@ -55,9 +55,6 @@ public Webapp(EventWaitHandle quitSignal) var service = new SimpleJsonService(); Handler = service.Handler; - // Tell the service about the custom JSON conversions for LaunchDarkly SDK types like User - service.SetJsonConverters(LaunchDarkly.Sdk.Json.LdJsonNet.Converter); - service.Route(HttpMethod.Get, "/", GetStatus); service.Route(HttpMethod.Delete, "/", ForceQuit); service.Route(HttpMethod.Post, "/", PostCreateClient); diff --git a/contract-tests/TestService.csproj b/contract-tests/TestService.csproj index b13c641e..8c1dce50 100644 --- a/contract-tests/TestService.csproj +++ b/contract-tests/TestService.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -16,20 +16,11 @@ - - - + - - - true - PreserveNewest - - - diff --git a/docs-src/namespaces/LaunchDarkly.Sdk.Json.md b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md index 54961abb..7b9737da 100644 --- a/docs-src/namespaces/LaunchDarkly.Sdk.Json.md +++ b/docs-src/namespaces/LaunchDarkly.Sdk.Json.md @@ -4,8 +4,7 @@ The NuGet package containing these types is [`LaunchDarkly.CommonSdk`](https://w Any LaunchDarkly SDK type that has the marker interface has a canonical JSON encoding that is consistent across all LaunchDarkly SDKs. There are three ways to convert any such type to or from JSON: -* On platforms that support the `System.Text.Json` API, these types already have the necessary attributes to behave correctly with that API. +* When using the `System.Text.Json` API, these types already have the necessary attributes to behave correctly. * You may use the methods and to convert to or from a JSON-encoded string. -* You may use the lower-level `LaunchDarkly.JsonStream` API (https://github.com/launchdarkly/dotnet-jsonstream) in conjunction with the converters in . Earlier versions of the LaunchDarkly SDKs used `Newtonsoft.Json` for JSON serialization, but current versions have no such third-party dependency. Therefore, these types will not work correctly with the reflection-based `JsonConvert` methods in `Newtonsoft.Json` without some extra logic. There is an add-on package, [`LaunchDarkly.CommonSdk.JsonNet`](https://github.com/launchdarkly/dotnet-sdk-common/tree/main/src/LaunchDarkly.CommonSdk.JsonNet), that provides an adapter to make this work; alternatively, you can call and put the resulting JSON output into a `Newtonsoft.Json.Linq.JRaw` value. diff --git a/src/LaunchDarkly.ClientSdk/DataModel.cs b/src/LaunchDarkly.ClientSdk/DataModel.cs index bcec0c0b..fe6def93 100644 --- a/src/LaunchDarkly.ClientSdk/DataModel.cs +++ b/src/LaunchDarkly.ClientSdk/DataModel.cs @@ -1,8 +1,11 @@ using System; -using LaunchDarkly.JsonStream; +using System.Text.Json; +using System.Text.Json.Serialization; +using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Json; using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; +using static LaunchDarkly.Sdk.Internal.JsonConverterHelpers; namespace LaunchDarkly.Sdk.Client { @@ -21,7 +24,7 @@ public static class DataModel /// /// Represents the state of a feature flag evaluation received from LaunchDarkly. /// - [JsonStreamConverter(typeof(FeatureFlagJsonConverter))] + [JsonConverter(typeof(FeatureFlagJsonConverter))] public sealed class FeatureFlag : IEquatable, IJsonSerializable { internal LdValue Value { get; } @@ -81,14 +84,12 @@ internal ItemDescriptor ToItemDescriptor() => new ItemDescriptor(Version, this); } - internal sealed class FeatureFlagJsonConverter : IJsonStreamConverter + internal sealed class FeatureFlagJsonConverter : JsonConverter { - public object ReadJson(ref JReader reader) - { - return ReadJsonValue(ref reader); - } + public override FeatureFlag Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + ReadJsonValue(ref reader); - public static FeatureFlag ReadJsonValue(ref JReader reader) + public static FeatureFlag ReadJsonValue(ref Utf8JsonReader reader) { LdValue value = LdValue.Null; int version = 0; @@ -99,47 +100,34 @@ public static FeatureFlag ReadJsonValue(ref JReader reader) bool trackReason = false; UnixMillisecondTime? debugEventsUntilDate = null; - for (var or = reader.Object(); or.Next(ref reader);) + for (var obj = RequireObject(ref reader); obj.Next(ref reader);) { - // The use of multiple == tests instead of switch allows for a slight optimization on - // some platforms where it wouldn't always need to allocate a string for or.Name. See: - // https://github.com/launchdarkly/dotnet-jsonstream/blob/main/src/LaunchDarkly.JsonStream/PropertyNameToken.cs - var name = or.Name; - if (name == "value") - { - value = LdJsonConverters.LdValueConverter.ReadJsonValue(ref reader); - } - else if (name == "version") - { - version = reader.Int(); - } - else if (name == "flagVersion") - { - flagVersion = reader.IntOrNull(); - } - else if (name == "variation") - { - variation = reader.IntOrNull(); - } - else if (name == "reason") - { - reason = LdJsonConverters.EvaluationReasonConverter.ReadJsonNullableValue(ref reader); - } - else if (name == "trackEvents") - { - trackEvents = reader.Bool(); - } - else if (name == "trackReason") - { - trackReason = reader.Bool(); - } - else if (name == "debugEventsUntilDate") + switch (obj.Name) { - var dt = reader.LongOrNull(); - if (dt.HasValue) - { - debugEventsUntilDate = UnixMillisecondTime.OfMillis(dt.Value); - } + case "value": + value = LdJsonConverters.LdValueConverter.ReadJsonValue(ref reader); + break; + case "version": + version = reader.GetInt32(); + break; + case "flagVersion": + flagVersion = JsonConverterHelpers.GetIntOrNull(ref reader); + break; + case "variation": + variation = JsonConverterHelpers.GetIntOrNull(ref reader); + break; + case "reason": + reason = JsonSerializer.Deserialize(ref reader); + break; + case "trackEvents": + trackEvents = reader.GetBoolean(); + break; + case "trackReason": + trackReason = reader.GetBoolean(); + break; + case "debugEventsUntilDate": + debugEventsUntilDate = JsonSerializer.Deserialize(ref reader); + break; } } @@ -155,32 +143,30 @@ public static FeatureFlag ReadJsonValue(ref JReader reader) ); } - public void WriteJson(object o, IValueWriter writer) - { - if (!(o is FeatureFlag value)) - { - throw new InvalidOperationException(); - } + public override void Write(Utf8JsonWriter writer, FeatureFlag value, JsonSerializerOptions options) => WriteJsonValue(value, writer); - } - public static void WriteJsonValue(FeatureFlag value, IValueWriter writer) + public static void WriteJsonValue(FeatureFlag value, Utf8JsonWriter writer) { - using (var ow = writer.Object()) + writer.WriteStartObject(); + + JsonConverterHelpers.WriteLdValue(writer, "value", value.Value); + writer.WriteNumber("version", value.Version); + JsonConverterHelpers.WriteIntIfNotNull(writer, "flagVersion", value.FlagVersion); + JsonConverterHelpers.WriteIntIfNotNull(writer, "variation", value.Variation); + if (value.Reason.HasValue) { - LdJsonConverters.LdValueConverter.WriteJsonValue(value.Value, ow.Name("value")); - ow.Name("version").Int(value.Version); - ow.MaybeName("flagVersion", value.FlagVersion.HasValue).Int(value.FlagVersion.GetValueOrDefault()); - ow.MaybeName("variation", value.Variation.HasValue).Int(value.Variation.GetValueOrDefault()); - if (value.Reason.HasValue) - { - LdJsonConverters.EvaluationReasonConverter.WriteJsonValue(value.Reason.Value, ow.Name("reason")); - } - ow.MaybeName("trackEvents", value.TrackEvents).Bool(value.TrackEvents); - ow.MaybeName("trackReason", value.TrackReason).Bool(value.TrackReason); - ow.MaybeName("debugEventsUntilDate", value.DebugEventsUntilDate.HasValue) - .Long(value.DebugEventsUntilDate.GetValueOrDefault().Value); + writer.WritePropertyName("reason"); + JsonSerializer.Serialize(writer, value.Reason.Value); } + JsonConverterHelpers.WriteBooleanIfTrue(writer, "trackEvents", value.TrackEvents); + JsonConverterHelpers.WriteBooleanIfTrue(writer, "trackReason", value.TrackReason); + if (value.DebugEventsUntilDate.HasValue) + { + writer.WriteNumber("debugEventsUntilDate", value.DebugEventsUntilDate.Value.Value); + } + + writer.WriteEndObject(); } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs index 6d98a168..a270aa36 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataModelSerialization.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; -using LaunchDarkly.JsonStream; +using System.Text; +using System.Text.Json; +using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Json; using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; +using static LaunchDarkly.Sdk.Internal.JsonConverterHelpers; namespace LaunchDarkly.Sdk.Client.Internal { @@ -37,18 +40,19 @@ internal static string SerializeFlag(FeatureFlag flag) => internal static string SerializeAll(FullDataSet allData) { - var w = JWriter.New(); - using (var ow = w.Object()) + return JsonUtils.WriteJsonAsString(w => { + w.WriteStartObject(); foreach (var item in allData.Items) { if (item.Value.Item != null) { - FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, ow.Name(item.Key)); + w.WritePropertyName(item.Key); + JsonSerializer.Serialize(w, item.Value.Item); } } - } - return w.GetString(); + w.WriteEndObject(); + }); } internal static FeatureFlag DeserializeFlag(string json) @@ -88,18 +92,21 @@ internal static FullDataSet DeserializeAll(string serializedData) internal static FullDataSet DeserializeV1Schema(string serializedData) { var builder = ImmutableList.CreateBuilder>(); - var r = JReader.FromString(serializedData); + var r = new Utf8JsonReader(Encoding.UTF8.GetBytes(serializedData)); + r.Read(); + try { - for (var or = r.Object(); or.Next(ref r);) + for (var obj = RequireObject(ref r); obj.Next(ref r);) { + var name = obj.Name; var flag = FeatureFlagJsonConverter.ReadJsonValue(ref r); - builder.Add(new KeyValuePair(or.Name.ToString(), flag.ToItemDescriptor())); + builder.Add(new KeyValuePair(name, flag.ToItemDescriptor())); } } catch (Exception e) { - throw new InvalidDataException(ParseErrorMessage, r.TranslateException(e)); + throw new InvalidDataException(ParseErrorMessage, e); } return new FullDataSet(builder.ToImmutable()); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs index d9ca1612..9379801b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/PollingDataSource.cs @@ -1,7 +1,7 @@ using System; +using System.IO; using System.Threading; using System.Threading.Tasks; -using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; @@ -101,7 +101,7 @@ private async Task UpdateTaskAsync() ((IDisposable)this).Dispose(); } } - catch (JsonReadException ex) + catch (InvalidDataException ex) { _log.Error("Polling request received malformed data: {0}", LogValues.ExceptionSummary(ex)); _updateSink.UpdateStatus(DataSourceState.Interrupted, diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs index e650025d..caf96d2c 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/StreamingDataSource.cs @@ -1,9 +1,9 @@ using System; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using LaunchDarkly.EventSource; -using LaunchDarkly.JsonStream; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Internal; @@ -152,7 +152,7 @@ private void OnMessage(object sender, EventSource.MessageReceivedEventArgs e) { HandleMessage(e.EventName, e.Message.Data); } - catch (JsonReadException ex) + catch (InvalidDataException ex) { _log.Error("LaunchDarkly service request failed or received invalid data: {0}", LogValues.ExceptionSummary(ex)); diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs index 4193d8c6..ff645bcf 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/ContextIndex.cs @@ -1,14 +1,32 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Text; +using System.Text.Json; using System.Linq; -using LaunchDarkly.JsonStream; +using LaunchDarkly.Sdk.Internal; + +using static LaunchDarkly.Sdk.Internal.JsonConverterHelpers; namespace LaunchDarkly.Sdk.Client.Internal.DataStores { /// /// Used internally to track which contexts have flag data in the persistent store. /// + /// + /// + /// This exists because we can't assume that the persistent store mechanism has an "enumerate + /// all the keys that exist under such-and-such prefix" capability, so we need a table of + /// contents at a fixed location. The only information being tracked here is, for each flag + /// data set that exists in storage, 1. a context identifier (hashed fully-qualified key, as + /// defined by FlagDataManager.ContextIdFor) and 2. the millisecond timestamp when it was + /// last accessed, to support the LRU eviction behavior of FlagDataManager. + /// + /// + /// To minimize overhead, this is stored as JSON in a very simple format: a JSON array where + /// each element is a nested JSON array in the form ["contextId", millisecondTimestamp]. + /// + /// internal class ContextIndex { internal ImmutableList Data { get; } @@ -53,19 +71,20 @@ public ContextIndex Prune(int maxContextsToRetain, out IEnumerable remov /// the JSON representation public string Serialize() { - var w = JWriter.New(); - using (var aw0 = w.Array()) + return JsonUtils.WriteJsonAsString(w => { - foreach (var e in Data) + w.WriteStartArray(); { - using (var aw1 = aw0.Array()) + foreach (var e in Data) { - aw1.String(e.ContextId); - aw1.Long(e.Timestamp.Value); + w.WriteStartArray(); + w.WriteStringValue(e.ContextId); + w.WriteNumberValue(e.Timestamp.Value); + w.WriteEndArray(); } } - } - return w.GetString(); + w.WriteEndArray(); + }); } /// @@ -84,18 +103,18 @@ public static ContextIndex Deserialize(string json) var builder = ImmutableList.CreateBuilder(); try { - var r = JReader.FromString(json); - for (var ar0 = r.Array(); ar0.Next(ref r);) + var r = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + for (var a0 = RequireArray(ref r); a0.Next(ref r);) { - var ar1 = r.Array(); - if (ar1.Next(ref r)) + var a1 = RequireArray(ref r); + if (a1.Next(ref r)) { - var contextId = r.String(); - if (ar1.Next(ref r)) + var contextId = r.GetString(); + if (a1.Next(ref r)) { - var timeMillis = r.Long(); + var timeMillis = r.GetInt64(); builder.Add(new IndexEntry { ContextId = contextId, Timestamp = UnixMillisecondTime.OfMillis(timeMillis) }); - ar1.Next(ref r); + while (a1.Next(ref r)) { } // discard any extra elements } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/JsonUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/JsonUtils.cs new file mode 100644 index 00000000..d9388c10 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/JsonUtils.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace LaunchDarkly.Sdk.Internal +{ + internal static class JsonUtils + { + /// + /// Shortcut for creating a Utf8JsonWriter, doing some action with it, and getting the output as a string. + /// + /// action to create some output + /// the output + public static string WriteJsonAsString(Action serializeAction) + { + var stream = new MemoryStream(); + var w = new Utf8JsonWriter(stream); + serializeAction(w); + w.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 5f826a91..6a30a8e7 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -33,18 +33,14 @@ 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 - - - - - - - - - + + + + + @@ -78,48 +74,9 @@ - - Code - - - Code - - - Code - - - Code - - - Code - Code - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - ../../LaunchDarkly.ClientSdk.snk diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index a81c5e50..ea6aef1d 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -67,7 +67,7 @@ - + diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index f7b4580c..ef9f9779 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,6 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -14,7 +15,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.8.165")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.0.0.73")] public partial class Resource { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index 9b9ba827..effb9ef5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -1,20 +1,22 @@ - net6.0 + + netcoreapp3.1 + $(TESTFRAMEWORK) LaunchDarkly.ClientSdk.Tests LaunchDarkly.Sdk.Client - + - - - diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index bd08cc6d..7e4bfbd6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text; using System.Threading.Tasks; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; @@ -218,11 +219,11 @@ public struct Params public int EventCount; } - public Task SendEventDataAsync(EventDataKind kind, string data, int eventCount) + public Task SendEventDataAsync(EventDataKind kind, byte[] data, int eventCount) { if (!FilterKind.HasValue || kind == FilterKind.Value) { - Calls.Add(new Params { Kind = kind, Data = data, EventCount = eventCount }); + Calls.Add(new Params { Kind = kind, Data = Encoding.UTF8.GetString(data), EventCount = eventCount }); } return Task.FromResult(new EventSenderResult(DeliveryStatus.Succeeded, null)); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 4307a410..d252b68b 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -2,8 +2,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using LaunchDarkly.JsonStream; using LaunchDarkly.Sdk.Client.Subsystems; +using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Json; using Xunit; @@ -132,18 +132,19 @@ public static void ClearClient() internal static string MakeJsonData(FullDataSet data) { - var w = JWriter.New(); - using (var ow = w.Object()) + return JsonUtils.WriteJsonAsString(w => { + w.WriteStartObject(); foreach (var item in data.Items) { if (item.Value.Item != null) { - FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, ow.Name(item.Key)); + w.WritePropertyName(item.Key); + FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, w); } } - } - return w.GetString(); + w.WriteEndObject(); + }); } } } diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj index b54c4710..53f2df9e 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj @@ -75,7 +75,7 @@ - + From ca64d54ec177c7f86355fe5838a323afa37a50bb Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 31 Aug 2022 18:34:43 -0700 Subject: [PATCH 419/499] use new polling endpoints with "context" in path --- .../Internal/StandardEndpoints.cs | 10 ++++---- .../DataSources/FeatureFlagRequestorTests.cs | 24 +++++++++---------- .../DataSources/PollingDataSourceTest.cs | 12 +++++----- .../LDClientEndToEndTests.cs | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs index 07c2417a..cce13fab 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/StandardEndpoints.cs @@ -12,13 +12,13 @@ internal static class StandardEndpoints new Uri("https://mobile.launchdarkly.com") ); - internal static string StreamingGetRequestPath(string userDataBase64) => - "/meval/" + userDataBase64; + internal static string StreamingGetRequestPath(string contextDataBase64) => + "/meval/" + contextDataBase64; internal const string StreamingReportRequestPath = "/meval"; - internal static string PollingRequestGetRequestPath(string userDataBase64) => - "msdk/evalx/users/" + userDataBase64; - internal const string PollingRequestReportRequestPath = "msdk/evalx/user"; + internal static string PollingRequestGetRequestPath(string contextDataBase64) => + "msdk/evalx/contexts/" + contextDataBase64; + internal const string PollingRequestReportRequestPath = "msdk/evalx/context"; internal const string AnalyticsEventsPostRequestPath = "mobile/events/bulk"; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 13651cb7..1170e665 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -29,12 +29,12 @@ public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response [Theory] - [InlineData("", false, "/msdk/evalx/users/", "")] - [InlineData("", true, "/msdk/evalx/users/", "?withReasons=true")] - [InlineData("/basepath", false, "/basepath/msdk/evalx/users/", "")] - [InlineData("/basepath", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] - [InlineData("/basepath/", false, "/basepath/msdk/evalx/users/", "")] - [InlineData("/basepath/", true, "/basepath/msdk/evalx/users/", "?withReasons=true")] + [InlineData("", false, "/msdk/evalx/contexts/", "")] + [InlineData("", true, "/msdk/evalx/contexts/", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/msdk/evalx/contexts/", "")] + [InlineData("/basepath", true, "/basepath/msdk/evalx/contexts/", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/msdk/evalx/contexts/", "")] + [InlineData("/basepath/", true, "/basepath/msdk/evalx/contexts/", "?withReasons=true")] public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync( string baseUriExtraPath, bool withReasons, @@ -72,12 +72,12 @@ string expectedQuery // REPORT mode is known to fail in Android (ch47341) #if !__ANDROID__ [Theory] - [InlineData("", false, "/msdk/evalx/user", "")] - [InlineData("", true, "/msdk/evalx/user", "?withReasons=true")] - [InlineData("/basepath", false, "/basepath/msdk/evalx/user", "")] - [InlineData("/basepath", true, "/basepath/msdk/evalx/user", "?withReasons=true")] - [InlineData("/basepath/", false, "/basepath/msdk/evalx/user", "")] - [InlineData("/basepath/", true, "/basepath/msdk/evalx/user", "?withReasons=true")] + [InlineData("", false, "/msdk/evalx/context", "")] + [InlineData("", true, "/msdk/evalx/context", "?withReasons=true")] + [InlineData("/basepath", false, "/basepath/msdk/evalx/context", "")] + [InlineData("/basepath", true, "/basepath/msdk/evalx/context", "?withReasons=true")] + [InlineData("/basepath/", false, "/basepath/msdk/evalx/context", "")] + [InlineData("/basepath/", true, "/basepath/msdk/evalx/context", "?withReasons=true")] public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync( string baseUriExtraPath, bool withReasons, diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index 25172598..b67495d0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -37,12 +37,12 @@ private IDataSource MakeDataSource(Uri baseUri, Context context, Action IsStreaming ? "Streaming" : "Polling"; From f0260e3709ece5181ccbb55e8be67d246277ccb7 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 24 Oct 2022 18:44:05 -0700 Subject: [PATCH 420/499] use latest prerelease packages where Secondary is removed --- contract-tests/Representations.cs | 1 - contract-tests/SdkClientEntity.cs | 1 - contract-tests/TestService.cs | 1 + src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index ce25890d..92dd04ad 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -130,7 +130,6 @@ public class ContextBuildSingleParams public string Key { get; set; } public string Name { get; set; } public bool Anonymous { get; set; } - public string Secondary { get; set; } public string[] Private { get; set; } public Dictionary Custom { get; set; } } diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index dd9b94d7..edefe729 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -218,7 +218,6 @@ private Context DoContextBuildSingle(ContextBuildSingleParams s) var b = Context.Builder(s.Key) .Kind(s.Kind) .Name(s.Name) - .Secondary(s.Secondary) .Anonymous(s.Anonymous); if (!(s.Private is null)) { diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs index 122bb8a4..63981e4a 100644 --- a/contract-tests/TestService.cs +++ b/contract-tests/TestService.cs @@ -31,6 +31,7 @@ public class Webapp { private static readonly string[] Capabilities = { "client-side", + "context-type", "mobile", "service-endpoints", "singleton", diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 6a30a8e7..49c49570 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + From 1e9ee44635424f8e52dc5329c5fc14d9f7ba7430 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 27 Oct 2022 13:15:20 -0700 Subject: [PATCH 421/499] use more correct path for release credential parameter --- .ldrelease/secrets.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ldrelease/secrets.properties b/.ldrelease/secrets.properties index c1b7d8df..4b099266 100644 --- a/.ldrelease/secrets.properties +++ b/.ldrelease/secrets.properties @@ -9,7 +9,7 @@ dotnet_code_signing_private_key=blob:/code-signing/catamorphic_code_signing_priv dotnet_code_signing_private_key_passphrase=param:/production/common/releasing/code_signing/private_key_passphrase # NuGet API key for publishing packages. -dotnet_nuget_api_key=param:/production/common/services/nuget/api_key +dotnet_nuget_api_key=param:/production/common/releasing/nuget/api_key # Strong-naming key. LaunchDarkly.ClientSdk.snk=blob:/dotnet/LaunchDarkly.ClientSdk.snk From d60df55af1a5da9d9117d5db5b0db3018541d38e Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 22 Nov 2022 14:35:25 -0800 Subject: [PATCH 422/499] replace various factory interfaces with a generic interface --- src/LaunchDarkly.ClientSdk/Components.cs | 20 ++-- src/LaunchDarkly.ClientSdk/Configuration.cs | 12 +-- .../ConfigurationBuilder.cs | 24 ++--- .../Integrations/EventProcessorBuilder.cs | 7 +- .../PersistenceConfigurationBuilder.cs | 10 +- .../Integrations/PollingDataSourceBuilder.cs | 33 +++---- .../StreamingDataSourceBuilder.cs | 41 ++++----- .../Integrations/TestData.cs | 15 +-- .../Internal/ComponentsImpl.cs | 13 +-- .../Internal/DataSources/ConnectionManager.cs | 8 +- .../DataStores/NullPersistentDataStore.cs | 4 +- .../Internal/Events/ClientDiagnosticStore.cs | 4 +- .../LaunchDarkly.ClientSdk.csproj | 3 + src/LaunchDarkly.ClientSdk/LdClient.cs | 10 +- .../Subsystems/IComponentConfigurer.cs | 20 ++++ .../Subsystems/IDataSource.cs | 5 +- .../Subsystems/IDataSourceFactory.cs | 27 ------ .../Subsystems/IDiagnosticDescription.cs | 5 +- .../Subsystems/IEventProcessorFactory.cs | 17 ---- .../Subsystems/IPersistentDataStore.cs | 1 - .../Subsystems/IPersistentDataStoreFactory.cs | 20 ---- .../Subsystems/LdClientContext.cs | 78 +++++++++++++++- .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 5 +- .../ConfigurationTest.cs | 4 +- .../Integrations/TestDataTest.cs | 6 +- .../DataSources/FeatureFlagRequestorTests.cs | 4 +- .../DataSources/PollingDataSourceTest.cs | 3 +- .../DataSources/StreamingDataSourceTest.cs | 9 +- .../LDClientEndToEndTests.cs | 7 +- .../LdClientDataSourceStatusTests.cs | 18 ++-- .../LdClientEventTests.cs | 8 +- .../LdClientTests.cs | 65 +++++++------ .../MockComponents.cs | 91 ++++++++----------- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 2 +- 34 files changed, 305 insertions(+), 294 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/IComponentConfigurer.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs delete mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index 30eb5b30..c20ab0b5 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -17,7 +17,7 @@ namespace LaunchDarkly.Sdk.Client /// apply any desired configuration change to the object that that method returns (such as /// ), and then use the /// corresponding method in (such as - /// ) to use that + /// ) to use that /// configured component in the SDK. /// public static class Components @@ -114,7 +114,7 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// Returns a configuration object that disables analytics events. /// /// - /// Passing this to causes + /// Passing this to causes /// the SDK to discard all analytics events and not send them to LaunchDarkly, regardless of /// any other configuration. /// @@ -125,9 +125,9 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) => /// .Build(); /// /// - /// + /// /// - public static IEventProcessorFactory NoEvents => + public static IComponentConfigurer NoEvents => ComponentsImpl.NullEventProcessorFactory.Instance; /// @@ -208,7 +208,7 @@ public static PersistenceConfigurationBuilder Persistence() => /// /// To use only polling mode, call this method to obtain a builder, change its properties with the /// methods, and pass it to - /// . + /// . /// /// /// Setting to will superseded this @@ -225,7 +225,7 @@ public static PersistenceConfigurationBuilder Persistence() => /// /// a builder for setting polling connection properties /// - /// + /// public static PollingDataSourceBuilder PollingDataSource() => new PollingDataSourceBuilder(); @@ -237,7 +237,7 @@ public static PollingDataSourceBuilder PollingDataSource() => /// The default configuration has events enabled with default settings. If you want to /// customize this behavior, call this method to obtain a builder, change its properties /// with the methods, and pass it to - /// . + /// . /// /// /// To completely disable sending analytics events, use instead. @@ -253,7 +253,7 @@ public static PollingDataSourceBuilder PollingDataSource() => /// /// /// a builder for setting event properties - /// + /// /// public static EventProcessorBuilder SendEvents() => new EventProcessorBuilder(); @@ -291,7 +291,7 @@ public static PollingDataSourceBuilder PollingDataSource() => /// the default behavior, you do not need to call this method. However, if you want to customize the behavior /// of the connection, call this method to obtain a builder, change its properties with the /// methods, and pass it to - /// . + /// . /// /// /// The SDK may still use polling mode sometimes even when streaming mode is enabled, such as @@ -312,7 +312,7 @@ public static PollingDataSourceBuilder PollingDataSource() => /// /// a builder for setting streaming connection properties /// - /// + /// public static StreamingDataSourceBuilder StreamingDataSource() => new StreamingDataSourceBuilder(); } diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 5bbe82ef..1f4b2999 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -36,8 +36,8 @@ public sealed class Configuration /// A factory object that creates an implementation of , which will /// receive feature flag data. /// - /// - public IDataSourceFactory DataSourceFactory { get; } + /// + public IComponentConfigurer DataSource { get; } /// /// True if diagnostic events have been disabled. @@ -71,8 +71,8 @@ public sealed class Configuration /// A factory object that creates an implementation of , responsible /// for sending analytics events. /// - /// - public IEventProcessorFactory EventProcessorFactory { get; } + /// + public IComponentConfigurer Events { get; } /// /// True if the SDK should provide unique keys for anonymous contexts. @@ -168,11 +168,11 @@ public static ConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { - DataSourceFactory = builder._dataSourceFactory; + DataSource = builder._dataSource; DiagnosticOptOut = builder._diagnosticOptOut; EnableBackgroundUpdating = builder._enableBackgroundUpdating; EvaluationReasons = builder._evaluationReasons; - EventProcessorFactory = builder._eventProcessorFactory; + Events = builder._events; GenerateAnonymousKeys = builder._generateAnonymousKeys; HttpConfigurationBuilder = builder._httpConfigurationBuilder; LoggingConfigurationBuilder = builder._loggingConfigurationBuilder; diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index edea4f8a..3ef521af 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -30,11 +30,11 @@ public sealed class ConfigurationBuilder // will replace it with a platform-specific implementation. internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); - internal IDataSourceFactory _dataSourceFactory = null; + internal IComponentConfigurer _dataSource = null; internal bool _diagnosticOptOut = false; internal bool _enableBackgroundUpdating = true; internal bool _evaluationReasons = false; - internal IEventProcessorFactory _eventProcessorFactory = null; + internal IComponentConfigurer _events = null; internal bool _generateAnonymousKeys = false; internal HttpConfigurationBuilder _httpConfigurationBuilder = null; internal LoggingConfigurationBuilder _loggingConfigurationBuilder = null; @@ -54,11 +54,11 @@ internal ConfigurationBuilder(string mobileKey) internal ConfigurationBuilder(Configuration copyFrom) { - _dataSourceFactory = copyFrom.DataSourceFactory; + _dataSource = copyFrom.DataSource; _diagnosticOptOut = copyFrom.DiagnosticOptOut; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; _evaluationReasons = copyFrom.EvaluationReasons; - _eventProcessorFactory = copyFrom.EventProcessorFactory; + _events = copyFrom.Events; _httpConfigurationBuilder = copyFrom.HttpConfigurationBuilder; _loggingConfigurationBuilder = copyFrom.LoggingConfigurationBuilder; _mobileKey = copyFrom.MobileKey; @@ -92,16 +92,16 @@ public Configuration Build() /// to configure them. /// /// - /// This overwrites any previous options set with . + /// This overwrites any previous options set with . /// If you want to set multiple options, set them on the same /// or . /// /// - /// the factory object + /// the factory object /// the same builder - public ConfigurationBuilder DataSource(IDataSourceFactory dataSourceFactory) + public ConfigurationBuilder DataSource(IComponentConfigurer dataSourceConfig) { - _dataSourceFactory = dataSourceFactory; + _dataSource = dataSourceConfig; return this; } @@ -172,15 +172,15 @@ public ConfigurationBuilder EvaluationReasons(bool evaluationReasons) /// disable events with . /// /// - /// This overwrites any previous options set with . + /// This overwrites any previous options set with . /// If you want to set multiple options, set them on the same . /// /// - /// a builder/factory object for event configuration + /// a builder/factory object for event configuration /// the same builder - public ConfigurationBuilder Events(IEventProcessorFactory eventProcessorFactory) + public ConfigurationBuilder Events(IComponentConfigurer eventsConfig) { - _eventProcessorFactory = eventProcessorFactory; + _events = eventsConfig; return this; } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 3b5bb3d7..2819e93a 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -18,7 +18,8 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// The SDK normally buffers analytics events and sends them to LaunchDarkly at intervals. If you want /// to customize this behavior, create a builder with , change its - /// properties with the methods of this class, and pass it to . + /// properties with the methods of this class, and pass it to + /// . /// /// /// @@ -29,7 +30,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class EventProcessorBuilder : IEventProcessorFactory, IDiagnosticDescription + public sealed class EventProcessorBuilder : IComponentConfigurer, IDiagnosticDescription { /// /// The default value for . @@ -180,7 +181,7 @@ public EventProcessorBuilder PrivateAttributes(params string[] attributes) } /// - public IEventProcessor CreateEventProcessor(LdClientContext context) + public IEventProcessor Build(LdClientContext context) { var eventsConfig = MakeEventsConfiguration(context, true); var logger = context.BaseLogger.SubLogger(LogNames.EventsSubLog); diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs index 1e61e814..972c8cf4 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs @@ -13,7 +13,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// /// By default, the SDK uses a persistence mechanism that is specific to each platform, as - /// described in . To use a custom persistence + /// described in . To use a custom persistence /// implementation, or to customize related properties defined in this class, create a builder with /// , change its properties with the methods of this class, and /// pass it to . @@ -41,7 +41,7 @@ public sealed class PersistenceConfigurationBuilder /// public const int UnlimitedCachedContexts = -1; - private IPersistentDataStoreFactory _storeFactory = null; + private IComponentConfigurer _storeFactory = null; private int _maxCachedContexts = DefaultMaxCachedContexts; internal PersistenceConfigurationBuilder() { } @@ -58,7 +58,7 @@ internal PersistenceConfigurationBuilder() { } /// a factory for the custom storage implementation, or /// to use the default implementation /// the builder - public PersistenceConfigurationBuilder Storage(IPersistentDataStoreFactory persistentDataStoreFactory) + public PersistenceConfigurationBuilder Storage(IComponentConfigurer persistentDataStoreFactory) { _storeFactory = persistentDataStoreFactory; return this; @@ -91,10 +91,10 @@ public PersistenceConfigurationBuilder MaxCachedContexts(int maxCachedContexts) return this; } - internal PersistenceConfiguration CreatePersistenceConfiguration(LdClientContext context) => + internal PersistenceConfiguration Build(LdClientContext clientContext) => new PersistenceConfiguration( _storeFactory is null ? PlatformSpecific.LocalStorage.Instance : - _storeFactory.CreatePersistentDataStore(context), + _storeFactory.Build(clientContext), _maxCachedContexts ); } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs index 123ab7b2..44f289bb 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PollingDataSourceBuilder.cs @@ -19,7 +19,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// /// /// To use polling mode, create a builder with , change its properties - /// with the methods of this class, and pass it to . + /// with the methods of this class, and pass it to . /// /// /// Setting to will supersede this @@ -34,7 +34,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class PollingDataSourceBuilder : IDataSourceFactory, IDiagnosticDescription + public sealed class PollingDataSourceBuilder : IComponentConfigurer, IDiagnosticDescription { /// /// The default value for : 5 minutes. @@ -86,40 +86,35 @@ internal PollingDataSourceBuilder PollIntervalNoMinimum(TimeSpan pollInterval) } /// - public IDataSource CreateDataSource( - LdClientContext context, - IDataSourceUpdateSink updateSink, - Context currentContext, - bool inBackground - ) + public IDataSource Build(LdClientContext clientContext) { - if (!inBackground) + if (!clientContext.InBackground) { - context.BaseLogger.Warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); + clientContext.BaseLogger.Warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support"); } var baseUri = StandardEndpoints.SelectBaseUri( - context.ServiceEndpoints, + clientContext.ServiceEndpoints, e => e.PollingBaseUri, "Polling", - context.BaseLogger + clientContext.BaseLogger ); - var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); + var logger = clientContext.BaseLogger.SubLogger(LogNames.DataSourceSubLog); var requestor = new FeatureFlagRequestor( baseUri, - currentContext, - context.EvaluationReasons, - context.Http, + clientContext.CurrentContext, + clientContext.EvaluationReasons, + clientContext.Http, logger ); return new PollingDataSource( - updateSink, - currentContext, + clientContext.DataSourceUpdateSink, + clientContext.CurrentContext, requestor, _pollInterval, TimeSpan.Zero, - context.TaskExecutor, + clientContext.TaskExecutor, logger ); } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs index 53978a16..0afd5ee6 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/StreamingDataSourceBuilder.cs @@ -15,7 +15,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. If you want /// to customize the behavior of the connection, create a builder with , /// change its properties with the methods of this class, and pass it to - /// . + /// . /// /// /// Setting to will supersede this @@ -30,7 +30,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .Build(); /// /// - public sealed class StreamingDataSourceBuilder : IDataSourceFactory, IDiagnosticDescription + public sealed class StreamingDataSourceBuilder : IComponentConfigurer, IDiagnosticDescription { /// /// The default value for : 1000 milliseconds. @@ -85,53 +85,48 @@ public StreamingDataSourceBuilder InitialReconnectDelay(TimeSpan initialReconnec } /// - public IDataSource CreateDataSource( - LdClientContext context, - IDataSourceUpdateSink updateSink, - Context currentContext, - bool inBackground - ) + public IDataSource Build(LdClientContext clientContext) { var baseUri = StandardEndpoints.SelectBaseUri( - context.ServiceEndpoints, + clientContext.ServiceEndpoints, e => e.StreamingBaseUri, "Streaming", - context.BaseLogger + clientContext.BaseLogger ); var pollingBaseUri = StandardEndpoints.SelectBaseUri( - context.ServiceEndpoints, + clientContext.ServiceEndpoints, e => e.PollingBaseUri, "Polling", - context.BaseLogger + clientContext.BaseLogger ); - if (inBackground) + if (clientContext.InBackground) { // When in the background, always use polling instead of streaming return new PollingDataSourceBuilder() .BackgroundPollInterval(_backgroundPollInterval) - .CreateDataSource(context, updateSink, currentContext, true); + .Build(clientContext); } - var logger = context.BaseLogger.SubLogger(LogNames.DataSourceSubLog); + var logger = clientContext.BaseLogger.SubLogger(LogNames.DataSourceSubLog); var requestor = new FeatureFlagRequestor( pollingBaseUri, - currentContext, - context.EvaluationReasons, - context.Http, + clientContext.CurrentContext, + clientContext.EvaluationReasons, + clientContext.Http, logger ); return new StreamingDataSource( - updateSink, - currentContext, + clientContext.DataSourceUpdateSink, + clientContext.CurrentContext, baseUri, - context.EvaluationReasons, + clientContext.EvaluationReasons, _initialReconnectDelay, requestor, - context.Http, + clientContext.Http, logger, - context.DiagnosticStore + clientContext.DiagnosticStore ); } diff --git a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs index d2233123..40565062 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/TestData.cs @@ -45,7 +45,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations /// .VariationForUser("some-user-key", false)); /// /// - public sealed class TestData : IDataSourceFactory + public sealed class TestData : IComponentConfigurer { #region Private fields @@ -196,18 +196,13 @@ public TestData UpdateStatus(DataSourceState newState, DataSourceStatus.ErrorInf } /// - public IDataSource CreateDataSource( - LdClientContext context, - IDataSourceUpdateSink updateSink, - Context currentContext, - bool inBackground - ) + public IDataSource Build(LdClientContext clientContext) { var instance = new DataSourceImpl( this, - updateSink, - currentContext, - context.BaseLogger.SubLogger("DataSource.TestData") + clientContext.DataSourceUpdateSink, + clientContext.CurrentContext, + clientContext.BaseLogger.SubLogger("DataSource.TestData") ); lock (_lock) { diff --git a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs index 0c32cee8..103d7b39 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/ComponentsImpl.cs @@ -5,14 +5,9 @@ namespace LaunchDarkly.Sdk.Client.Internal { internal static class ComponentsImpl { - internal sealed class NullDataSourceFactory : IDataSourceFactory + internal sealed class NullDataSourceFactory : IComponentConfigurer { - public IDataSource CreateDataSource( - LdClientContext context, - IDataSourceUpdateSink updateSink, - Context currentContext, - bool inBackground - ) => + public IDataSource Build(LdClientContext context) => new NullDataSource(); } @@ -25,11 +20,11 @@ public void Dispose() { } public Task Start() => Task.FromResult(true); } - internal sealed class NullEventProcessorFactory : IEventProcessorFactory + internal sealed class NullEventProcessorFactory : IComponentConfigurer { internal static readonly NullEventProcessorFactory Instance = new NullEventProcessorFactory(); - public IEventProcessor CreateEventProcessor(LdClientContext context) => + public IEventProcessor Build(LdClientContext context) => NullEventProcessor.Instance; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs index 189a3fcd..8eeb739d 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/ConnectionManager.cs @@ -30,7 +30,7 @@ internal sealed class ConnectionManager : IDisposable private readonly Logger _log; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly LdClientContext _clientContext; - private readonly IDataSourceFactory _dataSourceFactory; + private readonly IComponentConfigurer _dataSourceFactory; private readonly IDataSourceUpdateSink _updateSink; private readonly IEventProcessor _eventProcessor; private readonly DiagnosticDisablerImpl _diagnosticDisabler; @@ -66,7 +66,7 @@ internal sealed class ConnectionManager : IDisposable internal ConnectionManager( LdClientContext clientContext, - IDataSourceFactory dataSourceFactory, + IComponentConfigurer dataSourceFactory, IDataSourceUpdateSink updateSink, IEventProcessor eventProcessor, DiagnosticDisablerImpl diagnosticDisabler, @@ -284,7 +284,9 @@ private Task OpenOrCloseConnectionIfNecessary(bool mustReinitializeDataSou // started. The state will then be updated as appropriate by the data source either // calling UpdateStatus, or Init which implies UpdateStatus(Valid). _updateSink.UpdateStatus(DataSourceState.Initializing, null); - _dataSource = _dataSourceFactory.CreateDataSource(_clientContext, _updateSink, _context, _inBackground); + _dataSource = _dataSourceFactory.Build( + _clientContext.WithContextAndBackgroundState(_context, _inBackground) + .WithDataSourceUpdateSink(_updateSink)); return _dataSource.Start() .ContinueWith(SetInitializedIfUpdateProcessorStartedSuccessfully); } diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs index 8dbd43a8..661fb377 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataStores/NullPersistentDataStore.cs @@ -2,11 +2,11 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataStores { - internal sealed class NullPersistentDataStoreFactory : IPersistentDataStoreFactory + internal sealed class NullPersistentDataStoreFactory : IComponentConfigurer { internal static readonly NullPersistentDataStoreFactory Instance = new NullPersistentDataStoreFactory(); - public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => + public IPersistentDataStore Build(LdClientContext context) => NullPersistentDataStore.Instance; } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index 9030bfd7..bb7b25e8 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -44,8 +44,8 @@ private IEnumerable GetConfigProperties() .Build(); // Allow each pluggable component to describe its own relevant properties. - yield return GetComponentDescription(_config.DataSourceFactory ?? Components.StreamingDataSource()); - yield return GetComponentDescription(_config.EventProcessorFactory ?? Components.SendEvents()); + yield return GetComponentDescription(_config.DataSource ?? Components.StreamingDataSource()); + yield return GetComponentDescription(_config.Events ?? Components.SendEvents()); yield return GetComponentDescription(_config.HttpConfigurationBuilder ?? Components.HttpConfiguration()); } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 49c49570..bf47ab1a 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -77,6 +77,9 @@ Code + + + ../../LaunchDarkly.ClientSdk.snk diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 8748e6a8..4edb8ee3 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -140,7 +140,7 @@ public sealed class LdClient : ILdClient LdClient(Configuration configuration, Context initialContext, TimeSpan startWaitTime) { _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - var baseContext = new LdClientContext(configuration, this); + var baseContext = new LdClientContext(configuration, initialContext, this); var diagnosticStore = _config.DiagnosticOptOut ? null : new ClientDiagnosticStore(baseContext, _config, startWaitTime); @@ -154,7 +154,7 @@ public sealed class LdClient : ILdClient _log.Info("Starting LaunchDarkly Client {0}", Version); var persistenceConfiguration = (configuration.PersistenceConfigurationBuilder ?? Components.Persistence()) - .CreatePersistenceConfiguration(_clientContext); + .Build(_clientContext); _dataStore = new FlagDataManager( configuration.MobileKey, persistenceConfiguration, @@ -185,15 +185,15 @@ public sealed class LdClient : ILdClient _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); _flagTracker = new FlagTrackerImpl(dataSourceUpdateSink); - var dataSourceFactory = configuration.DataSourceFactory ?? Components.StreamingDataSource(); + var dataSourceFactory = configuration.DataSource ?? Components.StreamingDataSource(); _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); var isConnected = _connectivityStateManager.IsConnected; diagnosticDisabler?.SetDisabled(!isConnected || configuration.Offline); - _eventProcessor = (configuration.EventProcessorFactory ?? Components.SendEvents()) - .CreateEventProcessor(_clientContext); + _eventProcessor = (configuration.Events ?? Components.SendEvents()) + .Build(_clientContext); _eventProcessor.SetOffline(configuration.Offline || !isConnected); _connectionManager = new ConnectionManager( diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IComponentConfigurer.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IComponentConfigurer.cs new file mode 100644 index 00000000..c395ebae --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IComponentConfigurer.cs @@ -0,0 +1,20 @@ + +namespace LaunchDarkly.Sdk.Client.Subsystems +{ + /// + /// The common interface for SDK component factories and configuration builders. Applications should not + /// need to implement this interface. + /// + /// the type of SDK component or configuration object being constructed + public interface IComponentConfigurer + { + /// + /// Called internally by the SDK to create an implementation instance. Applications should not need + /// to call this method. + /// + /// provides configuration properties and other components from the current + /// SDK client instance + /// a instance of the component type + T Build(LdClientContext context); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs index 93e80dd6..a723ea56 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSource.cs @@ -7,12 +7,11 @@ namespace LaunchDarkly.Sdk.Client.Subsystems /// Interface for an object that receives updates to feature flags from LaunchDarkly. /// /// - /// This component uses a push model. When it is created (via ) it is - /// given a reference to an component, which is a write-only abstraction of + /// This component uses a push model. When it is created, the SDK will provide a reference to an + /// component, which is a write-only abstraction of /// the data store. The SDK never requests feature flag data from the , it /// only looks at the last known data that was previously put into the store. /// - /// public interface IDataSource : IDisposable { /// diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs deleted file mode 100644 index 7ff0fc68..00000000 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IDataSourceFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.Subsystems -{ - /// - /// Interface for a factory that creates some implementation of . - /// - /// - /// - public interface IDataSourceFactory - { - /// - /// Called internally by the SDK to create an implementation instance. Applications do not need - /// to call this method. - /// - /// configuration of the current client instance - /// the destination for pushing data and status updates - /// the current evaluation context - /// true if the application is known to be in the background - /// an instance - IDataSource CreateDataSource( - LdClientContext context, - IDataSourceUpdateSink updateSink, - Context currentContext, - bool inBackground - ); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs index 8d061897..abfd1df6 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IDiagnosticDescription.cs @@ -7,9 +7,8 @@ namespace LaunchDarkly.Sdk.Client.Subsystems /// /// /// The SDK uses a simplified JSON representation of its configuration when recording diagnostics data. - /// Any class that implements , , - /// or may choose to contribute values to this representation, - /// although the SDK may or may not use them. + /// Any class that implements may choose to contribute values to + /// this representation, although the SDK may or may not use them. /// /// /// The method should return either or a diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs deleted file mode 100644 index 4478fd97..00000000 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IEventProcessorFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.Subsystems -{ - /// - /// Interface for a factory that creates some implementation of . - /// - public interface IEventProcessorFactory - { - /// - /// Called internally by the SDK to create an implementation instance. Applications do not need - /// to call this method. - /// - /// configuration of the current client instance - /// an IEventProcessor instance - IEventProcessor CreateEventProcessor(LdClientContext context); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs index f3d9dc94..74cbf667 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStore.cs @@ -54,7 +54,6 @@ namespace LaunchDarkly.Sdk.Client.Subsystems /// of this. The SDK will decide whether to log the exception. /// /// - /// public interface IPersistentDataStore : IDisposable { /// diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs b/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs deleted file mode 100644 index 71fcfa18..00000000 --- a/src/LaunchDarkly.ClientSdk/Subsystems/IPersistentDataStoreFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LaunchDarkly.Sdk.Client.Integrations; - -namespace LaunchDarkly.Sdk.Client.Subsystems -{ - /// - /// Interface for a factory that creates some implementation of . - /// - /// - /// - public interface IPersistentDataStoreFactory - { - /// - /// Called internally by the SDK to create an implementation instance. Applications do not need - /// to call this method. - /// - /// configuration of the current client instance - /// an IPersistentDataStore instance - IPersistentDataStore CreatePersistentDataStore(LdClientContext context); - } -} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 8ad90271..8ef3683d 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client.Subsystems /// /// /// - /// Factory interfaces like receive this class as a parameter. + /// The component factory interface receives this class as a parameter. /// Its public properties provide information about the SDK configuration and environment. The SDK /// may also include non-public properties that are relevant only when creating one of the built-in /// component types and are not accessible to custom components. @@ -29,6 +29,16 @@ public sealed class LdClientContext /// public Logger BaseLogger { get; } + /// + /// The current evaluation context. + /// + public Context CurrentContext { get; } + + /// + /// + /// + public IDataSourceUpdateSink DataSourceUpdateSink { get; } + /// /// Whether to enable feature flag updates when the application is running in the background. /// @@ -44,6 +54,11 @@ public sealed class LdClientContext /// public HttpConfiguration Http { get; } + /// + /// True if the application is currently in a background state. + /// + public bool InBackground { get; } + /// /// The configured service base URIs. /// @@ -59,16 +74,21 @@ public sealed class LdClientContext /// Creates an instance. /// /// the SDK configuration + /// the current evaluation context public LdClientContext( - Configuration configuration - ) : this(configuration, null) { } + Configuration configuration, + Context currentContext + ) : this(configuration, currentContext, null) { } internal LdClientContext( string mobileKey, Logger baseLogger, + Context currentContext, + IDataSourceUpdateSink dataSourceUpdateSink, bool enableBackgroundUpdating, bool evaluationReasons, HttpConfiguration http, + bool inBackground, ServiceEndpoints serviceEndpoints, IDiagnosticDisabler diagnosticDisabler, IDiagnosticStore diagnosticStore, @@ -77,9 +97,12 @@ TaskExecutor taskExecutor { MobileKey = mobileKey; BaseLogger = baseLogger; + CurrentContext = currentContext; + DataSourceUpdateSink = dataSourceUpdateSink; EnableBackgroundUpdating = enableBackgroundUpdating; EvaluationReasons = evaluationReasons; Http = http; + InBackground = inBackground; ServiceEndpoints = serviceEndpoints ?? Components.ServiceEndpoints().Build(); DiagnosticDisabler = diagnosticDisabler; DiagnosticStore = diagnosticStore; @@ -91,15 +114,19 @@ TaskExecutor taskExecutor internal LdClientContext( Configuration configuration, + Context currentContext, object eventSender ) : this( configuration.MobileKey, MakeLogger(configuration), + currentContext, + null, configuration.EnableBackgroundUpdating, configuration.EvaluationReasons, (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) .CreateHttpConfiguration(MakeMinimalContext(configuration.MobileKey)), + false, configuration.ServiceEndpoints, null, null, @@ -118,19 +145,59 @@ internal static Logger MakeLogger(Configuration configuration) return logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); } - internal static LdClientContext MakeMinimalContext(string mobileKey) => + internal static LdClientContext MakeMinimalContext( + string mobileKey + ) => new LdClientContext( mobileKey, Logs.None.Logger(""), + new Context(), + null, false, false, HttpConfiguration.Default(), + false, null, null, null, null ); + internal LdClientContext WithContextAndBackgroundState( + Context newCurrentContext, + bool newInBackground + ) => + new LdClientContext( + MobileKey, + BaseLogger, + newCurrentContext, + DataSourceUpdateSink, + EnableBackgroundUpdating, + EvaluationReasons, + Http, + newInBackground, + ServiceEndpoints, + DiagnosticDisabler, + DiagnosticStore, + TaskExecutor); + + internal LdClientContext WithDataSourceUpdateSink( + IDataSourceUpdateSink newDataSourceUpdateSink + ) => + new LdClientContext( + MobileKey, + BaseLogger, + CurrentContext, + newDataSourceUpdateSink, + EnableBackgroundUpdating, + EvaluationReasons, + Http, + InBackground, + ServiceEndpoints, + DiagnosticDisabler, + DiagnosticStore, + TaskExecutor); + internal LdClientContext WithDiagnostics( IDiagnosticDisabler newDiagnosticDisabler, IDiagnosticStore newDiagnosticStore @@ -138,9 +205,12 @@ IDiagnosticStore newDiagnosticStore new LdClientContext( MobileKey, BaseLogger, + CurrentContext, + DataSourceUpdateSink, EnableBackgroundUpdating, EvaluationReasons, Http, + InBackground, ServiceEndpoints, newDiagnosticDisabler, newDiagnosticStore, diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 6d90fa43..f4490fb5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -1,6 +1,7 @@ using System; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal; using Xunit; using Xunit.Abstractions; @@ -45,11 +46,11 @@ protected ConfigurationBuilder BasicConfig() => Configuration.Builder(BasicMobileKey) .BackgroundModeManager(new MockBackgroundModeManager()) .ConnectivityStateManager(new MockConnectivityStateManager(true)) - .DataSource(new MockDataSource().AsSingletonFactory()) + .DataSource(new MockDataSource().AsSingletonFactory()) .Events(Components.NoEvents) .Logging(testLogging) .Persistence( - Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory()) + Components.Persistence().Storage(new MockPersistentDataStore().AsSingletonFactory()) ); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 821cba4e..76c39eb7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -34,7 +34,7 @@ public void BuilderSetsKey() [Fact] public void DataSource() { - var prop = _tester.Property(c => c.DataSourceFactory, (b, v) => b.DataSource(v)); + var prop = _tester.Property(c => c.DataSource, (b, v) => b.DataSource(v)); prop.AssertDefault(null); prop.AssertCanSet(new ComponentsImpl.NullDataSourceFactory()); } @@ -66,7 +66,7 @@ public void EvaluationReasons() [Fact] public void Events() { - var prop = _tester.Property(c => c.EventProcessorFactory, (b, v) => b.Events(v)); + var prop = _tester.Property(c => c.Events, (b, v) => b.Events(v)); prop.AssertDefault(null); prop.AssertCanSet(new ComponentsImpl.NullEventProcessorFactory()); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index 0dd752ad..e56d3a38 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -19,7 +19,7 @@ public class TestDataTest : BaseTest public TestDataTest(ITestOutputHelper testOutput) : base(testOutput) { - _context = new LdClientContext(Configuration.Builder("key").Logging(testLogging).Build()); + _context = new LdClientContext(Configuration.Builder("key").Logging(testLogging).Build(), _initialUser); } [Fact] @@ -170,7 +170,7 @@ public void UsePreconfiguredFlag() private void CreateAndStart() { - var ds = _td.CreateDataSource(_context, _updates, _initialUser, false); + var ds = _td.Build(_context.WithDataSourceUpdateSink(_updates).WithContextAndBackgroundState(_initialUser, false)); var started = ds.Start(); Assert.True(started.IsCompleted); } @@ -212,7 +212,7 @@ private void VerifyFlag(Func builder Action assertion) { var tdTemp = TestData.DataSource(); - using (var ds = tdTemp.CreateDataSource(_context, _updates, _initialUser, false)) + using (var ds = tdTemp.Build(_context.WithDataSourceUpdateSink(_updates).WithContextAndBackgroundState(_initialUser, false))) { ds.Start(); _updates.ExpectInit(_initialUser); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 1170e665..9bfa878f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -52,7 +52,7 @@ string expectedQuery baseUri, _context, withReasons, - new LdClientContext(config).Http, + new LdClientContext(config, _context).Http, testLogger)) { var resp = await requestor.FeatureFlagsAsync(); @@ -97,7 +97,7 @@ string expectedQuery baseUri, _context, withReasons, - new LdClientContext(config).Http, + new LdClientContext(config, _context).Http, testLogger)) { var resp = await requestor.FeatureFlagsAsync(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs index b67495d0..f6d559bf 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/PollingDataSourceTest.cs @@ -30,8 +30,7 @@ private IDataSource MakeDataSource(Uri baseUri, Context context, Action action) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs index e39bf68e..18ee2bbc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LDClientEndToEndTests.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; @@ -271,7 +272,7 @@ string expectedDiagnosticsPath public void OfflineClientUsesCachedFlagsSync() { var sharedPersistenceConfig = Components.Persistence() - .Storage(new MockPersistentDataStore().AsSingletonFactory()); + .Storage(new MockPersistentDataStore().AsSingletonFactory()); // streaming vs. polling should make no difference for this using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) @@ -296,7 +297,7 @@ public void OfflineClientUsesCachedFlagsSync() public async Task OfflineClientUsesCachedFlagsAsync() { var sharedPersistenceConfig = Components.Persistence() - .Storage(new MockPersistentDataStore().AsSingletonFactory()); + .Storage(new MockPersistentDataStore().AsSingletonFactory()); // streaming vs. polling should make no difference for this using (var server = HttpServer.Start(SetupResponse(_flagData1, UpdateMode.Polling))) @@ -494,7 +495,7 @@ public void HttpConfigurationIsAppliedToEvents() private Configuration BaseConfig(Func extraConfig = null) { var builder = BasicConfig() - .Events(new MockEventProcessor().AsSingletonFactory()); + .Events(new MockEventProcessor().AsSingletonFactory()); builder = extraConfig is null ? builder : extraConfig(builder); return builder.Build(); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs index baba8499..62c9e4fa 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientDataSourceStatusTests.cs @@ -1,6 +1,7 @@ using System; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.TestHelpers; using Xunit; using Xunit.Abstractions; @@ -66,7 +67,7 @@ public void DataSourceStatusProviderSendsStatusUpdates() public void DataSourceStatusStartsAsInitializing() { var config = BasicConfig() - .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) @@ -80,7 +81,8 @@ public void DataSourceStatusStartsAsInitializing() [Fact] public void DataSourceStatusRemainsInitializingAfterErrorIfNeverInitialized() { - var dataSourceFactory = new CapturingDataSourceFactory(); + var dataSourceFactory = new CapturingComponentConfigurer( + new MockDataSource().AsSingletonFactory()); var config = BasicConfig() .DataSource(dataSourceFactory) @@ -92,7 +94,8 @@ public void DataSourceStatusRemainsInitializingAfterErrorIfNeverInitialized() client.DataSourceStatusProvider.StatusChanged += statuses.Add; var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(503); - dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + dataSourceFactory.ReceivedClientContext.DataSourceUpdateSink.UpdateStatus( + DataSourceState.Interrupted, errorInfo); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Initializing, newStatus1.State); @@ -103,7 +106,8 @@ public void DataSourceStatusRemainsInitializingAfterErrorIfNeverInitialized() [Fact] public void DataSourceStatusIsInterruptedAfterErrorIfAlreadyInitialized() { - var dataSourceFactory = new CapturingDataSourceFactory(); + var dataSourceFactory = new CapturingComponentConfigurer( + new MockDataSource().AsSingletonFactory()); var config = BasicConfig() .DataSource(dataSourceFactory) @@ -114,13 +118,15 @@ public void DataSourceStatusIsInterruptedAfterErrorIfAlreadyInitialized() var statuses = new EventSink(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; - dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Valid, null); + dataSourceFactory.ReceivedClientContext.DataSourceUpdateSink.UpdateStatus( + DataSourceState.Valid, null); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus1.State); var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(503); - dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); + dataSourceFactory.ReceivedClientContext.DataSourceUpdateSink.UpdateStatus( + DataSourceState.Interrupted, errorInfo); var newStatus2 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Interrupted, newStatus2.State); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs index 14db9724..15740ba6 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientEventTests.cs @@ -13,11 +13,11 @@ public class LdClientEventTests : BaseTest private static readonly Context user = Context.New("userkey"); private readonly TestData _testData = TestData.DataSource(); private MockEventProcessor eventProcessor = new MockEventProcessor(); - private IEventProcessorFactory _factory; + private IComponentConfigurer _factory; public LdClientEventTests(ITestOutputHelper testOutput) : base(testOutput) { - _factory = eventProcessor.AsSingletonFactory(); + _factory = eventProcessor.AsSingletonFactory(); } private LdClient MakeClient(Context c) => @@ -198,7 +198,7 @@ public void VariationSendsFeatureEventForUnknownFlag() public void VariationSendsFeatureEventForUnknownFlagWhenClientIsNotInitialized() { var config = BasicConfig() - .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Events(_factory); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) @@ -307,7 +307,7 @@ public void VariationDetailSendsFeatureEventWithReasonForUnknownFlag() public void VariationSendsFeatureEventWithReasonForUnknownFlagWhenClientIsNotInitialized() { var config = BasicConfig() - .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) + .DataSource(new MockDataSourceThatNeverInitializes().AsSingletonFactory()) .Events(_factory); using (LdClient client = TestUtil.CreateClient(config.Build(), user)) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 58faa0c4..f627a400 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using LaunchDarkly.Sdk.Client.Subsystems; using Xunit; using Xunit.Abstractions; @@ -42,15 +43,16 @@ public async void InitPassesUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); var config = BasicConfig() - .DataSource(stub.AsFactory()) + .DataSource(dataSourceConfig) .Build(); using (var client = await LdClient.InitAsync(config, BasicUser)) { var actualUser = client.Context; // may have been transformed e.g. to add device/OS properties Assert.Equal(BasicUser.Key, actualUser.Key); - Assert.Equal(actualUser, stub.ReceivedContext); + Assert.Equal(actualUser, dataSourceConfig.ReceivedClientContext.CurrentContext); } } @@ -102,7 +104,8 @@ public async Task InitWithAnonUserCanReusePreviousRandomizedKey() { // Note, we don't care about polling mode vs. streaming mode for this functionality. var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())) + var config = BasicConfig().Persistence(Components.Persistence().Storage( + store.AsSingletonFactory())) .GenerateAnonymousKeys(true).Build(); string key1; @@ -132,18 +135,20 @@ public async void InitWithAnonUserPassesGeneratedUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); var config = BasicConfig() - .DataSource(stub.AsFactory()) + .DataSource(dataSourceConfig) .GenerateAnonymousKeys(true) .Build(); using (var client = await LdClient.InitAsync(config, AnonUser)) { - Assert.NotEqual(AnonUser, stub.ReceivedContext); - Assert.Equal(client.Context, stub.ReceivedContext); + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.NotEqual(AnonUser, receivedContext); + Assert.Equal(client.Context, receivedContext); AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(stub.ReceivedContext.Key).Build(), - stub.ReceivedContext); + Context.BuilderFromContext(AnonUser).Key(receivedContext.Key).Build(), + receivedContext); } } @@ -182,19 +187,19 @@ private async Task IdentifyCompletesOnlyWhenNewFlagsAreAvailable(Func - new MockDataSourceFromLambda(context, async () => + var dataSourceFactory = MockComponents.ComponentConfigurerFromLambda(ctx => + new MockDataSourceFromLambda(ctx.CurrentContext, async () => { - switch (context.Key) + switch (ctx.CurrentContext.Key) { case "a": - updates.Init(context, userAFlags); + ctx.DataSourceUpdateSink.Init(ctx.CurrentContext, userAFlags); break; case "b": startedIdentifyUserB.Release(); await canFinishIdentifyUserB.WaitAsync(); - updates.Init(context, userBFlags); + ctx.DataSourceUpdateSink.Init(ctx.CurrentContext, userBFlags); break; } })); @@ -233,19 +238,22 @@ public async void IdentifyPassesUserToDataSource() MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); Context newUser = Context.New("new-user"); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); var config = BasicConfig() - .DataSource(stub.AsFactory()) + .DataSource(dataSourceConfig) .Build(); using (var client = await LdClient.InitAsync(config, BasicUser)) { + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; AssertHelpers.ContextsEqual(BasicUser, client.Context); - Assert.Equal(client.Context, stub.ReceivedContext); + Assert.Equal(client.Context, receivedContext); await client.IdentifyAsync(newUser); + receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; AssertHelpers.ContextsEqual(newUser, client.Context); - Assert.Equal(client.Context, stub.ReceivedContext); + Assert.Equal(client.Context, receivedContext); } } @@ -307,7 +315,8 @@ public async Task IdentifyWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() public async Task IdentifyWithAnonUserCanReusePersistedRandomizedKey() { var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage(store.AsSingletonFactory())) + var config = BasicConfig().Persistence(Components.Persistence().Storage( + store.AsSingletonFactory())) .GenerateAnonymousKeys(true).Build(); string key1; @@ -341,8 +350,9 @@ public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); var config = BasicConfig() - .DataSource(stub.AsFactory()) + .DataSource(dataSourceConfig) .GenerateAnonymousKeys(true) .Build(); @@ -350,11 +360,12 @@ public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() { await client.IdentifyAsync(AnonUser); - Assert.NotEqual(AnonUser, stub.ReceivedContext); - Assert.Equal(client.Context, stub.ReceivedContext); + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.NotEqual(AnonUser, receivedContext); + Assert.Equal(client.Context, receivedContext); AssertHelpers.ContextsEqual( Context.BuilderFromContext(AnonUser).Key(client.Context.Key).Build(), - stub.ReceivedContext); + receivedContext); } } @@ -392,7 +403,7 @@ public void ConnectionChangeShouldStopDataSource() var mockUpdateProc = new MockPollingProcessor(null); var mockConnectivityStateManager = new MockConnectivityStateManager(true); var config = BasicConfig() - .DataSource(mockUpdateProc.AsFactory()) + .DataSource(mockUpdateProc.AsSingletonFactory()) .ConnectivityStateManager(mockConnectivityStateManager) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) @@ -408,7 +419,7 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() var storage = new MockPersistentDataStore(); var data = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); var config = BasicConfig() - .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) + .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) .Offline(true) .Build(); storage.SetupUserData(config.MobileKey, BasicUser.Key, data); @@ -426,7 +437,7 @@ public void FlagsAreSavedToPersistentStorageByDefault() var initialFlags = new DataSetBuilder().Add("flag", 1, LdValue.Of(100), 0).Build(); var config = BasicConfig() .DataSource(MockPollingProcessor.Factory(initialFlags)) - .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) + .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) @@ -442,7 +453,7 @@ public void EventProcessorIsOnlineByDefault() { var eventProcessor = new MockEventProcessor(); var config = BasicConfig() - .Events(eventProcessor.AsSingletonFactory()) + .Events(eventProcessor.AsSingletonFactory()) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { @@ -457,7 +468,7 @@ public void EventProcessorIsOfflineWhenClientIsConfiguredOffline() var eventProcessor = new MockEventProcessor(); var config = BasicConfig() .ConnectivityStateManager(connectivityStateManager) - .Events(eventProcessor.AsSingletonFactory()) + .Events(eventProcessor.AsSingletonFactory()) .Offline(true) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) @@ -486,7 +497,7 @@ public void EventProcessorIsOfflineWhenNetworkIsUnavailable() var eventProcessor = new MockEventProcessor(); var config = BasicConfig() .ConnectivityStateManager(connectivityStateManager) - .Events(eventProcessor.AsSingletonFactory()) + .Events(eventProcessor.AsSingletonFactory()) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs index 7e4bfbd6..e623cc2d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/MockComponents.cs @@ -27,32 +27,51 @@ namespace LaunchDarkly.Sdk.Client internal static class MockComponentExtensions { - public static IDataSourceFactory AsSingletonFactory(this IDataSource instance) => - new SingleDataSourceFactory { Instance = instance }; + // Normally SDK configuration always specifies component factories rather than component instances, + // so that the SDK can handle the component lifecycle and dependency injection. However, in tests, + // we often want to set up a specific component instance; .AsSingletonFactory() wraps it in a + // factory that always returns that instance. + public static IComponentConfigurer AsSingletonFactory(this T instance) => + MockComponents.ComponentConfigurerFromLambda((clientContext) => instance); - public static IEventProcessorFactory AsSingletonFactory(this IEventProcessor instance) => - new SingleEventProcessorFactory { Instance = instance }; + public static IComponentConfigurer AsSingletonFactoryWithDiagnosticDescription(this T instance, LdValue description) => + new SingleComponentFactoryWithDiagnosticDescription { Instance = instance, Description = description }; - public static IPersistentDataStoreFactory AsSingletonFactory(this IPersistentDataStore instance) => - new SinglePersistentDataStoreFactory { Instance = instance }; + private class SingleComponentFactoryWithDiagnosticDescription : IComponentConfigurer, IDiagnosticDescription + { + public T Instance { get; set; } + public LdValue Description { get; set; } + public LdValue DescribeConfiguration(LdClientContext context) => Description; + public T Build(LdClientContext context) => Instance; + } + } - private class SingleDataSourceFactory : IDataSourceFactory + internal static class MockComponents + { + public static IComponentConfigurer ComponentConfigurerFromLambda(Func factory) => + new ComponentConfigurerFromLambdaImpl() { Factory = factory }; + + private class ComponentConfigurerFromLambdaImpl : IComponentConfigurer { - public IDataSource Instance { get; set; } - public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, - Context currentContext, bool inBackground) => Instance; + public Func Factory { get; set; } + public T Build(LdClientContext context) => Factory(context); } + } - private class SingleEventProcessorFactory : IEventProcessorFactory + internal class CapturingComponentConfigurer: IComponentConfigurer + { + private readonly IComponentConfigurer _factory; + public LdClientContext ReceivedClientContext { get; private set; } + + public CapturingComponentConfigurer(IComponentConfigurer factory) { - public IEventProcessor Instance { get; set; } - public IEventProcessor CreateEventProcessor(LdClientContext context) => Instance; + _factory = factory; } - private class SinglePersistentDataStoreFactory : IPersistentDataStoreFactory + public T Build(LdClientContext clientContext) { - public IPersistentDataStore Instance { get; set; } - public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) => Instance; + ReceivedClientContext = clientContext; + return _factory.Build(clientContext); } } @@ -306,38 +325,12 @@ internal ContextIndex InspectContextIndex(string mobileKey) => WithWrapper(mobileKey).GetIndex(); } - internal class CapturingDataSourceFactory : IDataSourceFactory - { - internal IDataSourceUpdateSink UpdateSink; - - public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, Context currentContext, bool inBackground) - { - UpdateSink = updateSink; - return new MockDataSourceThatNeverInitializes(); - } - } - - internal class MockDataSourceFactoryFromLambda : IDataSourceFactory - { - private readonly Func _fn; - - internal MockDataSourceFactoryFromLambda(Func fn) - { - _fn = fn; - } - - public IDataSource CreateDataSource(LdClientContext context, IDataSourceUpdateSink updateSink, Context currentContext, bool inBackground) => - _fn(context, updateSink, currentContext, inBackground); - } - internal class MockPollingProcessor : IDataSource { private IDataSourceUpdateSink _updateSink; private Context _context; private FullDataSet? _data; - public Context ReceivedContext => _context; - public MockPollingProcessor(FullDataSet? data) : this(null, new Context(), data) { } private MockPollingProcessor(IDataSourceUpdateSink updateSink, Context context, FullDataSet? data) @@ -347,17 +340,9 @@ private MockPollingProcessor(IDataSourceUpdateSink updateSink, Context context, _data = data; } - public static IDataSourceFactory Factory(FullDataSet? data) => - new MockDataSourceFactoryFromLambda((ctx, updates, context, bg) => - new MockPollingProcessor(updates, context, data)); - - public IDataSourceFactory AsFactory() => - new MockDataSourceFactoryFromLambda((ctx, updates, context, bg) => - { - this._updateSink = updates; - this._context = context; - return this; - }); + public static IComponentConfigurer Factory(FullDataSet? data) => + MockComponents.ComponentConfigurerFromLambda(clientContext => + new MockPollingProcessor(clientContext.DataSourceUpdateSink, clientContext.CurrentContext, data)); public bool IsRunning { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index d252b68b..e332b024 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -20,7 +20,7 @@ public static class TestUtil private static ThreadLocal InClientLock = new ThreadLocal(); - public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key")); + public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key"), Context.New("userkey")); public static Context Base64ContextFromUrlPath(string path, string pathPrefix) { From 103cf090c7cc28a01f28d2b08e84c05d30be9f72 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 28 Nov 2022 10:34:49 -0800 Subject: [PATCH 423/499] bump LaunchDarkly.Logging to v1.0.2 for sc-177921 --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index d3f9b089..acfbbcd4 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -44,7 +44,7 @@ - + From 1a15cb3d749e02925864a7732fd4ec362101fbf6 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 28 Nov 2022 13:46:46 -0800 Subject: [PATCH 424/499] fix release build to use newer osslsigncode (for 2.x) --- .ldrelease/mac-build.sh | 2 +- .ldrelease/mac-prepare.sh | 27 ++++++++++----------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.ldrelease/mac-build.sh b/.ldrelease/mac-build.sh index 2d768679..96b1ab05 100755 --- a/.ldrelease/mac-build.sh +++ b/.ldrelease/mac-build.sh @@ -13,7 +13,7 @@ msbuild /restore /p:Configuration=Release "${PROJECT_FILE}" # Sign the code with osslsigncode (which was installed in mac-prepare.sh) SIGNCODE_DIR="${LD_RELEASE_TEMP_DIR}/osslsigncode" -SIGNCODE="${SIGNCODE_DIR}/osslsigncode" +SIGNCODE="${SIGNCODE_DIR}/bin/osslsigncode" echo "" echo "Signing assemblies..." diff --git a/.ldrelease/mac-prepare.sh b/.ldrelease/mac-prepare.sh index 9f65d424..6ef2bf32 100755 --- a/.ldrelease/mac-prepare.sh +++ b/.ldrelease/mac-prepare.sh @@ -3,36 +3,29 @@ set -eu set +o pipefail -./.circleci/scripts/macos-install-xamarin.sh android ios -./.circleci/scripts/macos-install-android-sdk.sh 25 26 27 - # Download and build the osslsigncode tool. In our other .NET projects, code signing # is handled by our standard project template scripts for .NET, which use a Docker # image that already has osslsigncode installed. But here we're using a CircleCI job # to do the build, so we need to do the code-signing in the same place and download # the tool as needed. Unfortunately it has to be built from source. -SIGNCODE_DOWNLOAD_URL=https://github.com/mtrojnar/osslsigncode/releases/download/2.1/osslsigncode-2.1.0.tar.gz -SIGNCODE_ARCHIVE="${LD_RELEASE_TEMP_DIR}/osslsigncode-2.1.0.tar.gz" +SIGNCODE_DOWNLOAD_URL=https://github.com/mtrojnar/osslsigncode/releases/download/2.5/osslsigncode-2.5-macOS.zip +SIGNCODE_ARCHIVE="${LD_RELEASE_TEMP_DIR}/osslsigncode.zip" SIGNCODE_DIR="${LD_RELEASE_TEMP_DIR}/osslsigncode" -SIGNCODE="${SIGNCODE_DIR}/osslsigncode" echo "" echo "Downloading osslsigncode..." curl --fail --silent -L "${SIGNCODE_DOWNLOAD_URL}" >"${SIGNCODE_ARCHIVE}" mkdir -p "${SIGNCODE_DIR}" -tar xfz "${SIGNCODE_ARCHIVE}" -C "${SIGNCODE_DIR}" - -echo "" -echo "Building osslsigncode..." -HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config -export PKG_CONFIG=$(which pkg-config) -export PKG_CONFIG_PATH=/usr/local/Cellar/openssl@1.1/1.1.1i/lib/pkgconfig -pushd "${SIGNCODE_DIR}/osslsigncode-2.1.0" -./configure -make -cp osslsigncode .. +pushd "${SIGNCODE_DIR}" && unzip "${SIGNCODE_ARCHIVE}" popd +chmod a+x "${SIGNCODE_DIR}/bin/osslsigncode" # Copy the strong-naming key that was downloaded due to our secrets.properties declaration cp "${LD_RELEASE_SECRETS_DIR}/LaunchDarkly.ClientSdk.snk" . + +# Install the Xamarin frameworks; this will take a while +echo "" +echo "Installing Xamarin..." +./.circleci/scripts/macos-install-xamarin.sh android ios +./.circleci/scripts/macos-install-android-sdk.sh 25 26 27 From 2b70ce5988e3ee03f7d1fba4c2cebf216ec3ff6f Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Wed, 30 Nov 2022 09:51:30 -0800 Subject: [PATCH 425/499] doc comment --- src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 8ef3683d..f4d504ab 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -35,8 +35,13 @@ public sealed class LdClientContext public Context CurrentContext { get; } /// - /// + /// A component that implementations use to deliver status updates to + /// the SDK. /// + /// + /// This property is only set when the SDK is calling an factory. + /// Otherwise it is null. + /// public IDataSourceUpdateSink DataSourceUpdateSink { get; } /// From c09f29dc967849cef9765e45ca9ac256755c3273 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Thu, 1 Dec 2022 12:05:44 -0800 Subject: [PATCH 426/499] allow User to be used interchangeably with Context (#177) * allow User to be used interchangeably with Context * support User type in contract tests * make context nullable in test service commands --- contract-tests/Representations.cs | 6 +- contract-tests/SdkClientEntity.cs | 18 +++- contract-tests/TestService.cs | 3 +- .../ILdClientExtensions.cs | 55 ++++++++++++ .../Interfaces/ILdClient.cs | 4 + .../LaunchDarkly.ClientSdk.csproj | 4 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 86 ++++++++++++++++++- .../Resources/Resource.designer.cs | 2 +- 8 files changed, 166 insertions(+), 12 deletions(-) diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index 92dd04ad..d8cc4d7a 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -65,7 +65,8 @@ public class SdkConfigServiceEndpointsParams public class SdkClientSideParams { public bool? EvaluationReasons { get; set; } - public Context InitialContext { get; set; } + public Context? InitialContext { get; set; } + public User InitialUser { get; set; } public bool? UseReport { get; set; } } @@ -107,7 +108,8 @@ public class EvaluateAllFlagsResponse public class IdentifyEventParams { - public Context Context { get; set; } + public Context? Context { get; set; } + public User User { get; set; } } public class CustomEventParams diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index edefe729..b1eb060e 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -38,7 +38,14 @@ string tag startWaitTime = TimeSpan.FromMilliseconds(sdkParams.StartWaitTimeMs.Value); } - _client = LdClient.Init(config, sdkParams.ClientSide.InitialContext, startWaitTime); + if (sdkParams.ClientSide.InitialContext != null) + { + _client = LdClient.Init(config, sdkParams.ClientSide.InitialContext.Value, startWaitTime); + } + else + { + _client = LdClient.Init(config, sdkParams.ClientSide.InitialUser, startWaitTime); + } if (!_client.Initialized && !sdkParams.InitCanFail) { _client.Dispose(); @@ -64,7 +71,14 @@ public void Close() return (true, DoEvaluateAll(command.EvaluateAll)); case "identifyEvent": - await _client.IdentifyAsync(command.IdentifyEvent.Context); + if (command.IdentifyEvent.Context != null) + { + await _client.IdentifyAsync(command.IdentifyEvent.Context.Value); + } + else + { + await _client.IdentifyAsync(command.IdentifyEvent.User); + } return (true, null); case "customEvent": diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs index 63981e4a..2cbc5fb3 100644 --- a/contract-tests/TestService.cs +++ b/contract-tests/TestService.cs @@ -35,7 +35,8 @@ public class Webapp "mobile", "service-endpoints", "singleton", - "strongly-typed" + "strongly-typed", + "user-type" }; public readonly Handler Handler; diff --git a/src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs b/src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs index ceeef229..2bc8ff37 100644 --- a/src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs +++ b/src/LaunchDarkly.ClientSdk/ILdClientExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Interfaces; namespace LaunchDarkly.Sdk.Client @@ -7,9 +8,29 @@ namespace LaunchDarkly.Sdk.Client /// Convenience methods that extend the interface. /// /// + /// + /// These allow you to do the following: + /// + /// + /// + /// Treat a string-valued flag as if it referenced values of an enum type. + /// + /// + /// Call methods with the type instead of + /// . The SDK's preferred type for identifying an evaluation context, + /// is ; older versions of the SDK used only the simpler + /// model. These extension methods provide backward compatibility with application code that + /// used the type. Each of them simply converts the User to a Context with + /// and calls the equivalent ILdClient method. + /// For instance, client.Identify(user) is exactly equivalent to + /// client.Identify(Context.FromUser(user)). + /// + /// + /// /// These are implemented outside of and because they do not /// rely on any implementation details of ; they are decorators that would work equally /// well with a stub or test implementation of the interface. + /// /// public static class ILdClientExtensions { @@ -71,5 +92,39 @@ public static EvaluationDetail EnumVariationDetail(this ILdClient client, } return new EvaluationDetail(defaultValue, stringDetail.VariationIndex, stringDetail.Reason); } + + /// + /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, + /// and generates an analytics event to tell LaunchDarkly about the user. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the client instance + /// the user; should not be null (a null reference will cause an error + /// to be logged and no event will be sent + /// the maximum time to wait for the new flag values + /// true if new flag values were obtained + /// + public static bool Identify(this ILdClient client, User user, TimeSpan maxWaitTime) => + client.Identify(Context.FromUser(user), maxWaitTime); + + + /// + /// Changes the current user, requests flags for that user from LaunchDarkly if we are online, + /// and generates an analytics event to tell LaunchDarkly about the user. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the client instance + /// the user; should not be null (a null reference will cause an error + /// to be logged and no event will be sent + /// a task that yields true if new flag values were obtained + /// + public static Task IdentifyAsync(this ILdClient client, User user) => + client.IdentifyAsync(Context.FromUser(user)); } } diff --git a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs index 3ff2e0c0..00917519 100644 --- a/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs +++ b/src/LaunchDarkly.ClientSdk/Interfaces/ILdClient.cs @@ -326,6 +326,8 @@ public interface ILdClient : IDisposable /// about setting the context and optionally requesting a unique key for it /// the maximum time to wait for the new flag values /// true if new flag values were obtained + /// + /// bool Identify(Context context, TimeSpan maxWaitTime); /// @@ -346,6 +348,8 @@ public interface ILdClient : IDisposable /// the new evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// a task that yields true if new flag values were obtained + /// + /// Task IdentifyAsync(Context context); /// diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index bf47ab1a..b9e4c44e 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 4edb8ee3..f62738fa 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -269,11 +269,14 @@ async Task StartAsync() /// for the lifetime of your application. /// /// - /// the singleton instance /// the mobile key given to you by LaunchDarkly /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize + /// the singleton instance + /// + /// + /// public static LdClient Init(string mobileKey, Context initialContext, TimeSpan maxWaitTime) { var config = Configuration.Default(mobileKey); @@ -281,6 +284,24 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m return Init(config, initialContext, maxWaitTime); } + /// + /// Creates a new singleton instance and attempts to initialize feature flags. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the mobile key given to you by LaunchDarkly + /// the initial user attributes; see for more + /// about setting the context and optionally requesting a unique key for it + /// the maximum length of time to wait for the client to initialize + /// the singleton instance + /// + /// + /// + public static LdClient Init(string mobileKey, User initialUser, TimeSpan maxWaitTime) => + Init(mobileKey, Context.FromUser(initialUser), maxWaitTime); + /// /// Creates a new singleton instance and attempts to initialize feature flags /// asynchronously. @@ -300,10 +321,10 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m /// for the lifetime of your application. /// /// - /// the singleton instance /// the mobile key given to you by LaunchDarkly /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it + /// a Task that resolves to the singleton LdClient instance public static async Task InitAsync(string mobileKey, Context initialContext) { var config = Configuration.Default(mobileKey); @@ -311,6 +332,20 @@ public static async Task InitAsync(string mobileKey, Context initialCo return await InitAsync(config, initialContext); } + /// + /// Creates a new singleton instance and attempts to initialize feature flags + /// asynchronously. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the mobile key given to you by LaunchDarkly + /// the initial user attributes + /// a Task that resolves to the singleton LdClient instance + public static Task InitAsync(string mobileKey, User initialUser) => + InitAsync(mobileKey, Context.FromUser(initialUser)); + /// /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. @@ -332,13 +367,16 @@ public static async Task InitAsync(string mobileKey, Context initialCo /// for the lifetime of your application. /// /// - /// the singleton LdClient instance /// the client configuration /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize; /// if this time elapses, the method will not throw an exception but will return the client in /// an uninitialized state + /// the singleton LdClient instance + /// + /// + /// public static LdClient Init(Configuration config, Context initialContext, TimeSpan maxWaitTime) { if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) @@ -351,6 +389,26 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp return c; } + /// + /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// fetching Feature Flags. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the client configuration + /// the initial user attributes + /// the maximum length of time to wait for the client to initialize; + /// if this time elapses, the method will not throw an exception but will return the client in + /// an uninitialized state + /// the singleton LdClient instance + /// + /// + /// + public static LdClient Init(Configuration config, User initialUser, TimeSpan maxWaitTime) => + Init(config, Context.FromUser(initialUser), maxWaitTime); + /// /// Creates a new singleton instance and attempts to initialize feature flags /// asynchronously. @@ -370,10 +428,13 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp /// for the lifetime of your application. /// /// - /// the singleton LdClient instance /// the client configuration /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it + /// a Task that resolves to the singleton LdClient instance + /// + /// + /// public static async Task InitAsync(Configuration config, Context initialContext) { var c = CreateInstance(config, initialContext, TimeSpan.Zero); @@ -381,6 +442,23 @@ public static async Task InitAsync(Configuration config, Context initi return c; } + /// + /// Creates a new singleton instance and attempts to initialize feature flags + /// asynchronously. + /// + /// + /// This is equivalent to , but using the + /// type instead of . + /// + /// the client configuration + /// the initial user attributes + /// a Task that resolves to the singleton LdClient instance + /// + /// + /// + public static Task InitAsync(Configuration config, User initialUser) => + InitAsync(config, Context.FromUser(initialUser)); + static LdClient CreateInstance(Configuration configuration, Context initialContext, TimeSpan maxWaitTime) { lock (_createInstanceLock) diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index ef9f9779..7f4e9317 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -15,7 +15,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.0.0.73")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] public partial class Resource { From b457ae04875ff0c16acb7d2036718189b9933f86 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Mon, 5 Dec 2022 18:29:03 -0800 Subject: [PATCH 427/499] use latest package versions --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index b9e4c44e..ff5a3de9 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -1,7 +1,7 @@ - 2.0.1 + 3.0.0-alpha.1 - + + + + + + + + diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs new file mode 100644 index 00000000..42c3a645 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -0,0 +1,109 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System.Globalization; +using Android.Content; +using Android.Content.PM; +using Android.Content.Res; +using Android.Provider; +#if __ANDROID_29__ +using AndroidX.Core.Content.PM; +#else +using Android.Support.V4.Content.PM; +#endif + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + static string PlatformGetPackageName() => Platform.AppContext.PackageName; + + static string PlatformGetName() + { + var applicationInfo = Platform.AppContext.ApplicationInfo; + var packageManager = Platform.AppContext.PackageManager; + return applicationInfo.LoadLabel(packageManager); + } + + static string PlatformGetVersionString() + { + var pm = Platform.AppContext.PackageManager; + var packageName = Platform.AppContext.PackageName; +#pragma warning disable CS0618 // Type or member is obsolete + using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) +#pragma warning restore CS0618 // Type or member is obsolete + { + return info.VersionName; + } + } + + static string PlatformGetBuild() + { + var pm = Platform.AppContext.PackageManager; + var packageName = Platform.AppContext.PackageName; +#pragma warning disable CS0618 // Type or member is obsolete + using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) +#pragma warning restore CS0618 // Type or member is obsolete + { +#if __ANDROID_28__ + return PackageInfoCompat.GetLongVersionCode(info).ToString(CultureInfo.InvariantCulture); +#else +#pragma warning disable CS0618 // Type or member is obsolete + return info.VersionCode.ToString(CultureInfo.InvariantCulture); +#pragma warning restore CS0618 // Type or member is obsolete +#endif + } + } + + static void PlatformShowSettingsUI() + { + var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext; + + var settingsIntent = new Intent(); + settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings); + settingsIntent.AddCategory(Intent.CategoryDefault); + settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName())); + + var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents; + + settingsIntent.SetFlags(flags); + + context.StartActivity(settingsIntent); + } + + static AppTheme PlatformRequestedTheme() + { + return (Platform.AppContext.Resources.Configuration.UiMode & UiMode.NightMask) switch + { + UiMode.NightYes => AppTheme.Dark, + UiMode.NightNo => AppTheme.Light, + _ => AppTheme.Unspecified + }; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs new file mode 100644 index 00000000..8314a483 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs @@ -0,0 +1,160 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Foundation; +#if __IOS__ || __TVOS__ +using UIKit; +#elif __MACOS__ +using AppKit; +#endif + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + static string PlatformGetPackageName() => GetBundleValue("CFBundleIdentifier"); + + static string PlatformGetName() => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName"); + + static string PlatformGetVersionString() => GetBundleValue("CFBundleShortVersionString"); + + static string PlatformGetBuild() => GetBundleValue("CFBundleVersion"); + + static string GetBundleValue(string key) + => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); + +// #if __IOS__ || __TVOS__ +// static async void PlatformShowSettingsUI() +// => await Launcher.OpenAsync(UIApplication.OpenSettingsUrlString); +// #elif __MACOS__ +// static void PlatformShowSettingsUI() +// { +// MainThread.BeginInvokeOnMainThread(() => +// { +// // Ignore obsolete for the time being. Will be updated in a future release. +// #pragma warning disable CS0618 // Type or member is obsolete +// var prefsApp = ScriptingBridge.SBApplication.FromBundleIdentifier("com.apple.systempreferences"); +// #pragma warning restore CS0618 // Type or member is obsolete +// prefsApp.SendMode = ScriptingBridge.AESendMode.NoReply; +// prefsApp.Activate(); +// }); +// } +// #else +// static void PlatformShowSettingsUI() => +// throw new FeatureNotSupportedException(); +// #endif + +// #if __IOS__ || __TVOS__ +// static AppTheme PlatformRequestedTheme() +// { +// if (!Platform.HasOSVersion(13, 0)) +// return AppTheme.Unspecified; +// +// var uiStyle = Platform.GetCurrentUIViewController()?.TraitCollection?.UserInterfaceStyle ?? +// UITraitCollection.CurrentTraitCollection.UserInterfaceStyle; +// +// return uiStyle switch +// { +// UIUserInterfaceStyle.Light => AppTheme.Light, +// UIUserInterfaceStyle.Dark => AppTheme.Dark, +// _ => AppTheme.Unspecified +// }; +// } +// #elif __MACOS__ +// static AppTheme PlatformRequestedTheme() +// { +// if (DeviceInfo.Version >= new Version(10, 14)) +// { +// var app = NSAppearance.CurrentAppearance?.FindBestMatch(new string[] +// { +// NSAppearance.NameAqua, +// NSAppearance.NameDarkAqua +// }); +// +// if (string.IsNullOrEmpty(app)) +// return AppTheme.Unspecified; +// +// if (app == NSAppearance.NameDarkAqua) +// return AppTheme.Dark; +// } +// return AppTheme.Light; +// } +// #else +// static AppTheme PlatformRequestedTheme() => +// AppTheme.Unspecified; +// #endif + + internal static bool VerifyHasUrlScheme(string scheme) + { + var cleansed = scheme.Replace("://", string.Empty); + var schemes = GetCFBundleURLSchemes().ToList(); + return schemes.Any(x => x != null && x.Equals(cleansed, StringComparison.InvariantCultureIgnoreCase)); + } + + internal static IEnumerable GetCFBundleURLSchemes() + { + var schemes = new List(); + + NSObject nsobj = null; + if (!NSBundle.MainBundle.InfoDictionary.TryGetValue((NSString)"CFBundleURLTypes", out nsobj)) + return schemes; + + var array = nsobj as NSArray; + + if (array == null) + return schemes; + + for (nuint i = 0; i < array.Count; i++) + { + var d = array.GetItem(i); + if (d == null || !d.Any()) + continue; + + if (!d.TryGetValue((NSString)"CFBundleURLSchemes", out nsobj)) + continue; + + var a = nsobj as NSArray; + var urls = ConvertToIEnumerable(a).Select(x => x.ToString()).ToArray(); + foreach (var url in urls) + schemes.Add(url); + } + + return schemes; + } + + static IEnumerable ConvertToIEnumerable(NSArray array) + where T : class, ObjCRuntime.INativeObject + { + for (nuint i = 0; i < array.Count; i++) + yield return array.GetItem(i); + } + } +} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs new file mode 100644 index 00000000..d126aedc --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -0,0 +1,49 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + public static string PackageName => PlatformGetPackageName(); + + public static string Name => PlatformGetName(); + + public static string VersionString => PlatformGetVersionString(); + + public static Version Version => Utils.ParseVersion(VersionString); + + public static string BuildString => PlatformGetBuild(); + + // public static void ShowSettingsUI() => PlatformShowSettingsUI(); + // + // public static AppTheme RequestedTheme => PlatformRequestedTheme(); + } +} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/PlatformAttributes.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/PlatformAttributes.shared.cs new file mode 100644 index 00000000..a4d215f7 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/PlatformAttributes.shared.cs @@ -0,0 +1,34 @@ +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal class ConcreteProp : IProp + { + private readonly T _value; + + public ConcreteProp(T value) + { + _value = value; + } + + public bool HasValue() + { + return true; + } + + public T GetValue() + { + return _value; + } + } + + internal static partial class PlatformAttributes + { + internal static Layer Layer => new Layer { ApplicationInfo = new ConcreteProp(new ApplicationInfo( + "", + AppInfo.Name, + AppInfo.Version.ToString(), + "" + ))}; + } +} \ No newline at end of file From 8fbe643055633c3f60d6e3bd2da4aea5ad4afe6d Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 27 Sep 2023 16:24:05 -0700 Subject: [PATCH 440/499] Trying to get compiling.. --- .../LaunchDarkly.ClientSdk.csproj | 6 ++- .../PlatformSpecific/AppInfo.android.cs | 51 ++++++++++--------- .../AppInfo.ios.tvos.watchos.macos.cs | 1 + .../PlatformSpecific/AppInfo.shared.cs | 4 -- .../Subsystems/LdClientContext.cs | 3 ++ .../PlatformAttributes.cs} | 0 6 files changed, 34 insertions(+), 31 deletions(-) rename src/LaunchDarkly.ClientSdk/{PlatformSpecific/PlatformAttributes.shared.cs => Subsystems/PlatformAttributes.cs} (100%) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 6d74536f..20509fe7 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -32,7 +32,7 @@ 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 - + @@ -43,7 +43,9 @@ - + + Code + diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index 42c3a645..3586a721 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -41,6 +41,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { + static string PlatformGetPackageName() => Platform.AppContext.PackageName; static string PlatformGetName() @@ -80,30 +81,30 @@ static string PlatformGetBuild() } } - static void PlatformShowSettingsUI() - { - var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext; - - var settingsIntent = new Intent(); - settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings); - settingsIntent.AddCategory(Intent.CategoryDefault); - settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName())); - - var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents; - - settingsIntent.SetFlags(flags); - - context.StartActivity(settingsIntent); - } - - static AppTheme PlatformRequestedTheme() - { - return (Platform.AppContext.Resources.Configuration.UiMode & UiMode.NightMask) switch - { - UiMode.NightYes => AppTheme.Dark, - UiMode.NightNo => AppTheme.Light, - _ => AppTheme.Unspecified - }; - } + // static void PlatformShowSettingsUI() + // { + // var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext; + // + // var settingsIntent = new Intent(); + // settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings); + // settingsIntent.AddCategory(Intent.CategoryDefault); + // settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName())); + // + // var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents; + // + // settingsIntent.SetFlags(flags); + // + // context.StartActivity(settingsIntent); + // } + + // static AppTheme PlatformRequestedTheme() + // { + // return (Platform.AppContext.Resources.Configuration.UiMode & UiMode.NightMask) switch + // { + // UiMode.NightYes => AppTheme.Dark, + // UiMode.NightNo => AppTheme.Light, + // _ => AppTheme.Unspecified + // }; + // } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs index 8314a483..8e5394bd 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs @@ -40,6 +40,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { + static string PlatformGetPackageName() => GetBundleValue("CFBundleIdentifier"); static string PlatformGetName() => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName"); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index d126aedc..28ccf49a 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -41,9 +41,5 @@ internal static partial class AppInfo public static Version Version => Utils.ParseVersion(VersionString); public static string BuildString => PlatformGetBuild(); - - // public static void ShowSettingsUI() => PlatformShowSettingsUI(); - // - // public static AppTheme RequestedTheme => PlatformRequestedTheme(); } } \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index df3f2598..79f0c0c7 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; @@ -170,6 +171,8 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } + builder.SetPlatformLayer(PlatformAttributes.Layer); + return builder.Build(); } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/PlatformAttributes.shared.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs similarity index 100% rename from src/LaunchDarkly.ClientSdk/PlatformSpecific/PlatformAttributes.shared.cs rename to src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs From 4aed5423019c075353d2cf3350900c0b9a0987e7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 28 Sep 2023 12:24:34 -0700 Subject: [PATCH 441/499] add Android.Support.V4 dependency and missing Xamarin.Essentials utils --- .../Internal/XamarinEsssentialsUtils.cs | 69 +++++++++++ .../LaunchDarkly.ClientSdk.csproj | 11 +- ...s.tvos.watchos.macos.cs => AppInfo.ios.cs} | 6 +- .../PlatformSpecific/AppInfo.netstandard.cs | 107 ++++++++++++++++++ .../PlatformSpecific/AppInfo.shared.cs | 2 + 5 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs rename src/LaunchDarkly.ClientSdk/PlatformSpecific/{AppInfo.ios.tvos.watchos.macos.cs => AppInfo.ios.cs} (99%) create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs diff --git a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs new file mode 100644 index 00000000..9e224f02 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs @@ -0,0 +1,69 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal class Utils + { + internal static Version ParseVersion(string version) + { + if (Version.TryParse(version, out var number)) + return number; + + if (int.TryParse(version, out var major)) + return new Version(major, 0); + + return new Version(0, 0); + } + + internal static CancellationToken TimeoutToken(CancellationToken cancellationToken, TimeSpan timeout) + { + // create a new linked cancellation token source + var cancelTokenSrc = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + // if a timeout was given, make the token source cancel after it expires + if (timeout > TimeSpan.Zero) + cancelTokenSrc.CancelAfter(timeout); + + // our Cancel method will handle the actual cancellation logic + return cancelTokenSrc.Token; + } + + internal static async Task WithTimeout(Task task, TimeSpan timeSpan) + { + var retTask = await Task.WhenAny(task, Task.Delay(timeSpan)) + .ConfigureAwait(false); + + return retTask is Task ? task.Result : default(T); + } + } +} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 20509fe7..5865a1cd 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -67,7 +67,7 @@ - + @@ -90,6 +90,15 @@ + + + + + + + + + ../../LaunchDarkly.ClientSdk.snk true diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs similarity index 99% rename from src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs rename to src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index 8e5394bd..08029dad 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.tvos.watchos.macos.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -42,11 +42,11 @@ internal static partial class AppInfo { static string PlatformGetPackageName() => GetBundleValue("CFBundleIdentifier"); - + static string PlatformGetName() => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName"); - + static string PlatformGetVersionString() => GetBundleValue("CFBundleShortVersionString"); - + static string PlatformGetBuild() => GetBundleValue("CFBundleVersion"); static string GetBundleValue(string key) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs new file mode 100644 index 00000000..1a9bad91 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -0,0 +1,107 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static class ExceptionUtils + { +#if NETSTANDARD1_0 || NETSTANDARD2_0 + internal static NotImplementedInReferenceAssemblyException NotSupportedOrImplementedException => + new NotImplementedInReferenceAssemblyException(); +#else + internal static FeatureNotSupportedException NotSupportedOrImplementedException => + new FeatureNotSupportedException($"This API is not supported on {DeviceInfo.Platform}"); +#endif + + } + + internal class NotImplementedInReferenceAssemblyException : NotImplementedException + { + public NotImplementedInReferenceAssemblyException() + : base( + "This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.") + { + } + } + + internal class PermissionException : UnauthorizedAccessException + { + public PermissionException(string message) + : base(message) + { + } + } + + internal class FeatureNotSupportedException : NotSupportedException + { + public FeatureNotSupportedException() + { + } + + public FeatureNotSupportedException(string message) + : base(message) + { + } + + public FeatureNotSupportedException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + internal class FeatureNotEnabledException : InvalidOperationException + { + public FeatureNotEnabledException() + { + } + + public FeatureNotEnabledException(string message) + : base(message) + { + } + + public FeatureNotEnabledException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + internal static partial class AppInfo + { + static string PlatformGetPackageName() => throw ExceptionUtils.NotSupportedOrImplementedException; + + static string PlatformGetName() => throw ExceptionUtils.NotSupportedOrImplementedException; + + static string PlatformGetVersionString() => throw ExceptionUtils.NotSupportedOrImplementedException; + + static string PlatformGetBuild() => throw ExceptionUtils.NotSupportedOrImplementedException; + + } +} \ No newline at end of file diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index 28ccf49a..ee811be8 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -27,9 +27,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ using System; +using LaunchDarkly.Sdk.Client.Internal; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { + internal static partial class AppInfo { public static string PackageName => PlatformGetPackageName(); From 81312f2eb481c2265cc6654abf29ee86b146ccb2 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 28 Sep 2023 12:49:16 -0700 Subject: [PATCH 442/499] refactor platform APIs to align with LD requirements --- .../Internal/XamarinEsssentialsUtils.cs | 4 ++-- .../PlatformSpecific/AppInfo.android.cs | 9 +++++++++ .../PlatformSpecific/AppInfo.ios.cs | 15 +++++++------- .../PlatformSpecific/AppInfo.netstandard.cs | 13 +++++++----- .../PlatformSpecific/AppInfo.shared.cs | 20 +++++++++---------- .../Subsystems/PlatformAttributes.cs | 13 ++++++------ 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs index 9e224f02..f858a44a 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs @@ -32,7 +32,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE namespace LaunchDarkly.Sdk.Client.Internal { - internal class Utils + internal static class Utils { internal static Version ParseVersion(string version) { @@ -66,4 +66,4 @@ internal static async Task WithTimeout(Task task, TimeSpan timeSpan) return retTask is Task ? task.Result : default(T); } } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index 3586a721..92d161c8 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -42,6 +42,15 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class AppInfo { + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + static string PlatformGetAppId() => Platform.AppContext.PackageName; + static string PlatformGetAppName() => PlatformGetName(); + static string PlatformGetAppVersion() => PlatformGetBuild(); + static string PlatformGetAppVersionName() => PlatformGetVersionString(); + + // End LaunchDarkly additions. + static string PlatformGetPackageName() => Platform.AppContext.PackageName; static string PlatformGetName() diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index 08029dad..7fb45035 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -41,13 +41,14 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class AppInfo { - static string PlatformGetPackageName() => GetBundleValue("CFBundleIdentifier"); + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + static string PlatformGetAppId() => GetBundleValue("CFBundleIdentifier"); + static string PlatformGetAppName() => GetBundleValue("CFBundleName"); + static string PlatformGetAppVersion() => GetBundleValue("CFBundleVersion"); + static string PlatformGetAppVersionName() => GetBundleValue("CFBundleShortString"); - static string PlatformGetName() => GetBundleValue("CFBundleDisplayName") ?? GetBundleValue("CFBundleName"); - - static string PlatformGetVersionString() => GetBundleValue("CFBundleShortVersionString"); - - static string PlatformGetBuild() => GetBundleValue("CFBundleVersion"); + // End LaunchDarkly additions. static string GetBundleValue(string key) => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); @@ -158,4 +159,4 @@ static IEnumerable ConvertToIEnumerable(NSArray array) yield return array.GetItem(i); } } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index 1a9bad91..b6a866a8 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -95,13 +95,16 @@ public FeatureNotEnabledException(string message, Exception innerException) internal static partial class AppInfo { - static string PlatformGetPackageName() => throw ExceptionUtils.NotSupportedOrImplementedException; + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + static string PlatformGetAppId() => throw ExceptionUtils.NotSupportedOrImplementedException; - static string PlatformGetName() => throw ExceptionUtils.NotSupportedOrImplementedException; + static string PlatformGetAppName() => throw ExceptionUtils.NotSupportedOrImplementedException; - static string PlatformGetVersionString() => throw ExceptionUtils.NotSupportedOrImplementedException; + static string PlatformGetAppVersion() => throw ExceptionUtils.NotSupportedOrImplementedException; - static string PlatformGetBuild() => throw ExceptionUtils.NotSupportedOrImplementedException; + static string PlatformGetAppVersionName() => throw ExceptionUtils.NotSupportedOrImplementedException; + // End LaunchDarkly additions. } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index ee811be8..a5d7096e 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -34,14 +34,14 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class AppInfo { - public static string PackageName => PlatformGetPackageName(); - - public static string Name => PlatformGetName(); - - public static string VersionString => PlatformGetVersionString(); - - public static Version Version => Utils.ParseVersion(VersionString); - - public static string BuildString => PlatformGetBuild(); + + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + internal static string Id => PlatformGetAppId(); + internal static string Name => PlatformGetAppName(); + internal static string Version => PlatformGetAppVersion(); + internal static string VersionName => PlatformGetAppVersionName(); + + // End LaunchDarkly additions. } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index a4d215f7..5c98ddb7 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -1,6 +1,7 @@ +using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.EnvReporting; -namespace LaunchDarkly.Sdk.Client.PlatformSpecific +namespace LaunchDarkly.Sdk.Client.Subsystems { internal class ConcreteProp : IProp { @@ -22,13 +23,13 @@ public T GetValue() } } - internal static partial class PlatformAttributes + internal static class PlatformAttributes { internal static Layer Layer => new Layer { ApplicationInfo = new ConcreteProp(new ApplicationInfo( - "", + AppInfo.Id, AppInfo.Name, - AppInfo.Version.ToString(), - "" + AppInfo.Version, + AppInfo.VersionName ))}; } -} \ No newline at end of file +} From ebdeee44f953a37a2f1e0e4229213a6267f2335f Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 28 Sep 2023 13:55:14 -0700 Subject: [PATCH 443/499] make it cleaner --- .../PlatformSpecific/AppInfo.android.cs | 2 + .../PlatformSpecific/AppInfo.ios.cs | 2 + .../PlatformSpecific/AppInfo.netstandard.cs | 105 +----------------- .../PlatformSpecific/AppInfo.shared.cs | 39 +------ .../Subsystems/PlatformAttributes.cs | 20 +--- 5 files changed, 12 insertions(+), 156 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index 92d161c8..970bf6d2 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -41,6 +41,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { + static ApplicationInfo? PlatformGet() => new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), + PlatformGetAppVersion(), PlatformGetAppVersionName()); // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index 7fb45035..a8739390 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -40,6 +40,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { + static ApplicationInfo? PlatformGet() => new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), + PlatformGetAppVersion(), PlatformGetAppVersionName()); // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index b6a866a8..36950ff2 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -1,110 +1,7 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -using System; - namespace LaunchDarkly.Sdk.Client.PlatformSpecific { - internal static class ExceptionUtils - { -#if NETSTANDARD1_0 || NETSTANDARD2_0 - internal static NotImplementedInReferenceAssemblyException NotSupportedOrImplementedException => - new NotImplementedInReferenceAssemblyException(); -#else - internal static FeatureNotSupportedException NotSupportedOrImplementedException => - new FeatureNotSupportedException($"This API is not supported on {DeviceInfo.Platform}"); -#endif - - } - - internal class NotImplementedInReferenceAssemblyException : NotImplementedException - { - public NotImplementedInReferenceAssemblyException() - : base( - "This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.") - { - } - } - - internal class PermissionException : UnauthorizedAccessException - { - public PermissionException(string message) - : base(message) - { - } - } - - internal class FeatureNotSupportedException : NotSupportedException - { - public FeatureNotSupportedException() - { - } - - public FeatureNotSupportedException(string message) - : base(message) - { - } - - public FeatureNotSupportedException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - internal class FeatureNotEnabledException : InvalidOperationException - { - public FeatureNotEnabledException() - { - } - - public FeatureNotEnabledException(string message) - : base(message) - { - } - - public FeatureNotEnabledException(string message, Exception innerException) - : base(message, innerException) - { - } - } - internal static partial class AppInfo { - // The following methods are added by LaunchDarkly to align with the Application Info - // required by the SDK. - static string PlatformGetAppId() => throw ExceptionUtils.NotSupportedOrImplementedException; - - static string PlatformGetAppName() => throw ExceptionUtils.NotSupportedOrImplementedException; - - static string PlatformGetAppVersion() => throw ExceptionUtils.NotSupportedOrImplementedException; - - static string PlatformGetAppVersionName() => throw ExceptionUtils.NotSupportedOrImplementedException; - // End LaunchDarkly additions. - + private static ApplicationInfo? PlatformGet() => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index a5d7096e..ec74c280 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -1,47 +1,10 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - using System; using LaunchDarkly.Sdk.Client.Internal; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { - internal static partial class AppInfo { - - // The following methods are added by LaunchDarkly to align with the Application Info - // required by the SDK. - internal static string Id => PlatformGetAppId(); - internal static string Name => PlatformGetAppName(); - internal static string Version => PlatformGetAppVersion(); - internal static string VersionName => PlatformGetAppVersionName(); - - // End LaunchDarkly additions. + internal static ApplicationInfo? Get() => PlatformGet(); } } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index 5c98ddb7..a881e46e 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -3,33 +3,25 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { - internal class ConcreteProp : IProp + internal class MaybeAppInfo: IProp { - private readonly T _value; + private readonly ApplicationInfo? _value; - public ConcreteProp(T value) + public MaybeAppInfo(ApplicationInfo? value) { _value = value; } public bool HasValue() { - return true; + return _value.HasValue; } - public T GetValue() - { - return _value; - } + public ApplicationInfo GetValue() => _value.Value; } internal static class PlatformAttributes { - internal static Layer Layer => new Layer { ApplicationInfo = new ConcreteProp(new ApplicationInfo( - AppInfo.Id, - AppInfo.Name, - AppInfo.Version, - AppInfo.VersionName - ))}; + internal static Layer Layer => new Layer { ApplicationInfo = new MaybeAppInfo(AppInfo.Get()) }; } } From 2395f20ebada923efa129980a690691a75249cc2 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 29 Sep 2023 08:40:42 -0700 Subject: [PATCH 444/499] refactor: use Prop machinery from common library --- .../PlatformSpecific/AppInfo.android.cs | 6 ++++-- .../PlatformSpecific/AppInfo.ios.cs | 6 ++++-- .../PlatformSpecific/AppInfo.netstandard.cs | 4 +++- .../PlatformSpecific/AppInfo.shared.cs | 3 ++- .../Subsystems/PlatformAttributes.cs | 19 +------------------ 5 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index 970bf6d2..2730d167 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -31,6 +31,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using Android.Content.PM; using Android.Content.Res; using Android.Provider; +using LaunchDarkly.Sdk.EnvReporting; #if __ANDROID_29__ using AndroidX.Core.Content.PM; #else @@ -41,8 +42,9 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static ApplicationInfo? PlatformGet() => new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), - PlatformGetAppVersion(), PlatformGetAppVersionName()); + static IProp PlatformGetApplicationInfo() => new Props.Concrete(new ApplicationInfo( + PlatformGetAppId(), PlatformGetAppName(), + PlatformGetAppVersion(), PlatformGetAppVersionName())); // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index a8739390..a81a4b10 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -30,6 +30,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Collections.Generic; using System.Linq; using Foundation; +using LaunchDarkly.Sdk.EnvReporting; #if __IOS__ || __TVOS__ using UIKit; #elif __MACOS__ @@ -40,8 +41,9 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static ApplicationInfo? PlatformGet() => new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), - PlatformGetAppVersion(), PlatformGetAppVersionName()); + static IProp PlatformGetApplicationInfo() => new Props.Concrete( + new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), + PlatformGetAppVersion(), PlatformGetAppVersionName())); // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index 36950ff2..1393de8b 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -1,7 +1,9 @@ +using LaunchDarkly.Sdk.EnvReporting; + namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - private static ApplicationInfo? PlatformGet() => null; + private static IProp PlatformGetApplicationInfo() => new Props.Fallthrough(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index ec74c280..4e3048e6 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -1,10 +1,11 @@ using System; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - internal static ApplicationInfo? Get() => PlatformGet(); + internal static IProp GetProperty() => PlatformGetApplicationInfo(); } } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index a881e46e..fce26257 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -3,25 +3,8 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { - internal class MaybeAppInfo: IProp - { - private readonly ApplicationInfo? _value; - - public MaybeAppInfo(ApplicationInfo? value) - { - _value = value; - } - - public bool HasValue() - { - return _value.HasValue; - } - - public ApplicationInfo GetValue() => _value.Value; - } - internal static class PlatformAttributes { - internal static Layer Layer => new Layer { ApplicationInfo = new MaybeAppInfo(AppInfo.Get()) }; + internal static Layer Layer => new Layer { ApplicationInfo = AppInfo.GetProperty()}; } } From 5f1ee9db4624306b61bf3585ddc032e8e3d47c9e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 29 Sep 2023 11:28:00 -0700 Subject: [PATCH 445/499] fix build --- .../LaunchDarkly.ClientSdk.csproj | 2 + .../PlatformSpecific/AppInfo.shared.cs | 2 +- .../PlatformSpecific/DeviceInfo.android.cs | 158 ++++++++++++++++++ .../PlatformSpecific/DeviceInfo.ios.cs | 105 ++++++++++++ .../DeviceInfo.netstandard.cs | 13 ++ .../PlatformSpecific/DeviceInfo.shared.cs | 14 ++ .../PlatformSpecific/DevicePlatform.shared.cs | 60 +++++++ .../PlatformSpecific/Platform.android.cs | 2 +- .../PlatformSpecific/Platform.ios.cs | 62 +++++++ .../Subsystems/PlatformAttributes.cs | 7 +- 10 files changed, 422 insertions(+), 3 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 5865a1cd..2feeebbd 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -68,6 +68,8 @@ + + diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index 4e3048e6..2ff57a7c 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -6,6 +6,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - internal static IProp GetProperty() => PlatformGetApplicationInfo(); + internal static IProp GetAppInfo() => PlatformGetApplicationInfo(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs new file mode 100644 index 00000000..31ce5029 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -0,0 +1,158 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using Android.OS; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + private static IProp PlatformGetOsInfo() => + new Props.Concrete(new OsInfo(GetPlatform().ToString(), GetPlatform().ToString(), GetVersionString())); + + private static IProp PlatformGetDeviceInfo() => + new Props.Concrete(new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), GetModel())); + + + //const int tabletCrossover = 600; + + static string GetModel() => Build.Model; + + static string GetManufacturer() => Build.Manufacturer; + + // private static string GetDeviceName() + // { + // // DEVICE_NAME added in System.Global in API level 25 + // // https://developer.android.com/reference/android/provider/Settings.Global#DEVICE_NAME + // var name = GetSystemSetting("device_name", true); + // if (string.IsNullOrWhiteSpace(name)) + // name = ColorSpace.Model; + // return name; + // } + + private static string GetVersionString() => Build.VERSION.Release; + + private static DevicePlatform GetPlatform() => DevicePlatform.Android; + + // static DeviceIdiom GetIdiom() + // { + // var currentIdiom = DeviceIdiom.Unknown; + // + // // first try UIModeManager + // using var uiModeManager = UiModeManager.FromContext(Essentials.Platform.AppContext); + // + // try + // { + // var uiMode = uiModeManager?.CurrentModeType ?? UiMode.TypeUndefined; + // currentIdiom = DetectIdiom(uiMode); + // } + // catch (Exception ex) + // { + // System.Diagnostics.Debug.WriteLine($"Unable to detect using UiModeManager: {ex.Message}"); + // } + // + // // then try Configuration + // if (currentIdiom == DeviceIdiom.Unknown) + // { + // var configuration = Essentials.Platform.AppContext.Resources?.Configuration; + // if (configuration != null) + // { + // var minWidth = configuration.SmallestScreenWidthDp; + // var isWide = minWidth >= tabletCrossover; + // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; + // } + // else + // { + // // start clutching at straws + // using var metrics = Essentials.Platform.AppContext.Resources?.DisplayMetrics; + // if (metrics != null) + // { + // var minSize = Math.Min(metrics.WidthPixels, metrics.HeightPixels); + // var isWide = minSize * metrics.Density >= tabletCrossover; + // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; + // } + // } + // } + // + // // hope we got it somewhere + // return currentIdiom; + // } + + // static DeviceIdiom DetectIdiom(UiMode uiMode) + // { + // if (uiMode == UiMode.TypeNormal) + // return DeviceIdiom.Unknown; + // else if (uiMode == UiMode.TypeTelevision) + // return DeviceIdiom.TV; + // else if (uiMode == UiMode.TypeDesk) + // return DeviceIdiom.Desktop; + // else if (Essentials.Platform.HasApiLevel(BuildVersionCodes.KitkatWatch) && uiMode == UiMode.TypeWatch) + // return DeviceIdiom.Watch; + // + // return DeviceIdiom.Unknown; + // } + + // static DeviceType GetDeviceType() + // { + // var isEmulator = + // (Build.Brand.StartsWith("generic", StringComparison.InvariantCulture) && Build.Device.StartsWith("generic", StringComparison.InvariantCulture)) || + // Build.Fingerprint.StartsWith("generic", StringComparison.InvariantCulture) || + // Build.Fingerprint.StartsWith("unknown", StringComparison.InvariantCulture) || + // Build.Hardware.Contains("goldfish") || + // Build.Hardware.Contains("ranchu") || + // Build.Model.Contains("google_sdk") || + // Build.Model.Contains("Emulator") || + // Build.Model.Contains("Android SDK built for x86") || + // Build.Manufacturer.Contains("Genymotion") || + // Build.Manufacturer.Contains("VS Emulator") || + // Build.Product.Contains("emulator") || + // Build.Product.Contains("google_sdk") || + // Build.Product.Contains("sdk") || + // Build.Product.Contains("sdk_google") || + // Build.Product.Contains("sdk_x86") || + // Build.Product.Contains("simulator") || + // Build.Product.Contains("vbox86p"); + // + // if (isEmulator) + // return DeviceType.Virtual; + // + // return DeviceType.Physical; + // } + + // private static string GetSystemSetting(string name, bool isGlobal = false) + // { + // if (isGlobal && Platform.HasApiLevel(BuildVersionCodes.NMr1)) + // return Settings.Global.GetString(Platform.AppContext.ContentResolver, name); + // else + // return Settings.System.GetString(Platform.AppContext.ContentResolver, name); + // } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs new file mode 100644 index 00000000..e715695a --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -0,0 +1,105 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Diagnostics; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; +#if __WATCHOS__ +using WatchKit; +using UIDevice = WatchKit.WKInterfaceDevice; +#else +using UIKit; +#endif + +using ObjCRuntime; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + + private static IProp PlatformGetOsInfo() => + new Props.Concrete(new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString())); + + private static IProp PlatformGetDeviceInfo() => + new Props.Concrete(new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), GetModel())); + static string GetModel() + { + try + { + return Platform.GetSystemLibraryProperty("hw.machine"); + } + catch (Exception) + { + Debug.WriteLine("Unable to query hardware model, returning current device model."); + } + return UIDevice.CurrentDevice.Model; + } + + static string GetManufacturer() => "Apple"; + + static string GetDeviceName() => UIDevice.CurrentDevice.Name; + + static string GetVersionString() => UIDevice.CurrentDevice.SystemVersion; + + static DevicePlatform GetPlatform() => +#if __IOS__ + DevicePlatform.iOS; +#elif __TVOS__ + DevicePlatform.tvOS; +#elif __WATCHOS__ + DevicePlatform.watchOS; +#endif + +// static DeviceIdiom GetIdiom() +// { +// #if __WATCHOS__ +// return DeviceIdiom.Watch; +// #else +// switch (UIDevice.CurrentDevice.UserInterfaceIdiom) +// { +// case UIUserInterfaceIdiom.Pad: +// return DeviceIdiom.Tablet; +// case UIUserInterfaceIdiom.Phone: +// return DeviceIdiom.Phone; +// case UIUserInterfaceIdiom.TV: +// return DeviceIdiom.TV; +// case UIUserInterfaceIdiom.CarPlay: +// case UIUserInterfaceIdiom.Unspecified: +// default: +// return DeviceIdiom.Unknown; +// } +// #endif +// } + + // static DeviceType GetDeviceType() + // => Runtime.Arch == Arch.DEVICE ? DeviceType.Physical : DeviceType.Virtual; + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs new file mode 100644 index 00000000..66a31fa4 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -0,0 +1,13 @@ +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + private static IProp PlatformGetOsInfo() => new Props.Fallthrough(); + + private static IProp PlatformGetDeviceInfo() => + new Props.Fallthrough(); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs new file mode 100644 index 00000000..8da48212 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs @@ -0,0 +1,14 @@ +using System; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + internal static IProp GetOsInfo() => PlatformGetOsInfo(); + + internal static IProp GetDeviceInfo() => PlatformGetDeviceInfo(); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs new file mode 100644 index 00000000..dc745bb6 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs @@ -0,0 +1,60 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal readonly struct DevicePlatform : IEquatable + { + readonly string devicePlatform; + + public static DevicePlatform Android { get; } = new DevicePlatform(nameof(Android)); + + public static DevicePlatform iOS { get; } = new DevicePlatform(nameof(iOS)); + + public static DevicePlatform macOS { get; } = new DevicePlatform(nameof(macOS)); + + public static DevicePlatform tvOS { get; } = new DevicePlatform(nameof(tvOS)); + + public static DevicePlatform Tizen { get; } = new DevicePlatform(nameof(Tizen)); + + public static DevicePlatform UWP { get; } = new DevicePlatform(nameof(UWP)); + + public static DevicePlatform watchOS { get; } = new DevicePlatform(nameof(watchOS)); + + public static DevicePlatform Unknown { get; } = new DevicePlatform(nameof(Unknown)); + + DevicePlatform(string devicePlatform) + { + if (devicePlatform == null) + throw new ArgumentNullException(nameof(devicePlatform)); + + if (devicePlatform.Length == 0) + throw new ArgumentException(nameof(devicePlatform)); + + this.devicePlatform = devicePlatform; + } + + public static DevicePlatform Create(string devicePlatform) => + new DevicePlatform(devicePlatform); + + public bool Equals(DevicePlatform other) => + Equals(other.devicePlatform); + + internal bool Equals(string other) => + string.Equals(devicePlatform, other, StringComparison.Ordinal); + + public override bool Equals(object obj) => + obj is DevicePlatform && Equals((DevicePlatform)obj); + + public override int GetHashCode() => + devicePlatform == null ? 0 : devicePlatform.GetHashCode(); + + public override string ToString() => + devicePlatform ?? string.Empty; + + public static bool operator ==(DevicePlatform left, DevicePlatform right) => + left.Equals(right); + + public static bool operator !=(DevicePlatform left, DevicePlatform right) => + !left.Equals(right); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs index 7972425f..fad87cbe 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs @@ -87,7 +87,7 @@ internal static partial class Platform internal static bool HasApiLevel(BuildVersionCodes versionCode) => (int)Build.VERSION.SdkInt >= (int)versionCode; - + //internal static CameraManager CameraManager => // AppContext.GetSystemService(Context.CameraService) as CameraManager; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs new file mode 100644 index 00000000..df8f1b40 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs @@ -0,0 +1,62 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Runtime.InteropServices; +using ObjCRuntime; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Platform + { +#if __IOS__ + [DllImport(Constants.SystemLibrary, EntryPoint = "sysctlbyname")] +#else + [DllImport(Constants.libSystemLibrary, EntryPoint = "sysctlbyname")] +#endif + private static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); + + internal static string GetSystemLibraryProperty(string property) + { + var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); + SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); + + var propertyLength = Marshal.ReadInt32(lengthPtr); + + if (propertyLength == 0) + { + Marshal.FreeHGlobal(lengthPtr); + throw new InvalidOperationException("Unable to read length of property."); + } + + var valuePtr = Marshal.AllocHGlobal(propertyLength); + SysctlByName(property, valuePtr, lengthPtr, IntPtr.Zero, 0); + + var returnValue = Marshal.PtrToStringAnsi(valuePtr); + + Marshal.FreeHGlobal(lengthPtr); + Marshal.FreeHGlobal(valuePtr); + + return returnValue; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index fce26257..f397a8b8 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -5,6 +5,11 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { internal static class PlatformAttributes { - internal static Layer Layer => new Layer { ApplicationInfo = AppInfo.GetProperty()}; + internal static Layer Layer => new Layer + { + ApplicationInfo = AppInfo.GetAppInfo(), + OsInfo = DeviceInfo.GetOsInfo(), + DeviceInfo = DeviceInfo.GetDeviceInfo() + }; } } From c416f3478679322a04ee5a66d94c9ace15eafe33 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 2 Oct 2023 11:21:10 -0500 Subject: [PATCH 446/499] feat: Adds support for application tags. --- .gitignore | 1 + contract-tests/Representations.cs | 9 ++ contract-tests/SdkClientEntity.cs | 12 ++- contract-tests/TestService.cs | 3 +- src/LaunchDarkly.ClientSdk/Components.cs | 21 ++++ src/LaunchDarkly.ClientSdk/Configuration.cs | 7 +- .../ConfigurationBuilder.cs | 15 +++ .../Integrations/HttpConfigurationBuilder.cs | 18 ++-- .../LaunchDarkly.ClientSdk.csproj | 4 +- .../Subsystems/LdClientContext.cs | 97 ++++++++++--------- .../HttpConfigurationBuilderTest.cs | 22 +++-- 11 files changed, 139 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 25fdc901..5612b45b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ Thumbs.db # resharper *_Resharper.* *.Resharper +.idea # dotCover *.dotCover diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index d8cc4d7a..56199496 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -31,6 +31,7 @@ public class SdkConfigParams public SdkConfigEventParams Events { get; set; } public SdkConfigServiceEndpointsParams ServiceEndpoints { get; set; } public SdkClientSideParams ClientSide { get; set; } + public SdkConfigTagsParams Tags { get; set; } } public class SdkConfigStreamParams @@ -62,6 +63,14 @@ public class SdkConfigServiceEndpointsParams public Uri Events { get; set; } } + public class SdkConfigTagsParams + { + public string ApplicationId { get; set; } + public string ApplicationName { get; set; } + public string ApplicationVersion { get; set; } + public string ApplicationVersionName { get; set; } + } + public class SdkClientSideParams { public bool? EvaluationReasons { get; set; } diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index b1eb060e..81317bef 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -52,7 +52,7 @@ string tag throw new Exception("Client initialization failed"); } } - + public void Close() { _client.Dispose(); @@ -288,6 +288,16 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt } } + if (sdkParams.Tags != null) + { + var applicationInfo = Components.ApplicationInfo(); + applicationInfo.ApplicationId(sdkParams.Tags.ApplicationId); + applicationInfo.ApplicationName(sdkParams.Tags.ApplicationName); + applicationInfo.ApplicationVersion(sdkParams.Tags.ApplicationVersion); + applicationInfo.ApplicationVersionName(sdkParams.Tags.ApplicationVersionName); + builder.ApplicationInfo(applicationInfo); + } + var streamingParams = sdkParams.Streaming; var pollingParams = sdkParams.Polling; if (streamingParams != null) diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs index 2cbc5fb3..c485fc8b 100644 --- a/contract-tests/TestService.cs +++ b/contract-tests/TestService.cs @@ -36,7 +36,8 @@ public class Webapp "service-endpoints", "singleton", "strongly-typed", - "user-type" + "user-type", + "tags" }; public readonly Handler Handler; diff --git a/src/LaunchDarkly.ClientSdk/Components.cs b/src/LaunchDarkly.ClientSdk/Components.cs index c20ab0b5..3a6c02ab 100644 --- a/src/LaunchDarkly.ClientSdk/Components.cs +++ b/src/LaunchDarkly.ClientSdk/Components.cs @@ -281,6 +281,27 @@ public static PollingDataSourceBuilder PollingDataSource() => /// a configuration builder /// public static ServiceEndpointsBuilder ServiceEndpoints() => new ServiceEndpointsBuilder(); + + /// + /// Returns a configurable builder for the SDK's application metadata. + /// + /// + /// + /// Passing this to after setting any desired properties on the builder, + /// applies this configuration to the SDK. + /// + /// + /// + /// + /// var config = Configuration.Builder(mobileKey) + /// .ApplicationInfo( + /// Components.ApplicationInfo().ApplicationID("MyApplication").ApplicationVersion("version123abc") + /// ) + /// .Build(); + /// + /// + /// a configuration builder + public static ApplicationInfoBuilder ApplicationInfo() => new ApplicationInfoBuilder(); /// /// Returns a configurable factory for using streaming mode to get feature flag data. diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 1f4b2999..319cf400 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -118,6 +118,11 @@ public sealed class Configuration /// public ServiceEndpoints ServiceEndpoints { get; } + /// + /// ApplicationInfo configuration which contains info about the application the SDK is running in. + /// + public ApplicationInfoBuilder ApplicationInfo { get; } + /// /// Creates a configuration with all parameters set to the default. /// @@ -180,7 +185,7 @@ internal Configuration(ConfigurationBuilder builder) Offline = builder._offline; PersistenceConfigurationBuilder = builder._persistenceConfigurationBuilder; ServiceEndpoints = (builder._serviceEndpointsBuilder ?? Components.ServiceEndpoints()).Build(); - + ApplicationInfo = builder._applicationInfo; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 3ef521af..d7d05a45 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -30,6 +30,7 @@ public sealed class ConfigurationBuilder // will replace it with a platform-specific implementation. internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); + internal ApplicationInfoBuilder _applicationInfo; internal IComponentConfigurer _dataSource = null; internal bool _diagnosticOptOut = false; internal bool _enableBackgroundUpdating = true; @@ -65,6 +66,7 @@ internal ConfigurationBuilder(Configuration copyFrom) _offline = copyFrom.Offline; _persistenceConfigurationBuilder = copyFrom.PersistenceConfigurationBuilder; _serviceEndpointsBuilder = new ServiceEndpointsBuilder(copyFrom.ServiceEndpoints); + _applicationInfo = copyFrom.ApplicationInfo; } /// @@ -76,6 +78,19 @@ public Configuration Build() { return new Configuration(this); } + + /// + /// Sets the SDK's application metadata, which may be used in the LaunchDarkly analytics or other product + /// features. This object is normally a configuration builder obtained from , + /// which has methods for setting individual metadata properties. + /// + /// builder for + /// the same builder + public ConfigurationBuilder ApplicationInfo(ApplicationInfoBuilder applicationInfo) + { + _applicationInfo = applicationInfo; + return this; + } /// /// Sets the implementation of the component that receives feature flag data from LaunchDarkly, diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 8006f9d4..deb78c70 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -233,11 +233,12 @@ public HttpConfigurationBuilder Wrapper(string wrapperName, string wrapperVersio /// Called internally by the SDK to create an implementation instance. Applications do not need /// to call this method. /// - /// provides SDK configuration data + /// Key for authenticating with LD service + /// Application Info for this application environment /// an - public HttpConfiguration CreateHttpConfiguration(LdClientContext context) => + public HttpConfiguration CreateHttpConfiguration(string authKey, ApplicationInfo applicationInfo) => new HttpConfiguration( - MakeHttpProperties(context), + MakeHttpProperties(authKey, applicationInfo), _messageHandler, _responseStartTimeout, _useReport @@ -246,14 +247,14 @@ public HttpConfiguration CreateHttpConfiguration(LdClientContext context) => /// public LdValue DescribeConfiguration(LdClientContext context) => LdValue.BuildObject() - .WithHttpProperties(MakeHttpProperties(context)) + .WithHttpProperties(MakeHttpProperties(context.MobileKey, context.EnvironmentReporter.ApplicationInfo)) .Add("useReport", _useReport) .Set("socketTimeoutMillis", _responseStartTimeout.TotalMilliseconds) - // WithHttpProperties normally sets socketTimeoutMillis to the ReadTimeout value, - // which is more correct, but we can't really set ReadTimeout in this SDK + // WithHttpProperties normally sets socketTimeoutMillis to the ReadTimeout value, + // which is more correct, but we can't really set ReadTimeout in this SDK .Build(); - private HttpProperties MakeHttpProperties(LdClientContext context) + private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo applicationInfo) { Func handlerFn; if (_messageHandler is null) @@ -266,11 +267,12 @@ private HttpProperties MakeHttpProperties(LdClientContext context) } var httpProperties = HttpProperties.Default - .WithAuthorizationKey(context.MobileKey) + .WithAuthorizationKey(authToken) .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) + .WithApplicationTags(applicationInfo) .WithWrapper(_wrapperName, _wrapperVersion); foreach (var kv in _customHeaders) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index e9efb5be..70dd46ad 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index f4d504ab..532397e7 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -1,6 +1,8 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.Client.PlatformSpecific; +using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Events; @@ -69,6 +71,11 @@ public sealed class LdClientContext /// public ServiceEndpoints ServiceEndpoints { get; } + /// + /// The environment reporter. + /// + internal IEnvironmentReporter EnvironmentReporter { get; } + internal IDiagnosticDisabler DiagnosticDisabler { get; } internal IDiagnosticStore DiagnosticStore { get; } @@ -85,6 +92,28 @@ public LdClientContext( Context currentContext ) : this(configuration, currentContext, null) { } + internal LdClientContext(Configuration configuration, Context currentContext, object eventSender) : this( + configuration.MobileKey, + MakeLogger(configuration), + currentContext, + null, + configuration.EnableBackgroundUpdating, + configuration.EvaluationReasons, + (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) + .CreateHttpConfiguration(configuration.MobileKey, + MakeEnvironmentReporter(configuration.ApplicationInfo).ApplicationInfo), + false, + configuration.ServiceEndpoints, + MakeEnvironmentReporter(configuration.ApplicationInfo), + null, + null, + new TaskExecutor( + eventSender, + AsyncScheduler.ScheduleAction, + MakeLogger(configuration) + ) + ) { } + internal LdClientContext( string mobileKey, Logger baseLogger, @@ -95,6 +124,7 @@ internal LdClientContext( HttpConfiguration http, bool inBackground, ServiceEndpoints serviceEndpoints, + IEnvironmentReporter environmentReporter, IDiagnosticDisabler diagnosticDisabler, IDiagnosticStore diagnosticStore, TaskExecutor taskExecutor @@ -109,39 +139,15 @@ TaskExecutor taskExecutor Http = http; InBackground = inBackground; ServiceEndpoints = serviceEndpoints ?? Components.ServiceEndpoints().Build(); + EnvironmentReporter = environmentReporter; DiagnosticDisabler = diagnosticDisabler; DiagnosticStore = diagnosticStore; TaskExecutor = taskExecutor ?? new TaskExecutor(null, - PlatformSpecific.AsyncScheduler.ScheduleAction, + AsyncScheduler.ScheduleAction, baseLogger ); } - internal LdClientContext( - Configuration configuration, - Context currentContext, - object eventSender - ) : - this( - configuration.MobileKey, - MakeLogger(configuration), - currentContext, - null, - configuration.EnableBackgroundUpdating, - configuration.EvaluationReasons, - (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) - .CreateHttpConfiguration(MakeMinimalContext(configuration.MobileKey)), - false, - configuration.ServiceEndpoints, - null, - null, - new TaskExecutor( - eventSender, - PlatformSpecific.AsyncScheduler.ScheduleAction, - MakeLogger(configuration) - ) - ) { } - internal static Logger MakeLogger(Configuration configuration) { var logConfig = (configuration.LoggingConfigurationBuilder ?? Components.Logging()) @@ -150,28 +156,22 @@ internal static Logger MakeLogger(Configuration configuration) return logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); } - internal static LdClientContext MakeMinimalContext( - string mobileKey - ) => - new LdClientContext( - mobileKey, - Logs.None.Logger(""), - new Context(), - null, - false, - false, - HttpConfiguration.Default(), - false, - null, - null, - null, - null - ); + internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuilder applicationInfoBuilder) + { + var builder = new EnvironmentReporterBuilder(); + if (applicationInfoBuilder != null) + { + var applicationInfo = applicationInfoBuilder.Build(); + builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); + } + + return builder.Build(); + } internal LdClientContext WithContextAndBackgroundState( Context newCurrentContext, bool newInBackground - ) => + ) => new LdClientContext( MobileKey, BaseLogger, @@ -182,13 +182,14 @@ bool newInBackground Http, newInBackground, ServiceEndpoints, + EnvironmentReporter, DiagnosticDisabler, DiagnosticStore, TaskExecutor); internal LdClientContext WithDataSourceUpdateSink( IDataSourceUpdateSink newDataSourceUpdateSink - ) => + ) => new LdClientContext( MobileKey, BaseLogger, @@ -199,6 +200,7 @@ IDataSourceUpdateSink newDataSourceUpdateSink Http, InBackground, ServiceEndpoints, + EnvironmentReporter, DiagnosticDisabler, DiagnosticStore, TaskExecutor); @@ -206,7 +208,7 @@ IDataSourceUpdateSink newDataSourceUpdateSink internal LdClientContext WithDiagnostics( IDiagnosticDisabler newDiagnosticDisabler, IDiagnosticStore newDiagnosticStore - ) => + ) => new LdClientContext( MobileKey, BaseLogger, @@ -217,8 +219,9 @@ IDiagnosticStore newDiagnosticStore Http, InBackground, ServiceEndpoints, + EnvironmentReporter, newDiagnosticDisabler, newDiagnosticStore, TaskExecutor); } -} +} \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index e4299ac7..bdf512f2 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -4,6 +4,7 @@ using System.Net.Http; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Client.Subsystems; +using LaunchDarkly.Logging; using LaunchDarkly.TestHelpers; using Xunit; @@ -11,11 +12,12 @@ namespace LaunchDarkly.Sdk.Client.Integrations { public class HttpConfigurationBuilderTest { - private static readonly LdClientContext basicContext = LdClientContext.MakeMinimalContext("mobile-key"); + private static string mobileKey = "mobile-key"; + private static ApplicationInfo applicationInfo => new ApplicationInfo("mockId", "mockName", "blah", "blah"); private readonly BuilderBehavior.BuildTester _tester = BuilderBehavior.For(() => Components.HttpConfiguration(), - b => b.CreateHttpConfiguration(basicContext)); + b => b.CreateHttpConfiguration(mobileKey, applicationInfo)); [Fact] public void ConnectTimeout() @@ -31,7 +33,7 @@ public void CustomHeaders() var config = Components.HttpConfiguration() .CustomHeader("header1", "value1") .CustomHeader("header2", "value2") - .CreateHttpConfiguration(basicContext); + .CreateHttpConfiguration(mobileKey, applicationInfo); Assert.Equal("value1", HeadersAsMap(config.DefaultHeaders)["header1"]); Assert.Equal("value2", HeadersAsMap(config.DefaultHeaders)["header2"]); } @@ -47,8 +49,8 @@ public void MessageHandler() [Fact] public void MobileKeyHeader() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); - Assert.Equal(basicContext.MobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); + var config = Components.HttpConfiguration().CreateHttpConfiguration(mobileKey, applicationInfo); + Assert.Equal(mobileKey, HeadersAsMap(config.DefaultHeaders)["authorization"]); } [Fact] @@ -60,7 +62,7 @@ public void ResponseStartTimeout() prop.AssertCanSet(value); var config = Components.HttpConfiguration().ResponseStartTimeout(value) - .CreateHttpConfiguration(basicContext); + .CreateHttpConfiguration(mobileKey, applicationInfo); using (var client = config.NewHttpClient()) { Assert.Equal(value, client.Timeout); @@ -78,7 +80,7 @@ public void UseReport() [Fact] public void UserAgentHeader() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); + var config = Components.HttpConfiguration().CreateHttpConfiguration(mobileKey, applicationInfo); Assert.Equal("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)), HeadersAsMap(config.DefaultHeaders)["user-agent"]); // not configurable } @@ -86,7 +88,7 @@ public void UserAgentHeader() [Fact] public void WrapperDefaultNone() { - var config = Components.HttpConfiguration().CreateHttpConfiguration(basicContext); + var config = Components.HttpConfiguration().CreateHttpConfiguration(mobileKey, applicationInfo); Assert.False(HeadersAsMap(config.DefaultHeaders).ContainsKey("x-launchdarkly-wrapper")); } @@ -94,7 +96,7 @@ public void WrapperDefaultNone() public void WrapperNameOnly() { var config = Components.HttpConfiguration().Wrapper("w", null) - .CreateHttpConfiguration(basicContext); + .CreateHttpConfiguration(mobileKey, applicationInfo); Assert.Equal("w", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); } @@ -102,7 +104,7 @@ public void WrapperNameOnly() public void WrapperNameAndVersion() { var config = Components.HttpConfiguration().Wrapper("w", "1.0") - .CreateHttpConfiguration(basicContext); + .CreateHttpConfiguration(mobileKey, applicationInfo); Assert.Equal("w/1.0", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); } From 6aec53194598b7ada1bd31da2a891c60d8cc5f98 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 2 Oct 2023 09:50:42 -0700 Subject: [PATCH 447/499] update to latest usage of IOptionalProp --- .../PlatformSpecific/AppInfo.android.cs | 2 +- .../PlatformSpecific/AppInfo.ios.cs | 2 +- .../PlatformSpecific/AppInfo.netstandard.cs | 2 +- .../PlatformSpecific/AppInfo.shared.cs | 2 +- .../PlatformSpecific/DeviceInfo.android.cs | 8 ++++---- .../PlatformSpecific/DeviceInfo.ios.cs | 8 ++++---- .../PlatformSpecific/DeviceInfo.netstandard.cs | 6 +++--- .../PlatformSpecific/DeviceInfo.shared.cs | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index 2730d167..a474c66d 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -42,7 +42,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static IProp PlatformGetApplicationInfo() => new Props.Concrete(new ApplicationInfo( + static IOptionalProp PlatformGetApplicationInfo() => new Props.Some(new ApplicationInfo( PlatformGetAppId(), PlatformGetAppName(), PlatformGetAppVersion(), PlatformGetAppVersionName())); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index a81a4b10..7d6acdf7 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -41,7 +41,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static IProp PlatformGetApplicationInfo() => new Props.Concrete( + static IOptionalProp PlatformGetApplicationInfo() => new Props.Some( new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), PlatformGetAppVersion(), PlatformGetAppVersionName())); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index 1393de8b..969d42db 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -4,6 +4,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - private static IProp PlatformGetApplicationInfo() => new Props.Fallthrough(); + private static IOptionalProp PlatformGetApplicationInfo() => new Props.None(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index 2ff57a7c..3c790b76 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -6,6 +6,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - internal static IProp GetAppInfo() => PlatformGetApplicationInfo(); + internal static IOptionalProp GetAppInfo() => PlatformGetApplicationInfo(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs index 31ce5029..905df554 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -34,11 +34,11 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static IProp PlatformGetOsInfo() => - new Props.Concrete(new OsInfo(GetPlatform().ToString(), GetPlatform().ToString(), GetVersionString())); + private static IOptionalProp PlatformGetOsInfo() => + new Props.Some(new OsInfo(GetPlatform().ToString(), GetPlatform().ToString(), GetVersionString())); - private static IProp PlatformGetDeviceInfo() => - new Props.Concrete(new EnvReporting.LayerModels.DeviceInfo( + private static IOptionalProp PlatformGetDeviceInfo() => + new Props.Some(new EnvReporting.LayerModels.DeviceInfo( GetManufacturer(), GetModel())); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs index e715695a..a10481da 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -44,11 +44,11 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class DeviceInfo { - private static IProp PlatformGetOsInfo() => - new Props.Concrete(new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString())); + private static IOptionalProp PlatformGetOsInfo() => + new Props.Some(new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString())); - private static IProp PlatformGetDeviceInfo() => - new Props.Concrete(new EnvReporting.LayerModels.DeviceInfo( + private static IOptionalProp PlatformGetDeviceInfo() => + new Props.Some(new EnvReporting.LayerModels.DeviceInfo( GetManufacturer(), GetModel())); static string GetModel() { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs index 66a31fa4..a79a81b2 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -5,9 +5,9 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static IProp PlatformGetOsInfo() => new Props.Fallthrough(); + private static IOptionalProp PlatformGetOsInfo() => new Props.None(); - private static IProp PlatformGetDeviceInfo() => - new Props.Fallthrough(); + private static IOptionalProp PlatformGetDeviceInfo() => + new Props.None(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs index 8da48212..ae8c0f1f 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs @@ -7,8 +7,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - internal static IProp GetOsInfo() => PlatformGetOsInfo(); + internal static IOptionalProp GetOsInfo() => PlatformGetOsInfo(); - internal static IProp GetDeviceInfo() => PlatformGetDeviceInfo(); + internal static IOptionalProp GetDeviceInfo() => PlatformGetDeviceInfo(); } } From 1eb106b6ddea103add27075deca1e0f76c00cbb9 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 2 Oct 2023 12:10:46 -0700 Subject: [PATCH 448/499] Refactor SDK name and version into common utility file (update diagnostic and HTTP config) --- .../Integrations/HttpConfigurationBuilder.cs | 3 +- .../Internal/Events/ClientDiagnosticStore.cs | 2 +- .../Internal/SdkPackage.cs | 34 +++++++++++++++++++ .../Subsystems/LdClientContext.cs | 9 +++-- .../Subsystems/SdkAttributes.cs | 17 ++++++++++ 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs create mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 1f23f7de..342f7012 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -8,6 +8,7 @@ using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Internal; namespace LaunchDarkly.Sdk.Client.Integrations { @@ -273,7 +274,7 @@ private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo appl .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) - .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) + .WithUserAgent(SdkPackage.UserAgent) .WithApplicationTags(applicationInfo) .WithWrapper(_wrapperName, _wrapperVersion); diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index bb7b25e8..222061f4 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -15,7 +15,7 @@ internal class ClientDiagnosticStore : DiagnosticStoreBase private readonly TimeSpan _startWaitTime; protected override string SdkKeyOrMobileKey => _context.MobileKey; - protected override string SdkName => "dotnet-client-sdk"; + protected override string SdkName => SdkPackage.Name; protected override IEnumerable ConfigProperties => GetConfigProperties(); protected override string DotNetTargetFramework => GetDotNetTargetFramework(); protected override HttpProperties HttpProperties => _context.Http.HttpProperties; diff --git a/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs new file mode 100644 index 00000000..1defe292 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs @@ -0,0 +1,34 @@ +using LaunchDarkly.Sdk.Internal; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + + /// + /// Defines common information about the SDK itself for usage + /// in various components. + /// + internal static class SdkPackage + { + /// + /// The canonical name of this SDK, following the convention of (technology)-(server|client)-sdk. + /// + internal const string Name = "dotnet-client-sdk"; + + /// + /// The prefix for the User-Agent header, omitting the version string. This may be different than the Name + /// due to historical reasons. + /// + private const string UserAgentPrefix = "XamarinClient"; + + /// + /// Version of the SDK. + /// + internal static string Version => AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)); + + /// + /// User-Agent suitable for usage in HTTP requests. + /// + internal static string UserAgent => $"{UserAgentPrefix}/{Version}"; + + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 79f0c0c7..1509b347 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -168,15 +168,20 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil if (applicationInfoBuilder != null) { var applicationInfo = applicationInfoBuilder.Build(); + + // If AppInfo is provided by the user, then the Config layer has first priority in the environment reporter. builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } + // The platform layer has second priority if properties aren't set by the Config layer. builder.SetPlatformLayer(PlatformAttributes.Layer); + + // The SDK layer has third priority if properties aren't set by the Platform layer. + builder.SetSdkLayer(SdkAttributes.Layer); return builder.Build(); } - internal LdClientContext WithContextAndBackgroundState( Context newCurrentContext, bool newInBackground @@ -233,4 +238,4 @@ IDiagnosticStore newDiagnosticStore newDiagnosticStore, TaskExecutor); } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs new file mode 100644 index 00000000..5a46d151 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -0,0 +1,17 @@ +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.Subsystems +{ + internal static class SdkAttributes + { + internal static Layer Layer => new Layer + { + ApplicationInfo = new Props.Some(new ApplicationInfo( + SdkPackage.Name, + SdkPackage.Name, + SdkPackage.Version, + SdkPackage.Version)) + }; + } +} From 526a541963c10e085bec58e5f9d47373ab36afc7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 2 Oct 2023 12:11:07 -0700 Subject: [PATCH 449/499] propagate platform name in netstandard configuration --- .../DeviceInfo.netstandard.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs index a79a81b2..cab03fbf 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.EnvReporting.LayerModels; @@ -5,7 +6,25 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static IOptionalProp PlatformGetOsInfo() => new Props.None(); + private static IOptionalProp PlatformGetOsInfo() + { + var osName = "unknown"; + var osFamily = "unknown"; + var osVersion = "unknown"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + osName = OSPlatform.Linux.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + osName = OSPlatform.Windows.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + osName = OSPlatform.OSX.ToString(); + } + + return new Props.Some(new OsInfo(osFamily, osName, osVersion)); + } private static IOptionalProp PlatformGetDeviceInfo() => new Props.None(); From 8c4d660295936d96672c54e49c8bed8bb0dfdee8 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 2 Oct 2023 15:45:08 -0500 Subject: [PATCH 450/499] feat: incorporate client sdk default header values when no application info provided. --- .../Integrations/HttpConfigurationBuilder.cs | 7 ++-- .../Internal/Events/ClientDiagnosticStore.cs | 6 ++-- .../Internal/SdkPackage.cs | 34 +++++++++++++++++++ .../Subsystems/LdClientContext.cs | 3 +- .../Subsystems/SdkAttributes.cs | 17 ++++++++++ .../ConfigurationTest.cs | 10 +++++- .../HttpConfigurationBuilderTest.cs | 10 +++++- 7 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs create mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index deb78c70..ce8458af 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; +using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Internal.Http; using LaunchDarkly.Sdk.Client.Subsystems; @@ -136,14 +137,14 @@ public HttpConfigurationBuilder MessageHandler(HttpMessageHandler messageHandler /// /// /// // Example of using an HTTP proxy with basic authentication - /// + /// /// var proxyUri = new Uri("http://my-proxy-host:8080"); /// var proxy = new System.Net.WebProxy(proxyUri); /// var credentials = new System.Net.CredentialCache(); /// credentials.Add(proxyUri, "Basic", /// new System.Net.NetworkCredential("username", "password")); /// proxy.Credentials = credentials; - /// + /// /// var config = Configuration.Builder("my-sdk-key") /// .Http( /// Components.HttpConfiguration().Proxy(proxy) @@ -271,7 +272,7 @@ private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo appl .WithConnectTimeout(_connectTimeout) .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) - .WithUserAgent("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient))) + .WithUserAgent(SdkPackage.UserAgent) .WithApplicationTags(applicationInfo) .WithWrapper(_wrapperName, _wrapperVersion); diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index bb7b25e8..d5af2563 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; +using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.Internal.Events; using LaunchDarkly.Sdk.Internal.Http; -using LaunchDarkly.Sdk.Client.Subsystems; - -using static LaunchDarkly.Sdk.Internal.Events.DiagnosticConfigProperties; namespace LaunchDarkly.Sdk.Client.Internal.Events { @@ -15,7 +13,7 @@ internal class ClientDiagnosticStore : DiagnosticStoreBase private readonly TimeSpan _startWaitTime; protected override string SdkKeyOrMobileKey => _context.MobileKey; - protected override string SdkName => "dotnet-client-sdk"; + protected override string SdkName => SdkPackage.Name; protected override IEnumerable ConfigProperties => GetConfigProperties(); protected override string DotNetTargetFramework => GetDotNetTargetFramework(); protected override HttpProperties HttpProperties => _context.Http.HttpProperties; diff --git a/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs new file mode 100644 index 00000000..e6749c14 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs @@ -0,0 +1,34 @@ +using LaunchDarkly.Sdk.Internal; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + + /// + /// Defines common information about the SDK itself for usage + /// in various components. + /// + internal static class SdkPackage + { + /// + /// The canonical name of this SDK, following the convention of (technology)-(server|client)-sdk. + /// + internal const string Name = "dotnet-client-sdk"; + + /// + /// The prefix for the User-Agent header, omitting the version string. This may be different than the Name + /// due to historical reasons. + /// + private const string UserAgentPrefix = "XamarinClient"; + + /// + /// Version of the SDK. + /// + internal static string Version => AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)); + + /// + /// User-Agent suitable for usage in HTTP requests. + /// + internal static string UserAgent => $"{UserAgentPrefix}/{Version}"; + + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 532397e7..5904166d 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -165,6 +165,7 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } + builder.SetSdkLayer(SdkAttributes.Layer); return builder.Build(); } @@ -224,4 +225,4 @@ IDiagnosticStore newDiagnosticStore newDiagnosticStore, TaskExecutor); } -} \ No newline at end of file +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs new file mode 100644 index 00000000..84248067 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -0,0 +1,17 @@ +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.Subsystems +{ + internal static class SdkAttributes + { + internal static Layer Layer => new Layer + { + ApplicationInfo = new Props.Concrete(new ApplicationInfo( + SdkPackage.Name, + SdkPackage.Name, + SdkPackage.Version, + SdkPackage.Version)) + }; + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 76c39eb7..eb95f354 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -31,6 +31,14 @@ public void BuilderSetsKey() Assert.Equal(mobileKey, config.MobileKey); } + [Fact] + public void ApplicationInfo() + { + var prop = _tester.Property(c => c.ApplicationInfo, (b, v) => b.ApplicationInfo(v)); + prop.AssertDefault(null); + prop.AssertCanSet(new ApplicationInfoBuilder()); + } + [Fact] public void DataSource() { @@ -131,4 +139,4 @@ public void MobileKeyCannotBeEmpty() Assert.Throws(() => Configuration.Default("")); } } -} \ No newline at end of file +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index bdf512f2..401aa7d3 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -13,7 +13,7 @@ namespace LaunchDarkly.Sdk.Client.Integrations public class HttpConfigurationBuilderTest { private static string mobileKey = "mobile-key"; - private static ApplicationInfo applicationInfo => new ApplicationInfo("mockId", "mockName", "blah", "blah"); + private static ApplicationInfo applicationInfo => new ApplicationInfo("mockId", "mockName", "mockVersion", "mockVersionName"); private readonly BuilderBehavior.BuildTester _tester = BuilderBehavior.For(() => Components.HttpConfiguration(), @@ -108,6 +108,14 @@ public void WrapperNameAndVersion() Assert.Equal("w/1.0", HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-wrapper"]); } + [Fact] + public void ApplicationTagsHeader() + { + var config = Components.HttpConfiguration().CreateHttpConfiguration(mobileKey, applicationInfo); + Assert.Equal("application-id/mockId application-name/mockName application-version/mockVersion application-version-name/mockVersionName", + HeadersAsMap(config.DefaultHeaders)["x-launchdarkly-tags"]); + } + private static Dictionary HeadersAsMap(IEnumerable> headers) { return headers.ToDictionary(kv => kv.Key.ToLower(), kv => kv.Value); From 4c55a5d227399a4f7c4e17ccec6f47e9fa657af4 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 2 Oct 2023 16:28:49 -0700 Subject: [PATCH 451/499] remove IProp/IOptionalProp machinery --- .../PlatformSpecific/AppInfo.android.cs | 14 +++++++----- .../PlatformSpecific/AppInfo.ios.cs | 15 ++++++++----- .../PlatformSpecific/AppInfo.netstandard.cs | 2 +- .../PlatformSpecific/AppInfo.shared.cs | 2 +- .../PlatformSpecific/DeviceInfo.android.cs | 22 ++++++++++++------- .../PlatformSpecific/DeviceInfo.ios.cs | 10 ++++----- .../DeviceInfo.netstandard.cs | 7 +++--- .../PlatformSpecific/DeviceInfo.shared.cs | 4 ++-- .../Subsystems/PlatformAttributes.cs | 11 +++++----- .../Subsystems/SdkAttributes.cs | 14 +++++------- 10 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index a474c66d..d9f51f13 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -42,19 +42,21 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static IOptionalProp PlatformGetApplicationInfo() => new Props.Some(new ApplicationInfo( - PlatformGetAppId(), PlatformGetAppName(), - PlatformGetAppVersion(), PlatformGetAppVersionName())); - + static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( + PlatformGetAppId(), + PlatformGetAppName(), + PlatformGetAppVersion(), + PlatformGetAppVersionName()); + // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. static string PlatformGetAppId() => Platform.AppContext.PackageName; static string PlatformGetAppName() => PlatformGetName(); static string PlatformGetAppVersion() => PlatformGetBuild(); static string PlatformGetAppVersionName() => PlatformGetVersionString(); - + // End LaunchDarkly additions. - + static string PlatformGetPackageName() => Platform.AppContext.PackageName; static string PlatformGetName() diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index 7d6acdf7..44bc877e 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -33,6 +33,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using LaunchDarkly.Sdk.EnvReporting; #if __IOS__ || __TVOS__ using UIKit; + #elif __MACOS__ using AppKit; #endif @@ -41,21 +42,23 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - static IOptionalProp PlatformGetApplicationInfo() => new Props.Some( - new ApplicationInfo(PlatformGetAppId(), PlatformGetAppName(), - PlatformGetAppVersion(), PlatformGetAppVersionName())); - + static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( + PlatformGetAppId(), + PlatformGetAppName(), + PlatformGetAppVersion(), + PlatformGetAppVersionName()); + // The following methods are added by LaunchDarkly to align with the Application Info // required by the SDK. static string PlatformGetAppId() => GetBundleValue("CFBundleIdentifier"); static string PlatformGetAppName() => GetBundleValue("CFBundleName"); static string PlatformGetAppVersion() => GetBundleValue("CFBundleVersion"); static string PlatformGetAppVersionName() => GetBundleValue("CFBundleShortString"); - + // End LaunchDarkly additions. static string GetBundleValue(string key) - => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); + => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); // #if __IOS__ || __TVOS__ // static async void PlatformShowSettingsUI() diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index 969d42db..6d1ecdcc 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -4,6 +4,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - private static IOptionalProp PlatformGetApplicationInfo() => new Props.None(); + private static ApplicationInfo? PlatformGetApplicationInfo() => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index 3c790b76..19689b96 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -6,6 +6,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - internal static IOptionalProp GetAppInfo() => PlatformGetApplicationInfo(); + internal static ApplicationInfo? GetAppInfo() => PlatformGetApplicationInfo(); } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs index 905df554..9077e3f7 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -34,14 +34,20 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static IOptionalProp PlatformGetOsInfo() => - new Props.Some(new OsInfo(GetPlatform().ToString(), GetPlatform().ToString(), GetVersionString())); - - private static IOptionalProp PlatformGetDeviceInfo() => - new Props.Some(new EnvReporting.LayerModels.DeviceInfo( - GetManufacturer(), GetModel())); - - + private static OsInfo? PlatformGetOsInfo() => + new OsInfo( + GetPlatform().ToString(), + GetPlatform().ToString(), + GetVersionString() + ); + + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => + new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), + GetModel() + ); + + //const int tabletCrossover = 600; static string GetModel() => Build.Model; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs index a10481da..2ff0def0 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -44,12 +44,12 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class DeviceInfo { - private static IOptionalProp PlatformGetOsInfo() => - new Props.Some(new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString())); + private static OsInfo? PlatformGetOsInfo() => + new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString()); - private static IOptionalProp PlatformGetDeviceInfo() => - new Props.Some(new EnvReporting.LayerModels.DeviceInfo( - GetManufacturer(), GetModel())); + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => + new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), GetModel()); static string GetModel() { try diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs index cab03fbf..15a3386d 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -6,7 +6,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static IOptionalProp PlatformGetOsInfo() + private static OsInfo? PlatformGetOsInfo() { var osName = "unknown"; var osFamily = "unknown"; @@ -23,10 +23,9 @@ private static IOptionalProp PlatformGetOsInfo() osName = OSPlatform.OSX.ToString(); } - return new Props.Some(new OsInfo(osFamily, osName, osVersion)); + return new OsInfo(osFamily, osName, osVersion); } - private static IOptionalProp PlatformGetDeviceInfo() => - new Props.None(); + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs index ae8c0f1f..f4e5b78a 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs @@ -7,8 +7,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - internal static IOptionalProp GetOsInfo() => PlatformGetOsInfo(); + internal static OsInfo? GetOsInfo() => PlatformGetOsInfo(); - internal static IOptionalProp GetDeviceInfo() => PlatformGetDeviceInfo(); + internal static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? GetDeviceInfo() => PlatformGetDeviceInfo(); } } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index f397a8b8..63ec5d65 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -5,11 +5,10 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { internal static class PlatformAttributes { - internal static Layer Layer => new Layer - { - ApplicationInfo = AppInfo.GetAppInfo(), - OsInfo = DeviceInfo.GetOsInfo(), - DeviceInfo = DeviceInfo.GetDeviceInfo() - }; + internal static Layer Layer => new Layer( + AppInfo.GetAppInfo(), + DeviceInfo.GetOsInfo(), + DeviceInfo.GetDeviceInfo() + ); } } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs index 5a46d151..791d17f1 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -5,13 +5,11 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { internal static class SdkAttributes { - internal static Layer Layer => new Layer - { - ApplicationInfo = new Props.Some(new ApplicationInfo( - SdkPackage.Name, - SdkPackage.Name, - SdkPackage.Version, - SdkPackage.Version)) - }; + internal static Layer Layer => new Layer(new ApplicationInfo( + SdkPackage.Name, + SdkPackage.Name, + SdkPackage.Version, + SdkPackage.Version), + null, null); } } From 2e7ae5eaf7508befdd6204dcf3542188ffd1e04f Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 3 Oct 2023 13:56:20 -0500 Subject: [PATCH 452/499] refactor: refactoring LdClientContext to eliminate constructor spaghetti. --- .../Subsystems/LdClientContext.cs | 258 ++++++++++-------- 1 file changed, 137 insertions(+), 121 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 5904166d..57d28a51 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -24,17 +24,17 @@ public sealed class LdClientContext /// /// The configured mobile key. /// - public string MobileKey { get; } + public string MobileKey { get; private set; } /// /// The configured logger for the SDK. /// - public Logger BaseLogger { get; } + public Logger BaseLogger { get; private set; } /// /// The current evaluation context. /// - public Context CurrentContext { get; } + public Context CurrentContext { get; private set; } /// /// A component that implementations use to deliver status updates to @@ -44,110 +44,182 @@ public sealed class LdClientContext /// This property is only set when the SDK is calling an factory. /// Otherwise it is null. /// - public IDataSourceUpdateSink DataSourceUpdateSink { get; } + public IDataSourceUpdateSink DataSourceUpdateSink { get; private set; } /// /// Whether to enable feature flag updates when the application is running in the background. /// - public bool EnableBackgroundUpdating { get; } + public bool EnableBackgroundUpdating { get; private set; } /// /// True if evaluation reasons are enabled. /// - public bool EvaluationReasons { get; } + public bool EvaluationReasons { get; private set; } /// /// The HTTP configuration properties. /// - public HttpConfiguration Http { get; } + public HttpConfiguration Http { get; private set; } /// /// True if the application is currently in a background state. /// - public bool InBackground { get; } + public bool InBackground { get; private set; } /// /// The configured service base URIs. /// - public ServiceEndpoints ServiceEndpoints { get; } + public ServiceEndpoints ServiceEndpoints { get; private set; } /// /// The environment reporter. /// - internal IEnvironmentReporter EnvironmentReporter { get; } + internal IEnvironmentReporter EnvironmentReporter { get; private set; } - internal IDiagnosticDisabler DiagnosticDisabler { get; } + internal IDiagnosticDisabler DiagnosticDisabler { get; private set; } - internal IDiagnosticStore DiagnosticStore { get; } + internal IDiagnosticStore DiagnosticStore { get; private set; } - internal TaskExecutor TaskExecutor { get; } + internal TaskExecutor TaskExecutor { get; private set; } /// /// Creates an instance. /// /// the SDK configuration /// the current evaluation context + /// public LdClientContext( Configuration configuration, - Context currentContext - ) : this(configuration, currentContext, null) { } - - internal LdClientContext(Configuration configuration, Context currentContext, object eventSender) : this( - configuration.MobileKey, - MakeLogger(configuration), - currentContext, - null, - configuration.EnableBackgroundUpdating, - configuration.EvaluationReasons, - (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) - .CreateHttpConfiguration(configuration.MobileKey, - MakeEnvironmentReporter(configuration.ApplicationInfo).ApplicationInfo), - false, - configuration.ServiceEndpoints, - MakeEnvironmentReporter(configuration.ApplicationInfo), - null, - null, - new TaskExecutor( - eventSender, - AsyncScheduler.ScheduleAction, - MakeLogger(configuration) - ) - ) { } - - internal LdClientContext( - string mobileKey, - Logger baseLogger, Context currentContext, - IDataSourceUpdateSink dataSourceUpdateSink, - bool enableBackgroundUpdating, - bool evaluationReasons, - HttpConfiguration http, - bool inBackground, - ServiceEndpoints serviceEndpoints, - IEnvironmentReporter environmentReporter, - IDiagnosticDisabler diagnosticDisabler, - IDiagnosticStore diagnosticStore, - TaskExecutor taskExecutor - ) + object eventSender = null + ) { - MobileKey = mobileKey; - BaseLogger = baseLogger; + var logger = MakeLogger(configuration); + var environmentReporter = MakeEnvironmentReporter(configuration.ApplicationInfo); + MobileKey = configuration.MobileKey; + BaseLogger = logger; CurrentContext = currentContext; - DataSourceUpdateSink = dataSourceUpdateSink; - EnableBackgroundUpdating = enableBackgroundUpdating; - EvaluationReasons = evaluationReasons; - Http = http; - InBackground = inBackground; - ServiceEndpoints = serviceEndpoints ?? Components.ServiceEndpoints().Build(); + DataSourceUpdateSink = null; + EnableBackgroundUpdating = configuration.EnableBackgroundUpdating; + EvaluationReasons = configuration.EvaluationReasons; + Http = (configuration.HttpConfigurationBuilder ?? Components.HttpConfiguration()) + .CreateHttpConfiguration(configuration.MobileKey, environmentReporter.ApplicationInfo); + InBackground = false; + ServiceEndpoints = configuration.ServiceEndpoints; EnvironmentReporter = environmentReporter; - DiagnosticDisabler = diagnosticDisabler; - DiagnosticStore = diagnosticStore; - TaskExecutor = taskExecutor ?? new TaskExecutor(null, + DiagnosticDisabler = null; + DiagnosticStore = null; + TaskExecutor = new TaskExecutor( + eventSender, AsyncScheduler.ScheduleAction, - baseLogger - ); + MakeLogger(configuration) + ); } + /// + /// Copy constructor + /// + /// to use as reference for copying + private LdClientContext(LdClientContext toCopy) + { + MobileKey = toCopy.MobileKey; + BaseLogger = toCopy.BaseLogger; + CurrentContext = toCopy.CurrentContext; + DataSourceUpdateSink = toCopy.DataSourceUpdateSink; + EnableBackgroundUpdating = toCopy.EnableBackgroundUpdating; + EvaluationReasons = toCopy.EvaluationReasons; + Http = toCopy.Http; + InBackground = toCopy.InBackground; + ServiceEndpoints = toCopy.ServiceEndpoints; + EnvironmentReporter = toCopy.EnvironmentReporter; + DiagnosticDisabler = toCopy.DiagnosticDisabler; + DiagnosticStore = toCopy.DiagnosticStore; + TaskExecutor = toCopy.TaskExecutor; + } + + internal LdClientContext WithLogger(Logger logger) => + new LdClientContext(this) + { + BaseLogger = logger, + }; + + internal LdClientContext WithContext(Context context) => + new LdClientContext(this) + { + CurrentContext = context, + }; + + internal LdClientContext WithBackgroundUpdatingEnabled(bool enabled) => + new LdClientContext(this) + { + EnableBackgroundUpdating = enabled, + }; + + internal LdClientContext WithEvaluationReasons(bool enabled) => + new LdClientContext(this) + { + EvaluationReasons = enabled, + }; + + internal LdClientContext WithHttpConfiguration(HttpConfiguration configuration) => + new LdClientContext(this) + { + Http = configuration, + }; + + internal LdClientContext WithInBackground(bool inBackground) => + new LdClientContext(this) + { + InBackground = inBackground, + }; + + internal LdClientContext WithServiceEndpoints(ServiceEndpoints endpoints) => + new LdClientContext(this) + { + ServiceEndpoints = endpoints, + }; + + internal LdClientContext WithEnvironmentReporter(IEnvironmentReporter reporter) => + new LdClientContext(this) + { + EnvironmentReporter = reporter, + }; + + internal LdClientContext WithTaskExecutor(TaskExecutor executor) => + new LdClientContext(this) + { + TaskExecutor = executor, + }; + + internal LdClientContext WithMobileKey(string mobileKey) => + new LdClientContext(this) + { + MobileKey = mobileKey, + }; + + internal LdClientContext WithContextAndBackgroundState(Context newCurrentContext, bool newInBackground) => + new LdClientContext(this) + { + CurrentContext = newCurrentContext, + InBackground = newInBackground + }; + + internal LdClientContext WithDataSourceUpdateSink(IDataSourceUpdateSink newDataSourceUpdateSink) => + new LdClientContext(this) + { + DataSourceUpdateSink = newDataSourceUpdateSink + }; + + internal LdClientContext WithDiagnostics( + IDiagnosticDisabler newDiagnosticDisabler, + IDiagnosticStore newDiagnosticStore + ) => + new LdClientContext(this) + { + DiagnosticDisabler = newDiagnosticDisabler, + DiagnosticStore = newDiagnosticStore + }; + internal static Logger MakeLogger(Configuration configuration) { var logConfig = (configuration.LoggingConfigurationBuilder ?? Components.Logging()) @@ -168,61 +240,5 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil builder.SetSdkLayer(SdkAttributes.Layer); return builder.Build(); } - - internal LdClientContext WithContextAndBackgroundState( - Context newCurrentContext, - bool newInBackground - ) => - new LdClientContext( - MobileKey, - BaseLogger, - newCurrentContext, - DataSourceUpdateSink, - EnableBackgroundUpdating, - EvaluationReasons, - Http, - newInBackground, - ServiceEndpoints, - EnvironmentReporter, - DiagnosticDisabler, - DiagnosticStore, - TaskExecutor); - - internal LdClientContext WithDataSourceUpdateSink( - IDataSourceUpdateSink newDataSourceUpdateSink - ) => - new LdClientContext( - MobileKey, - BaseLogger, - CurrentContext, - newDataSourceUpdateSink, - EnableBackgroundUpdating, - EvaluationReasons, - Http, - InBackground, - ServiceEndpoints, - EnvironmentReporter, - DiagnosticDisabler, - DiagnosticStore, - TaskExecutor); - - internal LdClientContext WithDiagnostics( - IDiagnosticDisabler newDiagnosticDisabler, - IDiagnosticStore newDiagnosticStore - ) => - new LdClientContext( - MobileKey, - BaseLogger, - CurrentContext, - DataSourceUpdateSink, - EnableBackgroundUpdating, - EvaluationReasons, - Http, - InBackground, - ServiceEndpoints, - EnvironmentReporter, - newDiagnosticDisabler, - newDiagnosticStore, - TaskExecutor); } } From a8fbc313dd72b7aa0e9f985c36a90a5597aee2bd Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 4 Oct 2023 10:49:02 -0500 Subject: [PATCH 453/499] feat: adds automatic environment attributes support --- contract-tests/SdkClientEntity.cs | 2 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 30 ++-- .../ConfigurationBuilder.cs | 9 +- ...tor.cs => AnonymousKeyContextDecorator.cs} | 4 +- .../Internal/AutoEnvContextDecorator.cs | 144 +++++++++++++++ .../Internal/XamarinEsssentialsUtils.cs | 69 +++++++ .../LaunchDarkly.ClientSdk.csproj | 32 +++- src/LaunchDarkly.ClientSdk/LdClient.cs | 35 ++-- .../PlatformSpecific/AppInfo.android.cs | 125 +++++++++++++ .../PlatformSpecific/AppInfo.ios.cs | 169 ++++++++++++++++++ .../PlatformSpecific/AppInfo.netstandard.cs | 9 + .../PlatformSpecific/AppInfo.shared.cs | 11 ++ .../PlatformSpecific/DeviceInfo.android.cs | 164 +++++++++++++++++ .../PlatformSpecific/DeviceInfo.ios.cs | 105 +++++++++++ .../DeviceInfo.netstandard.cs | 31 ++++ .../PlatformSpecific/DeviceInfo.shared.cs | 14 ++ .../PlatformSpecific/DevicePlatform.shared.cs | 60 +++++++ .../PlatformSpecific/Platform.android.cs | 2 +- .../PlatformSpecific/Platform.ios.cs | 62 +++++++ .../Subsystems/LdClientContext.cs | 6 + .../Subsystems/PlatformAttributes.cs | 14 ++ .../Subsystems/SdkAttributes.cs | 8 +- ...cs => AnonymousKeyContextDecoratorTest.cs} | 8 +- .../Internal/Base64Test.cs | 28 +++ 24 files changed, 1094 insertions(+), 47 deletions(-) rename src/LaunchDarkly.ClientSdk/Internal/{ContextDecorator.cs => AnonymousKeyContextDecorator.cs} (96%) create mode 100644 src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs create mode 100644 src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs rename tests/LaunchDarkly.ClientSdk.Tests/Internal/{ContextDecoratorTest.cs => AnonymousKeyContextDecoratorTest.cs} (94%) diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index 81317bef..fbff2825 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -266,7 +266,7 @@ private ContextBuildResponse DoContextConvert(ContextConvertParams p) private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) { - var builder = Configuration.Builder(sdkParams.Credential); + var builder = Configuration.Builder(sdkParams.Credential, true); builder.Logging(Components.Logging(logAdapter).BaseLoggerName(tag + ".SDK")); diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 319cf400..bb0edbe2 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -7,7 +7,7 @@ namespace LaunchDarkly.Sdk.Client { /// - /// Configuration options for . + /// Configuration options for . /// /// /// Instances of are immutable once created. They can be created with the factory method @@ -16,6 +16,16 @@ namespace LaunchDarkly.Sdk.Client /// public sealed class Configuration { + /// + /// ApplicationInfo configuration which contains info about the application the SDK is running in. + /// + public ApplicationInfoBuilder ApplicationInfo { get; } + + /// + /// TODO + /// + public bool AutoEnvAttributes { get; } + /// /// Default value for and /// . @@ -118,19 +128,15 @@ public sealed class Configuration /// public ServiceEndpoints ServiceEndpoints { get; } - /// - /// ApplicationInfo configuration which contains info about the application the SDK is running in. - /// - public ApplicationInfoBuilder ApplicationInfo { get; } - /// /// Creates a configuration with all parameters set to the default. /// /// the SDK key for your LaunchDarkly environment + /// TODOo /// a instance - public static Configuration Default(string mobileKey) + public static Configuration Default(string mobileKey, bool autoEnvAttributes) { - return Builder(mobileKey).Build(); + return Builder(mobileKey, autoEnvAttributes).Build(); } /// @@ -151,14 +157,15 @@ public static Configuration Default(string mobileKey) /// /// /// the mobile SDK key for your LaunchDarkly environment + /// TODOo /// a builder object - public static ConfigurationBuilder Builder(string mobileKey) + public static ConfigurationBuilder Builder(string mobileKey, bool autoEnvAttributes) { if (String.IsNullOrEmpty(mobileKey)) { throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); } - return new ConfigurationBuilder(mobileKey); + return new ConfigurationBuilder(mobileKey, autoEnvAttributes); } /// @@ -173,6 +180,8 @@ public static ConfigurationBuilder Builder(Configuration fromConfiguration) internal Configuration(ConfigurationBuilder builder) { + ApplicationInfo = builder._applicationInfo; + AutoEnvAttributes = builder._autoEnvAttributes; DataSource = builder._dataSource; DiagnosticOptOut = builder._diagnosticOptOut; EnableBackgroundUpdating = builder._enableBackgroundUpdating; @@ -185,7 +194,6 @@ internal Configuration(ConfigurationBuilder builder) Offline = builder._offline; PersistenceConfigurationBuilder = builder._persistenceConfigurationBuilder; ServiceEndpoints = (builder._serviceEndpointsBuilder ?? Components.ServiceEndpoints()).Build(); - ApplicationInfo = builder._applicationInfo; BackgroundModeManager = builder._backgroundModeManager; ConnectivityStateManager = builder._connectivityStateManager; } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index d7d05a45..4763b9eb 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -31,6 +31,7 @@ public sealed class ConfigurationBuilder internal static readonly HttpMessageHandler DefaultHttpMessageHandlerInstance = new HttpClientHandler(); internal ApplicationInfoBuilder _applicationInfo; + internal bool _autoEnvAttributes = false; internal IComponentConfigurer _dataSource = null; internal bool _diagnosticOptOut = false; internal bool _enableBackgroundUpdating = true; @@ -48,13 +49,16 @@ public sealed class ConfigurationBuilder internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; - internal ConfigurationBuilder(string mobileKey) + internal ConfigurationBuilder(string mobileKey, bool autoEnvAttributes) { _mobileKey = mobileKey; + _autoEnvAttributes = autoEnvAttributes; } internal ConfigurationBuilder(Configuration copyFrom) { + _applicationInfo = copyFrom.ApplicationInfo; + _autoEnvAttributes = copyFrom.AutoEnvAttributes; _dataSource = copyFrom.DataSource; _diagnosticOptOut = copyFrom.DiagnosticOptOut; _enableBackgroundUpdating = copyFrom.EnableBackgroundUpdating; @@ -66,7 +70,6 @@ internal ConfigurationBuilder(Configuration copyFrom) _offline = copyFrom.Offline; _persistenceConfigurationBuilder = copyFrom.PersistenceConfigurationBuilder; _serviceEndpointsBuilder = new ServiceEndpointsBuilder(copyFrom.ServiceEndpoints); - _applicationInfo = copyFrom.ApplicationInfo; } /// @@ -78,7 +81,7 @@ public Configuration Build() { return new Configuration(this); } - + /// /// Sets the SDK's application metadata, which may be used in the LaunchDarkly analytics or other product /// features. This object is normally a configuration builder obtained from , diff --git a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AnonymousKeyContextDecorator.cs similarity index 96% rename from src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs rename to src/LaunchDarkly.ClientSdk/Internal/AnonymousKeyContextDecorator.cs index 69d256cc..008a8bed 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/ContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AnonymousKeyContextDecorator.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Sdk.Client.Internal { - internal class ContextDecorator + internal class AnonymousKeyContextDecorator { private readonly PersistentDataStoreWrapper _store; private readonly bool _generateAnonymousKeys; @@ -13,7 +13,7 @@ internal class ContextDecorator private Dictionary _cachedGeneratedKey = new Dictionary(); private object _generatedKeyLock = new object(); - public ContextDecorator( + public AnonymousKeyContextDecorator( PersistentDataStoreWrapper store, bool generateAnonymousKeys ) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs new file mode 100644 index 00000000..5528def5 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.Client.Internal.DataStores; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + /// + /// TODO + /// + internal class AutoEnvContextModifier + { + private const string LD_APPLICATION_KIND = "ld_application"; + private const string LD_DEVICE_KIND = "ld_device"; + private const string ATTR_ID = "id"; + private const string ATTR_NAME = "name"; + private const string ATTR_VERSION = "version"; + private const string ATTR_VERSION_NAME = "versionName"; + private const string ATTR_MANUFACTURER = "manufacturer"; + private const string ATTR_MODEL = "model"; + private const string ATTR_LOCALE = "locale"; + private const string ATTR_OS = "os"; + private const string ATTR_FAMILY = "family"; + private const string ENV_ATTRIBUTES_VERSION = "envAttributesVersion"; + private const string SPEC_VERSION = "1.0"; + + private readonly PersistentDataStoreWrapper persistentData; + private readonly IEnvironmentReporter environmentReporter; + private readonly Logger logger; + + /// + /// TODO + /// + /// + /// + /// + public AutoEnvContextModifier( + PersistentDataStoreWrapper persistentData, + IEnvironmentReporter environmentReporter, + Logger logger) + { + this.persistentData = persistentData; + this.environmentReporter = environmentReporter; + this.logger = logger; + } + + public Context DecorateContext(Context context) + { + var builder = Context.MultiBuilder(); + builder.Add(context); + + foreach (ContextRecipe recipe in MakeRecipeList()) + { + if (!context.TryGetContextByKind(recipe.Kind, out _)) + { + // only add contexts for recipe Kinds not already in context to avoid overwriting data. + builder.Add(MakeLdContextFromRecipe(recipe)); + } + else + { + logger.Warn("Unable to automatically add environment attributes for kind:{0}. {1} already exists.", + recipe.Kind, recipe.Kind); + } + } + + return builder.Build(); + } + + private class ContextRecipe + { + public ContextKind Kind; + public Func KeyCallable; + public Dictionary> AttributeCallables; + + public ContextRecipe(ContextKind kind, Func keyCallable, + Dictionary> attributeCallables) + { + this.Kind = kind; + this.KeyCallable = keyCallable; + this.AttributeCallables = attributeCallables; + } + } + + private static Context MakeLdContextFromRecipe(ContextRecipe recipe) + { + var builder = Context.Builder(recipe.Kind, recipe.KeyCallable.Invoke()); + foreach (var entry in recipe.AttributeCallables) + { + builder.Set(entry.Key, entry.Value.Invoke()); + } + + return builder.Build(); + } + + private List MakeRecipeList() + { + var ldApplicationKind = ContextKind.Of(LD_APPLICATION_KIND); + var applicationCallables = new Dictionary> + { + { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, + { ATTR_ID, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationId) }, + { ATTR_NAME, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationName) }, + { ATTR_VERSION, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationVersion) }, + { ATTR_VERSION_NAME, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationVersionName) } + }; + + // TODO: missing locale in environment reporter implementation + // applicationCallables.Add(ATTR_LOCALE, () => LDValue.Of(environmentReporter.GetLocale())); + + var ldDeviceKind = ContextKind.Of(LD_DEVICE_KIND); + var deviceCallables = new Dictionary> + { + { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, + { ATTR_MANUFACTURER, () => LdValue.Of(environmentReporter.DeviceInfo.Manufacturer) }, + { ATTR_MODEL, () => LdValue.Of(environmentReporter.DeviceInfo.Model) }, + { + ATTR_OS, () => + LdValue.BuildObject() + .Add(ATTR_FAMILY, environmentReporter.OsInfo.Family) + .Add(ATTR_NAME, environmentReporter.OsInfo.Name) + .Add(ATTR_VERSION, environmentReporter.OsInfo.Version) + .Build() + } + }; + + return new List + { + new ContextRecipe( + ldApplicationKind, + () => Base64.UrlSafeSha256Hash( + environmentReporter.ApplicationInfo.ApplicationId ?? "" + ), + applicationCallables + ), + new ContextRecipe( + ldDeviceKind, + () => persistentData.GetGeneratedContextKey(ldDeviceKind), + deviceCallables + ) + }; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs new file mode 100644 index 00000000..f858a44a --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs @@ -0,0 +1,69 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + internal static class Utils + { + internal static Version ParseVersion(string version) + { + if (Version.TryParse(version, out var number)) + return number; + + if (int.TryParse(version, out var major)) + return new Version(major, 0); + + return new Version(0, 0); + } + + internal static CancellationToken TimeoutToken(CancellationToken cancellationToken, TimeSpan timeout) + { + // create a new linked cancellation token source + var cancelTokenSrc = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + // if a timeout was given, make the token source cancel after it expires + if (timeout > TimeSpan.Zero) + cancelTokenSrc.CancelAfter(timeout); + + // our Cancel method will handle the actual cancellation logic + return cancelTokenSrc.Token; + } + + internal static async Task WithTimeout(Task task, TimeSpan timeSpan) + { + var retTask = await Task.WhenAny(task, Task.Delay(timeSpan)) + .ConfigureAwait(false); + + return retTask is Task ? task.Result : default(T); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 70dd46ad..200d9f11 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -30,20 +30,24 @@ LaunchDarkly.Sdk.Client - 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 + + + 1570,1571,1572,1573,1580,1581,1584,1591,1710,1711,1712 - + - + - + + Code + @@ -65,7 +69,16 @@ - + + + + + + + + + + @@ -81,6 +94,15 @@ + + + + + + + + + ../../LaunchDarkly.ClientSdk.snk true diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 147ce5f9..6d9584ea 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -59,7 +59,8 @@ public sealed class LdClient : ILdClient readonly IEventProcessor _eventProcessor; readonly IFlagTracker _flagTracker; readonly TaskExecutor _taskExecutor; - readonly ContextDecorator _contextDecorator; + readonly AnonymousKeyContextDecorator _anonymousKeyAnonymousKeyContextDecorator; + private readonly AnonymousKeyContextDecorator _autoEnvContextDecorator; private readonly Logger _log; @@ -161,8 +162,8 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataStoreSubLog) ); - _contextDecorator = new ContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); - _context = _contextDecorator.DecorateContext(initialContext); + _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); + _context = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the @@ -218,7 +219,7 @@ public sealed class LdClient : ILdClient _log.Debug("Setting online to {0} due to a connectivity change event", networkAvailable); _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result }; - + // Send an initial identify event, but only if we weren't explicitly set to be offline if (!configuration.Offline) @@ -270,6 +271,7 @@ async Task StartAsync() /// /// /// the mobile key given to you by LaunchDarkly + /// TODOo /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize @@ -277,9 +279,9 @@ async Task StartAsync() /// /// /// - public static LdClient Init(string mobileKey, Context initialContext, TimeSpan maxWaitTime) + public static LdClient Init(string mobileKey, bool autoEnvAttributes, Context initialContext, TimeSpan maxWaitTime) { - var config = Configuration.Default(mobileKey); + var config = Configuration.Default(mobileKey, autoEnvAttributes); return Init(config, initialContext, maxWaitTime); } @@ -292,6 +294,7 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m /// type instead of . /// /// the mobile key given to you by LaunchDarkly + /// TODOo /// the initial user attributes; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize @@ -299,8 +302,8 @@ public static LdClient Init(string mobileKey, Context initialContext, TimeSpan m /// /// /// - public static LdClient Init(string mobileKey, User initialUser, TimeSpan maxWaitTime) => - Init(mobileKey, Context.FromUser(initialUser), maxWaitTime); + public static LdClient Init(string mobileKey, bool autoEnvAttributes, User initialUser, TimeSpan maxWaitTime) => + Init(mobileKey, autoEnvAttributes, Context.FromUser(initialUser), maxWaitTime); /// /// Creates a new singleton instance and attempts to initialize feature flags @@ -322,12 +325,13 @@ public static LdClient Init(string mobileKey, User initialUser, TimeSpan maxWait /// /// /// the mobile key given to you by LaunchDarkly + /// TODOo /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// a Task that resolves to the singleton LdClient instance - public static async Task InitAsync(string mobileKey, Context initialContext) + public static async Task InitAsync(string mobileKey, bool autoEnvAttributes, Context initialContext) { - var config = Configuration.Default(mobileKey); + var config = Configuration.Default(mobileKey, autoEnvAttributes); return await InitAsync(config, initialContext); } @@ -341,13 +345,14 @@ public static async Task InitAsync(string mobileKey, Context initialCo /// type instead of . /// /// the mobile key given to you by LaunchDarkly + /// TODOo /// the initial user attributes /// a Task that resolves to the singleton LdClient instance - public static Task InitAsync(string mobileKey, User initialUser) => - InitAsync(mobileKey, Context.FromUser(initialUser)); + public static Task InitAsync(string mobileKey, bool autoEnvAttributes, User initialUser) => + InitAsync(mobileKey, autoEnvAttributes, Context.FromUser(initialUser)); /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. /// /// @@ -390,7 +395,7 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp } /// - /// Creates and returns a new LdClient singleton instance, then starts the workflow for + /// Creates and returns a new LdClient singleton instance, then starts the workflow for /// fetching Feature Flags. /// /// @@ -685,7 +690,7 @@ public bool Identify(Context context, TimeSpan maxWaitTime) /// public async Task IdentifyAsync(Context context) { - Context newContext = _contextDecorator.DecorateContext(context); + Context newContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(context); Context oldContext = newContext; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs new file mode 100644 index 00000000..d9f51f13 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -0,0 +1,125 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System.Globalization; +using Android.Content; +using Android.Content.PM; +using Android.Content.Res; +using Android.Provider; +using LaunchDarkly.Sdk.EnvReporting; +#if __ANDROID_29__ +using AndroidX.Core.Content.PM; +#else +using Android.Support.V4.Content.PM; +#endif + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( + PlatformGetAppId(), + PlatformGetAppName(), + PlatformGetAppVersion(), + PlatformGetAppVersionName()); + + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + static string PlatformGetAppId() => Platform.AppContext.PackageName; + static string PlatformGetAppName() => PlatformGetName(); + static string PlatformGetAppVersion() => PlatformGetBuild(); + static string PlatformGetAppVersionName() => PlatformGetVersionString(); + + // End LaunchDarkly additions. + + static string PlatformGetPackageName() => Platform.AppContext.PackageName; + + static string PlatformGetName() + { + var applicationInfo = Platform.AppContext.ApplicationInfo; + var packageManager = Platform.AppContext.PackageManager; + return applicationInfo.LoadLabel(packageManager); + } + + static string PlatformGetVersionString() + { + var pm = Platform.AppContext.PackageManager; + var packageName = Platform.AppContext.PackageName; +#pragma warning disable CS0618 // Type or member is obsolete + using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) +#pragma warning restore CS0618 // Type or member is obsolete + { + return info.VersionName; + } + } + + static string PlatformGetBuild() + { + var pm = Platform.AppContext.PackageManager; + var packageName = Platform.AppContext.PackageName; +#pragma warning disable CS0618 // Type or member is obsolete + using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) +#pragma warning restore CS0618 // Type or member is obsolete + { +#if __ANDROID_28__ + return PackageInfoCompat.GetLongVersionCode(info).ToString(CultureInfo.InvariantCulture); +#else +#pragma warning disable CS0618 // Type or member is obsolete + return info.VersionCode.ToString(CultureInfo.InvariantCulture); +#pragma warning restore CS0618 // Type or member is obsolete +#endif + } + } + + // static void PlatformShowSettingsUI() + // { + // var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext; + // + // var settingsIntent = new Intent(); + // settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings); + // settingsIntent.AddCategory(Intent.CategoryDefault); + // settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName())); + // + // var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents; + // + // settingsIntent.SetFlags(flags); + // + // context.StartActivity(settingsIntent); + // } + + // static AppTheme PlatformRequestedTheme() + // { + // return (Platform.AppContext.Resources.Configuration.UiMode & UiMode.NightMask) switch + // { + // UiMode.NightYes => AppTheme.Dark, + // UiMode.NightNo => AppTheme.Light, + // _ => AppTheme.Unspecified + // }; + // } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs new file mode 100644 index 00000000..44bc877e --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -0,0 +1,169 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Foundation; +using LaunchDarkly.Sdk.EnvReporting; +#if __IOS__ || __TVOS__ +using UIKit; + +#elif __MACOS__ +using AppKit; +#endif + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( + PlatformGetAppId(), + PlatformGetAppName(), + PlatformGetAppVersion(), + PlatformGetAppVersionName()); + + // The following methods are added by LaunchDarkly to align with the Application Info + // required by the SDK. + static string PlatformGetAppId() => GetBundleValue("CFBundleIdentifier"); + static string PlatformGetAppName() => GetBundleValue("CFBundleName"); + static string PlatformGetAppVersion() => GetBundleValue("CFBundleVersion"); + static string PlatformGetAppVersionName() => GetBundleValue("CFBundleShortString"); + + // End LaunchDarkly additions. + + static string GetBundleValue(string key) + => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); + +// #if __IOS__ || __TVOS__ +// static async void PlatformShowSettingsUI() +// => await Launcher.OpenAsync(UIApplication.OpenSettingsUrlString); +// #elif __MACOS__ +// static void PlatformShowSettingsUI() +// { +// MainThread.BeginInvokeOnMainThread(() => +// { +// // Ignore obsolete for the time being. Will be updated in a future release. +// #pragma warning disable CS0618 // Type or member is obsolete +// var prefsApp = ScriptingBridge.SBApplication.FromBundleIdentifier("com.apple.systempreferences"); +// #pragma warning restore CS0618 // Type or member is obsolete +// prefsApp.SendMode = ScriptingBridge.AESendMode.NoReply; +// prefsApp.Activate(); +// }); +// } +// #else +// static void PlatformShowSettingsUI() => +// throw new FeatureNotSupportedException(); +// #endif + +// #if __IOS__ || __TVOS__ +// static AppTheme PlatformRequestedTheme() +// { +// if (!Platform.HasOSVersion(13, 0)) +// return AppTheme.Unspecified; +// +// var uiStyle = Platform.GetCurrentUIViewController()?.TraitCollection?.UserInterfaceStyle ?? +// UITraitCollection.CurrentTraitCollection.UserInterfaceStyle; +// +// return uiStyle switch +// { +// UIUserInterfaceStyle.Light => AppTheme.Light, +// UIUserInterfaceStyle.Dark => AppTheme.Dark, +// _ => AppTheme.Unspecified +// }; +// } +// #elif __MACOS__ +// static AppTheme PlatformRequestedTheme() +// { +// if (DeviceInfo.Version >= new Version(10, 14)) +// { +// var app = NSAppearance.CurrentAppearance?.FindBestMatch(new string[] +// { +// NSAppearance.NameAqua, +// NSAppearance.NameDarkAqua +// }); +// +// if (string.IsNullOrEmpty(app)) +// return AppTheme.Unspecified; +// +// if (app == NSAppearance.NameDarkAqua) +// return AppTheme.Dark; +// } +// return AppTheme.Light; +// } +// #else +// static AppTheme PlatformRequestedTheme() => +// AppTheme.Unspecified; +// #endif + + internal static bool VerifyHasUrlScheme(string scheme) + { + var cleansed = scheme.Replace("://", string.Empty); + var schemes = GetCFBundleURLSchemes().ToList(); + return schemes.Any(x => x != null && x.Equals(cleansed, StringComparison.InvariantCultureIgnoreCase)); + } + + internal static IEnumerable GetCFBundleURLSchemes() + { + var schemes = new List(); + + NSObject nsobj = null; + if (!NSBundle.MainBundle.InfoDictionary.TryGetValue((NSString)"CFBundleURLTypes", out nsobj)) + return schemes; + + var array = nsobj as NSArray; + + if (array == null) + return schemes; + + for (nuint i = 0; i < array.Count; i++) + { + var d = array.GetItem(i); + if (d == null || !d.Any()) + continue; + + if (!d.TryGetValue((NSString)"CFBundleURLSchemes", out nsobj)) + continue; + + var a = nsobj as NSArray; + var urls = ConvertToIEnumerable(a).Select(x => x.ToString()).ToArray(); + foreach (var url in urls) + schemes.Add(url); + } + + return schemes; + } + + static IEnumerable ConvertToIEnumerable(NSArray array) + where T : class, ObjCRuntime.INativeObject + { + for (nuint i = 0; i < array.Count; i++) + yield return array.GetItem(i); + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs new file mode 100644 index 00000000..6d1ecdcc --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -0,0 +1,9 @@ +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + private static ApplicationInfo? PlatformGetApplicationInfo() => null; + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs new file mode 100644 index 00000000..19689b96 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -0,0 +1,11 @@ +using System; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + internal static ApplicationInfo? GetAppInfo() => PlatformGetApplicationInfo(); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs new file mode 100644 index 00000000..9077e3f7 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -0,0 +1,164 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using Android.OS; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + private static OsInfo? PlatformGetOsInfo() => + new OsInfo( + GetPlatform().ToString(), + GetPlatform().ToString(), + GetVersionString() + ); + + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => + new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), + GetModel() + ); + + + //const int tabletCrossover = 600; + + static string GetModel() => Build.Model; + + static string GetManufacturer() => Build.Manufacturer; + + // private static string GetDeviceName() + // { + // // DEVICE_NAME added in System.Global in API level 25 + // // https://developer.android.com/reference/android/provider/Settings.Global#DEVICE_NAME + // var name = GetSystemSetting("device_name", true); + // if (string.IsNullOrWhiteSpace(name)) + // name = ColorSpace.Model; + // return name; + // } + + private static string GetVersionString() => Build.VERSION.Release; + + private static DevicePlatform GetPlatform() => DevicePlatform.Android; + + // static DeviceIdiom GetIdiom() + // { + // var currentIdiom = DeviceIdiom.Unknown; + // + // // first try UIModeManager + // using var uiModeManager = UiModeManager.FromContext(Essentials.Platform.AppContext); + // + // try + // { + // var uiMode = uiModeManager?.CurrentModeType ?? UiMode.TypeUndefined; + // currentIdiom = DetectIdiom(uiMode); + // } + // catch (Exception ex) + // { + // System.Diagnostics.Debug.WriteLine($"Unable to detect using UiModeManager: {ex.Message}"); + // } + // + // // then try Configuration + // if (currentIdiom == DeviceIdiom.Unknown) + // { + // var configuration = Essentials.Platform.AppContext.Resources?.Configuration; + // if (configuration != null) + // { + // var minWidth = configuration.SmallestScreenWidthDp; + // var isWide = minWidth >= tabletCrossover; + // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; + // } + // else + // { + // // start clutching at straws + // using var metrics = Essentials.Platform.AppContext.Resources?.DisplayMetrics; + // if (metrics != null) + // { + // var minSize = Math.Min(metrics.WidthPixels, metrics.HeightPixels); + // var isWide = minSize * metrics.Density >= tabletCrossover; + // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; + // } + // } + // } + // + // // hope we got it somewhere + // return currentIdiom; + // } + + // static DeviceIdiom DetectIdiom(UiMode uiMode) + // { + // if (uiMode == UiMode.TypeNormal) + // return DeviceIdiom.Unknown; + // else if (uiMode == UiMode.TypeTelevision) + // return DeviceIdiom.TV; + // else if (uiMode == UiMode.TypeDesk) + // return DeviceIdiom.Desktop; + // else if (Essentials.Platform.HasApiLevel(BuildVersionCodes.KitkatWatch) && uiMode == UiMode.TypeWatch) + // return DeviceIdiom.Watch; + // + // return DeviceIdiom.Unknown; + // } + + // static DeviceType GetDeviceType() + // { + // var isEmulator = + // (Build.Brand.StartsWith("generic", StringComparison.InvariantCulture) && Build.Device.StartsWith("generic", StringComparison.InvariantCulture)) || + // Build.Fingerprint.StartsWith("generic", StringComparison.InvariantCulture) || + // Build.Fingerprint.StartsWith("unknown", StringComparison.InvariantCulture) || + // Build.Hardware.Contains("goldfish") || + // Build.Hardware.Contains("ranchu") || + // Build.Model.Contains("google_sdk") || + // Build.Model.Contains("Emulator") || + // Build.Model.Contains("Android SDK built for x86") || + // Build.Manufacturer.Contains("Genymotion") || + // Build.Manufacturer.Contains("VS Emulator") || + // Build.Product.Contains("emulator") || + // Build.Product.Contains("google_sdk") || + // Build.Product.Contains("sdk") || + // Build.Product.Contains("sdk_google") || + // Build.Product.Contains("sdk_x86") || + // Build.Product.Contains("simulator") || + // Build.Product.Contains("vbox86p"); + // + // if (isEmulator) + // return DeviceType.Virtual; + // + // return DeviceType.Physical; + // } + + // private static string GetSystemSetting(string name, bool isGlobal = false) + // { + // if (isGlobal && Platform.HasApiLevel(BuildVersionCodes.NMr1)) + // return Settings.Global.GetString(Platform.AppContext.ContentResolver, name); + // else + // return Settings.System.GetString(Platform.AppContext.ContentResolver, name); + // } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs new file mode 100644 index 00000000..2ff0def0 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -0,0 +1,105 @@ +/* +Xamarin.Essentials + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +using System; +using System.Diagnostics; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; +#if __WATCHOS__ +using WatchKit; +using UIDevice = WatchKit.WKInterfaceDevice; +#else +using UIKit; +#endif + +using ObjCRuntime; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + + private static OsInfo? PlatformGetOsInfo() => + new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString()); + + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => + new EnvReporting.LayerModels.DeviceInfo( + GetManufacturer(), GetModel()); + static string GetModel() + { + try + { + return Platform.GetSystemLibraryProperty("hw.machine"); + } + catch (Exception) + { + Debug.WriteLine("Unable to query hardware model, returning current device model."); + } + return UIDevice.CurrentDevice.Model; + } + + static string GetManufacturer() => "Apple"; + + static string GetDeviceName() => UIDevice.CurrentDevice.Name; + + static string GetVersionString() => UIDevice.CurrentDevice.SystemVersion; + + static DevicePlatform GetPlatform() => +#if __IOS__ + DevicePlatform.iOS; +#elif __TVOS__ + DevicePlatform.tvOS; +#elif __WATCHOS__ + DevicePlatform.watchOS; +#endif + +// static DeviceIdiom GetIdiom() +// { +// #if __WATCHOS__ +// return DeviceIdiom.Watch; +// #else +// switch (UIDevice.CurrentDevice.UserInterfaceIdiom) +// { +// case UIUserInterfaceIdiom.Pad: +// return DeviceIdiom.Tablet; +// case UIUserInterfaceIdiom.Phone: +// return DeviceIdiom.Phone; +// case UIUserInterfaceIdiom.TV: +// return DeviceIdiom.TV; +// case UIUserInterfaceIdiom.CarPlay: +// case UIUserInterfaceIdiom.Unspecified: +// default: +// return DeviceIdiom.Unknown; +// } +// #endif +// } + + // static DeviceType GetDeviceType() + // => Runtime.Arch == Arch.DEVICE ? DeviceType.Physical : DeviceType.Virtual; + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs new file mode 100644 index 00000000..15a3386d --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + private static OsInfo? PlatformGetOsInfo() + { + var osName = "unknown"; + var osFamily = "unknown"; + var osVersion = "unknown"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + osName = OSPlatform.Linux.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + osName = OSPlatform.Windows.ToString(); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + osName = OSPlatform.OSX.ToString(); + } + + return new OsInfo(osFamily, osName, osVersion); + } + + private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => null; + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs new file mode 100644 index 00000000..f4e5b78a --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs @@ -0,0 +1,14 @@ +using System; +using LaunchDarkly.Sdk.Client.Internal; +using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + internal static OsInfo? GetOsInfo() => PlatformGetOsInfo(); + + internal static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? GetDeviceInfo() => PlatformGetDeviceInfo(); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs new file mode 100644 index 00000000..dc745bb6 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs @@ -0,0 +1,60 @@ +using System; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal readonly struct DevicePlatform : IEquatable + { + readonly string devicePlatform; + + public static DevicePlatform Android { get; } = new DevicePlatform(nameof(Android)); + + public static DevicePlatform iOS { get; } = new DevicePlatform(nameof(iOS)); + + public static DevicePlatform macOS { get; } = new DevicePlatform(nameof(macOS)); + + public static DevicePlatform tvOS { get; } = new DevicePlatform(nameof(tvOS)); + + public static DevicePlatform Tizen { get; } = new DevicePlatform(nameof(Tizen)); + + public static DevicePlatform UWP { get; } = new DevicePlatform(nameof(UWP)); + + public static DevicePlatform watchOS { get; } = new DevicePlatform(nameof(watchOS)); + + public static DevicePlatform Unknown { get; } = new DevicePlatform(nameof(Unknown)); + + DevicePlatform(string devicePlatform) + { + if (devicePlatform == null) + throw new ArgumentNullException(nameof(devicePlatform)); + + if (devicePlatform.Length == 0) + throw new ArgumentException(nameof(devicePlatform)); + + this.devicePlatform = devicePlatform; + } + + public static DevicePlatform Create(string devicePlatform) => + new DevicePlatform(devicePlatform); + + public bool Equals(DevicePlatform other) => + Equals(other.devicePlatform); + + internal bool Equals(string other) => + string.Equals(devicePlatform, other, StringComparison.Ordinal); + + public override bool Equals(object obj) => + obj is DevicePlatform && Equals((DevicePlatform)obj); + + public override int GetHashCode() => + devicePlatform == null ? 0 : devicePlatform.GetHashCode(); + + public override string ToString() => + devicePlatform ?? string.Empty; + + public static bool operator ==(DevicePlatform left, DevicePlatform right) => + left.Equals(right); + + public static bool operator !=(DevicePlatform left, DevicePlatform right) => + !left.Equals(right); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs index 7972425f..fad87cbe 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs @@ -87,7 +87,7 @@ internal static partial class Platform internal static bool HasApiLevel(BuildVersionCodes versionCode) => (int)Build.VERSION.SdkInt >= (int)versionCode; - + //internal static CameraManager CameraManager => // AppContext.GetSystemService(Context.CameraService) as CameraManager; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs new file mode 100644 index 00000000..df8f1b40 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs @@ -0,0 +1,62 @@ +/* +Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License + +The MIT License(MIT) +Copyright(c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Runtime.InteropServices; +using ObjCRuntime; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class Platform + { +#if __IOS__ + [DllImport(Constants.SystemLibrary, EntryPoint = "sysctlbyname")] +#else + [DllImport(Constants.libSystemLibrary, EntryPoint = "sysctlbyname")] +#endif + private static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); + + internal static string GetSystemLibraryProperty(string property) + { + var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); + SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); + + var propertyLength = Marshal.ReadInt32(lengthPtr); + + if (propertyLength == 0) + { + Marshal.FreeHGlobal(lengthPtr); + throw new InvalidOperationException("Unable to read length of property."); + } + + var valuePtr = Marshal.AllocHGlobal(propertyLength); + SysctlByName(property, valuePtr, lengthPtr, IntPtr.Zero, 0); + + var returnValue = Marshal.PtrToStringAnsi(valuePtr); + + Marshal.FreeHGlobal(lengthPtr); + Marshal.FreeHGlobal(valuePtr); + + return returnValue; + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 57d28a51..d7c9e931 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -96,6 +96,9 @@ public LdClientContext( { var logger = MakeLogger(configuration); var environmentReporter = MakeEnvironmentReporter(configuration.ApplicationInfo); + + + MobileKey = configuration.MobileKey; BaseLogger = logger; CurrentContext = currentContext; @@ -237,6 +240,9 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } + // The platform layer has second priority if properties aren't set by the Config layer. + builder.SetPlatformLayer(PlatformAttributes.Layer); + builder.SetSdkLayer(SdkAttributes.Layer); return builder.Build(); } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs new file mode 100644 index 00000000..63ec5d65 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -0,0 +1,14 @@ +using LaunchDarkly.Sdk.Client.PlatformSpecific; +using LaunchDarkly.Sdk.EnvReporting; + +namespace LaunchDarkly.Sdk.Client.Subsystems +{ + internal static class PlatformAttributes + { + internal static Layer Layer => new Layer( + AppInfo.GetAppInfo(), + DeviceInfo.GetOsInfo(), + DeviceInfo.GetDeviceInfo() + ); + } +} diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs index 84248067..791d17f1 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -5,13 +5,11 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { internal static class SdkAttributes { - internal static Layer Layer => new Layer - { - ApplicationInfo = new Props.Concrete(new ApplicationInfo( + internal static Layer Layer => new Layer(new ApplicationInfo( SdkPackage.Name, SdkPackage.Name, SdkPackage.Version, - SdkPackage.Version)) - }; + SdkPackage.Version), + null, null); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AnonymousKeyContextDecoratorTest.cs similarity index 94% rename from tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs rename to tests/LaunchDarkly.ClientSdk.Tests/Internal/AnonymousKeyContextDecoratorTest.cs index 908d732a..cdec94ae 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/ContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AnonymousKeyContextDecoratorTest.cs @@ -5,7 +5,7 @@ namespace LaunchDarkly.Sdk.Client.Internal { - public class ContextDecoratorTest : BaseTest + public class AnonymousKeyContextDecoratorTest : BaseTest { private static readonly ContextKind Kind1 = ContextKind.Of("kind1"); private static readonly ContextKind Kind2 = ContextKind.Of("kind2"); @@ -162,10 +162,10 @@ public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled Assert.NotEqual(c2TransformedA.Key, c2TransformedB.Key); } - private ContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store, bool generateAnonymousKeys = false) => - new ContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger), generateAnonymousKeys); + private AnonymousKeyContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store, bool generateAnonymousKeys = false) => + new AnonymousKeyContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger), generateAnonymousKeys); - private ContextDecorator MakeDecoratorWithoutPersistence(bool generateAnonymousKeys = false) => + private AnonymousKeyContextDecorator MakeDecoratorWithoutPersistence(bool generateAnonymousKeys = false) => MakeDecoratorWithPersistence(new NullPersistentDataStore(), generateAnonymousKeys); private void AssertContextHasBeenTransformedWithNewKey(Context original, Context transformed) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs index 1e9d6d75..32b4daaa 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs @@ -10,5 +10,33 @@ public void TestUrlSafeBase64Encode() Assert.Equal("eyJrZXkiOiJmb28-YmFyX18_In0=", Base64.UrlSafeEncode(@"{""key"":""foo>bar__?""}")); } + + [Fact] + public void TestUrlSafeSha256Hash() + { + Assert.Equal("something", Base64.UrlSafeSha256Hash("OhYeah?HashThis!!!")); + } + + // func testSha256base64() throws { + // let input = "hashThis!" + // let expectedOutput = "sfXg3HewbCAVNQLJzPZhnFKntWYvN0nAYyUWFGy24dQ=" + // let output = Util.sha256base64(input) + // XCTAssertEqual(output, expectedOutput) + // } + // + // func testSha256base64UrlEncoding() throws { + // let input = "OhYeah?HashThis!!!" // hash is KzDwVRpvTuf//jfMK27M4OMpIRTecNcJoaffvAEi+as= and it has a + and a / + // let expectedOutput = "KzDwVRpvTuf__jfMK27M4OMpIRTecNcJoaffvAEi-as=" + // let output = Util.sha256(input).base64UrlEncodedString + // XCTAssertEqual(output, expectedOutput) + // } + // + // @Test + // public void testUrlSafeBase64Hash() { + // String input = "hashThis!"; + // String expectedOutput = "sfXg3HewbCAVNQLJzPZhnFKntWYvN0nAYyUWFGy24dQ="; + // String output = LDUtil.urlSafeBase64Hash(input); + // Assert.assertEquals(expectedOutput, output); + // } } } From 6c1074b1f3f7786e7204ec839fd23b23e495777a Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 5 Oct 2023 10:14:27 -0500 Subject: [PATCH 454/499] bugfix: correcting auto env context key generation. Now it works. --- .../Internal/AutoEnvContextDecorator.cs | 52 ++- src/LaunchDarkly.ClientSdk/LdClient.cs | 7 +- .../Subsystems/LdClientContext.cs | 2 - .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 2 +- .../ConfigurationTest.cs | 12 +- .../Integrations/TestDataTest.cs | 2 +- .../Integrations/TestDataWithClientTest.cs | 2 +- .../Internal/AutoEnvContextDecoratorTest.cs | 420 ++++++++++++++++++ .../DataSources/FeatureFlagRequestorTests.cs | 4 +- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 2 +- 10 files changed, 469 insertions(+), 36 deletions(-) create mode 100644 tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 5528def5..39e8dd30 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -9,7 +9,7 @@ namespace LaunchDarkly.Sdk.Client.Internal /// /// TODO /// - internal class AutoEnvContextModifier + internal class AutoEnvContextDecorator { private const string LD_APPLICATION_KIND = "ld_application"; private const string LD_DEVICE_KIND = "ld_device"; @@ -25,9 +25,9 @@ internal class AutoEnvContextModifier private const string ENV_ATTRIBUTES_VERSION = "envAttributesVersion"; private const string SPEC_VERSION = "1.0"; - private readonly PersistentDataStoreWrapper persistentData; - private readonly IEnvironmentReporter environmentReporter; - private readonly Logger logger; + private readonly PersistentDataStoreWrapper _persistentData; + private readonly IEnvironmentReporter _environmentReporter; + private readonly Logger _logger; /// /// TODO @@ -35,14 +35,14 @@ internal class AutoEnvContextModifier /// /// /// - public AutoEnvContextModifier( + public AutoEnvContextDecorator( PersistentDataStoreWrapper persistentData, IEnvironmentReporter environmentReporter, Logger logger) { - this.persistentData = persistentData; - this.environmentReporter = environmentReporter; - this.logger = logger; + _persistentData = persistentData; + _environmentReporter = environmentReporter; + _logger = logger; } public Context DecorateContext(Context context) @@ -59,7 +59,7 @@ public Context DecorateContext(Context context) } else { - logger.Warn("Unable to automatically add environment attributes for kind:{0}. {1} already exists.", + _logger.Warn("Unable to automatically add environment attributes for kind:{0}. {1} already exists.", recipe.Kind, recipe.Kind); } } @@ -99,10 +99,10 @@ private List MakeRecipeList() var applicationCallables = new Dictionary> { { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_ID, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationId) }, - { ATTR_NAME, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationName) }, - { ATTR_VERSION, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationVersion) }, - { ATTR_VERSION_NAME, () => LdValue.Of(environmentReporter.ApplicationInfo.ApplicationVersionName) } + { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationId) }, + { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationName) }, + { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersion) }, + { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) } }; // TODO: missing locale in environment reporter implementation @@ -112,14 +112,14 @@ private List MakeRecipeList() var deviceCallables = new Dictionary> { { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_MANUFACTURER, () => LdValue.Of(environmentReporter.DeviceInfo.Manufacturer) }, - { ATTR_MODEL, () => LdValue.Of(environmentReporter.DeviceInfo.Model) }, + { ATTR_MANUFACTURER, () => LdValue.Of(_environmentReporter.DeviceInfo.Manufacturer) }, + { ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo.Model) }, { ATTR_OS, () => LdValue.BuildObject() - .Add(ATTR_FAMILY, environmentReporter.OsInfo.Family) - .Add(ATTR_NAME, environmentReporter.OsInfo.Name) - .Add(ATTR_VERSION, environmentReporter.OsInfo.Version) + .Add(ATTR_FAMILY, _environmentReporter.OsInfo.Family) + .Add(ATTR_NAME, _environmentReporter.OsInfo.Name) + .Add(ATTR_VERSION, _environmentReporter.OsInfo.Version) .Build() } }; @@ -129,16 +129,28 @@ private List MakeRecipeList() new ContextRecipe( ldApplicationKind, () => Base64.UrlSafeSha256Hash( - environmentReporter.ApplicationInfo.ApplicationId ?? "" + _environmentReporter.ApplicationInfo.ApplicationId ?? "" ), applicationCallables ), new ContextRecipe( ldDeviceKind, - () => persistentData.GetGeneratedContextKey(ldDeviceKind), + () => GetOrCreateAutoContextKey(_persistentData, ldDeviceKind), deviceCallables ) }; } + + // TODO: commonize this with duplicate implementation in AnonymousKeyContextDecorator + private string GetOrCreateAutoContextKey(PersistentDataStoreWrapper store, ContextKind contextKind) + { + var uniqueId = store.GetGeneratedContextKey(contextKind); + if (uniqueId is null) + { + uniqueId = Guid.NewGuid().ToString(); + store.SetGeneratedContextKey(contextKind, uniqueId); + } + return uniqueId; + } } } diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 6d9584ea..9ab34807 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -60,7 +60,7 @@ public sealed class LdClient : ILdClient readonly IFlagTracker _flagTracker; readonly TaskExecutor _taskExecutor; readonly AnonymousKeyContextDecorator _anonymousKeyAnonymousKeyContextDecorator; - private readonly AnonymousKeyContextDecorator _autoEnvContextDecorator; + private readonly AutoEnvContextDecorator _autoEnvContextDecorator; private readonly Logger _log; @@ -163,7 +163,10 @@ public sealed class LdClient : ILdClient ); _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); - _context = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); + var decoratedContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); + + _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, _clientContext.EnvironmentReporter, _log); + _context = _autoEnvContextDecorator.DecorateContext(decoratedContext); // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index d7c9e931..3fb8ea9c 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -97,8 +97,6 @@ public LdClientContext( var logger = MakeLogger(configuration); var environmentReporter = MakeEnvironmentReporter(configuration.ApplicationInfo); - - MobileKey = configuration.MobileKey; BaseLogger = logger; CurrentContext = currentContext; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index f4490fb5..2f58b18f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -43,7 +43,7 @@ public void Dispose() // needed, protects against accidental interaction with external services and also makes it easier to // see which properties are important in a test. protected ConfigurationBuilder BasicConfig() => - Configuration.Builder(BasicMobileKey) + Configuration.Builder(BasicMobileKey, true) .BackgroundModeManager(new MockBackgroundModeManager()) .ConnectivityStateManager(new MockConnectivityStateManager(true)) .DataSource(new MockDataSource().AsSingletonFactory()) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index eb95f354..a1c9cba0 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -10,7 +10,7 @@ namespace LaunchDarkly.Sdk.Client public class ConfigurationTest : BaseTest { private readonly BuilderBehavior.BuildTester _tester = - BuilderBehavior.For(() => Configuration.Builder(mobileKey), b => b.Build()) + BuilderBehavior.For(() => Configuration.Builder(mobileKey, true), b => b.Build()) .WithCopyConstructor(c => Configuration.Builder(c)); const string mobileKey = "any-key"; @@ -20,14 +20,14 @@ public ConfigurationTest(ITestOutputHelper testOutput) : base(testOutput) { } [Fact] public void DefaultSetsKey() { - var config = Configuration.Default(mobileKey); + var config = Configuration.Default(mobileKey, true); Assert.Equal(mobileKey, config.MobileKey); } [Fact] public void BuilderSetsKey() { - var config = Configuration.Builder(mobileKey).Build(); + var config = Configuration.Builder(mobileKey, true).Build(); Assert.Equal(mobileKey, config.MobileKey); } @@ -99,7 +99,7 @@ public void Logging() public void LoggingAdapterShortcut() { var adapter = Logs.ToWriter(Console.Out); - var config = Configuration.Builder("key").Logging(adapter).Build(); + var config = Configuration.Builder("key", true).Logging(adapter).Build(); var logConfig = config.LoggingConfigurationBuilder.CreateLoggingConfiguration(); Assert.Same(adapter, logConfig.LogAdapter); } @@ -130,13 +130,13 @@ public void Persistence() [Fact] public void MobileKeyCannotBeNull() { - Assert.Throws(() => Configuration.Default(null)); + Assert.Throws(() => Configuration.Default(null, true)); } [Fact] public void MobileKeyCannotBeEmpty() { - Assert.Throws(() => Configuration.Default("")); + Assert.Throws(() => Configuration.Default("", true)); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index b6bd55fa..cf56b801 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -21,7 +21,7 @@ public class TestDataTest : BaseTest public TestDataTest(ITestOutputHelper testOutput) : base(testOutput) { - _context = new LdClientContext(Configuration.Builder("key").Logging(testLogging).Build(), _initialUser); + _context = new LdClientContext(Configuration.Builder("key", true).Logging(testLogging).Build(), _initialUser); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs index aeeb9ad4..4d96964e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs @@ -12,7 +12,7 @@ public class TestDataWithClientTest : BaseTest public TestDataWithClientTest(ITestOutputHelper testOutput) : base(testOutput) { - _config = Configuration.Builder("mobile-key") + _config = Configuration.Builder("mobile-key", true) .DataSource(_td) .Events(Components.NoEvents) .Build(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs new file mode 100644 index 00000000..18481c65 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -0,0 +1,420 @@ +using System.Linq; +using LaunchDarkly.Logging; +using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.Client.Subsystems; +using LaunchDarkly.Sdk.EnvReporting; +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Internal +{ + public class AutoEnvContextDecoratorTest : BaseTest + { + // private static readonly ContextKind Kind1 = ContextKind.Of("kind1"); + // private static readonly ContextKind Kind2 = ContextKind.Of("kind2"); + // + // [Fact] + // public void SingleKindNonAnonymousContextIsUnchanged() + // { + // var context = Context.Builder("key1").Name("name").Build(); + // + // AssertHelpers.ContextsEqual(context, + // MakeDecoratorWithoutPersistence().DecorateContext(context)); + // } + // + // [Fact] + // public void SingleKindAnonymousContextIsUnchangedIfConfigOptionIsNotSet() + // { + // var context = Context.Builder("key1").Anonymous(true).Name("name").Build(); + // + // AssertHelpers.ContextsEqual(context, + // MakeDecoratorWithoutPersistence().DecorateContext(context)); + // } + // + // [Fact] + // public void SingleKindAnonymousContextGetsGeneratedKeyIfConfigOptionIsSet() + // { + // var context = TestUtil.BuildAutoContext().Name("name").Build(); + // + // var transformed = MakeDecoratorWithoutPersistence(true).DecorateContext(context); + // + // AssertContextHasBeenTransformedWithNewKey(context, transformed); + // } + // + // [Fact] + // public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() + // { + // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); + // var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); + // + // var multiContext = Context.NewMulti(c1, c2); + // + // AssertHelpers.ContextsEqual(multiContext, + // MakeDecoratorWithoutPersistence().DecorateContext(multiContext)); + // } + // + // [Fact] + // public void MultiKindContextGetsGeneratedKeyForIndividualContext() + // { + // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); + // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); + // var multiContext = Context.NewMulti(c1, c2); + // var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); + // + // Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), + // transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); + // + // transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); + // AssertHelpers.ContextsEqual(c1, c1Transformed); + // + // transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); + // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + // + // AssertHelpers.ContextsEqual(Context.NewMulti(c1, c2Transformed), transformedMulti); + // } + // + // [Fact] + // public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() + // { + // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); + // var multiContext = Context.NewMulti(c1, c2); + // var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); + // + // Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), + // transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); + // + // transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); + // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + // + // transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); + // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + // + // Assert.NotEqual(c1Transformed.Key, c2Transformed.Key); + // + // AssertHelpers.ContextsEqual(Context.NewMulti(c1Transformed, c2Transformed), transformedMulti); + // } + // + // [Fact] + // public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() + // { + // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); + // var multiContext = Context.NewMulti(c1, c2); + // + // var store = new MockPersistentDataStore(); + // + // var decorator1 = MakeDecoratorWithPersistence(store, true); + // + // var transformedMultiA = decorator1.DecorateContext(multiContext); + // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); + // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); + // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + // + // var decorator2 = MakeDecoratorWithPersistence(store, true); + // + // var transformedMultiB = decorator2.DecorateContext(multiContext); + // AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); + // } + // + // [Fact] + // public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDisabled() + // { + // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); + // var multiContext = Context.NewMulti(c1, c2); + // + // var store = new MockPersistentDataStore(); + // + // var decorator = MakeDecoratorWithoutPersistence(true); + // + // var transformedMultiA = decorator.DecorateContext(multiContext); + // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); + // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); + // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); + // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); + // + // var transformedMultiB = decorator.DecorateContext(multiContext); + // AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); + // } + // + // [Fact] + // public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled() + // { + // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); + // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); + // var multiContext = Context.NewMulti(c1, c2); + // + // var decorator1 = MakeDecoratorWithoutPersistence(true); + // + // var transformedMultiA = decorator1.DecorateContext(multiContext); + // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1TransformedA); + // AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedA); + // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2TransformedA); + // AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedA); + // + // var decorator2 = MakeDecoratorWithoutPersistence(true); + // + // var transformedMultiB = decorator2.DecorateContext(multiContext); + // transformedMultiB.TryGetContextByKind(c1.Kind, out var c1TransformedB); + // AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedB); + // Assert.NotEqual(c1TransformedA.Key, c1TransformedB.Key); + // transformedMultiB.TryGetContextByKind(c2.Kind, out var c2TransformedB); + // AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedB); + // Assert.NotEqual(c2TransformedA.Key, c2TransformedB.Key); + // } + // + // private AutoEnvContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store) + // { + // var environmentReporter = new EnvironmentReporterBuilder().Build(); + // return new AutoEnvContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger), environmentReporter, testLogger); + // } + // + // private AutoEnvContextDecorator MakeDecoratorWithoutPersistence() => + // MakeDecoratorWithPersistence(new NullPersistentDataStore()); + // + // private void AssertContextHasBeenTransformedWithNewKey(Context original, Context transformed) + // { + // Assert.NotEqual(original.Key, transformed.Key); + // AssertHelpers.ContextsEqual(Context.BuilderFromContext(original).Key(transformed.Key).Build(), + // transformed); + // } + // + // public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() + // { + // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); + // var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); + // + // var multiContext = Context.NewMulti(c1, c2); + // + // AssertHelpers.ContextsEqual(multiContext, + // MakeDecoratorWithoutPersistence().DecorateContext(multiContext)); + // } + // + // [Fact] + // public void AdheresToSchemaTest() + // { + // var store = new MockPersistentDataStore(); + // var decoratorUnderTest = MakeDecoratorWithPersistence(store); + // + // Context input = Context.Builder("aKey").Kind(ContextKind.Of("aKind")) + // .Set("dontOverwriteMeBro", "really bro").Build(); + // LDContext output = underTest.ModifyContext(input); + // + // // Create the expected context after the code runs + // // because there will be persistence side effects + // ContextKind applicationKind = ContextKind.Of(AutoEnvContextDecorator.LD_APPLICATION_KIND); + // string expectedApplicationKey = LDUtil.UrlSafeBase64Hash(reporter.GetApplicationInfo().GetApplicationId()); + // LDContext expectedAppContext = LDContext.Builder(applicationKind, expectedApplicationKey) + // .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) + // .Set(AutoEnvContextDecorator.ATTR_ID, LDPackageConsts.SDK_NAME) + // .Set(AutoEnvContextDecorator.ATTR_NAME, LDPackageConsts.SDK_NAME) + // .Set(AutoEnvContextDecorator.ATTR_VERSION, BuildConfig.VERSION_NAME) + // .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, BuildConfig.VERSION_NAME) + // .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") + // .Build(); + // + // ContextKind deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); + // LDContext expectedDeviceContext = LDContext.Builder(deviceKind, wrapper.GetOrGenerateContextKey(deviceKind)) + // .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) + // .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") + // .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") + // .Set(AutoEnvContextDecorator.ATTR_OS, new LdValue.ObjectBuilder() + // .Put(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") + // .Put(AutoEnvContextDecorator.ATTR_NAME, "unknown") + // .Put(AutoEnvContextDecorator.ATTR_VERSION, "unknown") + // .Build()) + // .Build(); + // + // LDContext expectedOutput = LDContext.MultiBuilder().Add(input).Add(expectedAppContext).Add(expectedDeviceContext).Build(); + // + // Assert.AreEqual(expectedOutput, output); + // } + + } +} + + + // /** + // * Requirement 1.2.2.1 - Schema adherence + // * Requirement 1.2.2.3 - Adding all attributes + // * Requirement 1.2.2.5 - Schema version in _meta + // * Requirement 1.2.2.7 - Adding all context kinds + // */ + // @Test + // public void adheresToSchemaTest() { + // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); + // IEnvironmentReporter reporter = new EnvironmentReporterBuilder().build(); + // AutoEnvContextModifier underTest = new AutoEnvContextModifier( + // wrapper, + // reporter, + // LDLogger.none() + // ); + // + // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey") + // .set("dontOverwriteMeBro", "really bro").build(); + // LDContext output = underTest.modifyContext(input); + // + // // it is important that we create this expected context after the code runs because there + // // will be persistence side effects + // ContextKind applicationKind = ContextKind.of(AutoEnvContextModifier.LD_APPLICATION_KIND); + // String expectedApplicationKey = LDUtil.urlSafeBase64Hash(reporter.getApplicationInfo().getApplicationId()); + // LDContext expectedAppContext = LDContext.builder(applicationKind, expectedApplicationKey) + // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) + // .set(AutoEnvContextModifier.ATTR_ID, LDPackageConsts.SDK_NAME) + // .set(AutoEnvContextModifier.ATTR_NAME, LDPackageConsts.SDK_NAME) + // .set(AutoEnvContextModifier.ATTR_VERSION, BuildConfig.VERSION_NAME) + // .set(AutoEnvContextModifier.ATTR_VERSION_NAME, BuildConfig.VERSION_NAME) + // .set(AutoEnvContextModifier.ATTR_LOCALE, "unknown") + // .build(); + // + // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); + // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) + // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) + // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") + // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") + // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() + // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") + // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") + // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") + // .build()) + // .build(); + // + // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedAppContext).add(expectedDeviceContext).build(); + // + // Assert.assertEquals(expectedOutput, output); + // } + // + // /** + // * Requirement 1.2.2.6 - Don't add kind if already exists + // * Requirement 1.2.5.1 - Doesn't change customer provided data + // * Requirement 1.2.7.1 - Log warning when kind already exists + // */ + // @Test + // public void doesNotOverwriteCustomerDataTest() { + // + // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); + // AutoEnvContextModifier underTest = new AutoEnvContextModifier( + // wrapper, + // new EnvironmentReporterBuilder().build(), + // logging.logger + // ); + // + // LDContext input = LDContext.builder(ContextKind.of("ld_application"), "aKey") + // .set("dontOverwriteMeBro", "really bro").build(); + // LDContext output = underTest.modifyContext(input); + // + // // it is important that we create this expected context after the code runs because there + // // will be persistence side effects + // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); + // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) + // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) + // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") + // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") + // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() + // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") + // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") + // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") + // .build()) + // .build(); + // + // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedDeviceContext).build(); + // + // Assert.assertEquals(expectedOutput, output); + // logging.assertWarnLogged("Unable to automatically add environment attributes for " + + // "kind:ld_application. ld_application already exists."); + // } + // + // /** + // * Requirement 1.2.5.1 - Doesn't change customer provided data + // */ + // @Test + // public void doesNotOverwriteCustomerDataMultiContextTest() { + // + // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); + // AutoEnvContextModifier underTest = new AutoEnvContextModifier( + // wrapper, + // new EnvironmentReporterBuilder().build(), + // LDLogger.none() + // ); + // + // LDContext input1 = LDContext.builder(ContextKind.of("ld_application"), "aKey") + // .set("dontOverwriteMeBro", "really bro").build(); + // LDContext input2 = LDContext.builder(ContextKind.of("ld_device"), "anotherKey") + // .set("AndDontOverwriteThisEither", "bro").build(); + // LDContext multiContextInput = LDContext.multiBuilder().add(input1).add(input2).build(); + // LDContext output = underTest.modifyContext(multiContextInput); + // + // // input and output should be the same + // Assert.assertEquals(multiContextInput, output); + // } + // + // /** + // * Requirement 1.2.6.3 - Generated keys are consistent + // */ + // @Test + // public void generatesConsistentKeysAcrossMultipleCalls() { + // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); + // AutoEnvContextModifier underTest = new AutoEnvContextModifier( + // wrapper, + // new EnvironmentReporterBuilder().build(), + // LDLogger.none() + // ); + // + // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey") + // .set("dontOverwriteMeBro", "really bro").build(); + // + // LDContext output1 = underTest.modifyContext(input); + // String key1 = output1.getIndividualContext("ld_application").getKey(); + // + // LDContext output2 = underTest.modifyContext(input); + // String key2 = output2.getIndividualContext("ld_application").getKey(); + // + // Assert.assertEquals(key1, key2); + // } + // + // /** + // * Test that when only myID is supplied, hash is hash(myID:) and not hash(myId:null) + // */ + // @Test + // public void generatedApplicationKeyWithVersionMissing() { + // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); + // ApplicationInfo info = new ApplicationInfo("myID", null, null, null); + // EnvironmentReporterBuilder b = new EnvironmentReporterBuilder(); + // b.setApplicationInfo(info); + // IEnvironmentReporter reporter = b.build(); + // AutoEnvContextModifier underTest = new AutoEnvContextModifier( + // wrapper, + // reporter, + // LDLogger.none() + // ); + // + // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey").build(); + // LDContext output = underTest.modifyContext(input); + // + // // it is important that we create this expected context after the code runs because there + // // will be persistence side effects + // ContextKind applicationKind = ContextKind.of(AutoEnvContextModifier.LD_APPLICATION_KIND); + // String expectedApplicationKey = LDUtil.urlSafeBase64Hash(reporter.getApplicationInfo().getApplicationId()); + // + // LDContext expectedAppContext = LDContext.builder(applicationKind, expectedApplicationKey) + // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) + // .set(AutoEnvContextModifier.ATTR_ID, "myID") + // .set(AutoEnvContextModifier.ATTR_LOCALE, "unknown") + // .build(); + // + // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); + // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) + // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) + // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") + // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") + // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() + // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") + // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") + // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") + // .build()) + // .build(); + // + // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedAppContext).add(expectedDeviceContext).build(); + // Assert.assertEquals(expectedOutput, output); + // } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 9bfa878f..7904571f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -46,7 +46,7 @@ string expectedQuery { var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); - var config = Configuration.Default(_mobileKey); + var config = Configuration.Default(_mobileKey, true); using (var requestor = new FeatureFlagRequestor( baseUri, @@ -89,7 +89,7 @@ string expectedQuery { var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); - var config = Configuration.Builder(_mobileKey) + var config = Configuration.Builder(_mobileKey, true) .Http(Components.HttpConfiguration().UseReport(true)) .Build(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index e332b024..9e4488bb 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -20,7 +20,7 @@ public static class TestUtil private static ThreadLocal InClientLock = new ThreadLocal(); - public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key"), Context.New("userkey")); + public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key", true), Context.New("userkey")); public static Context Base64ContextFromUrlPath(string path, string pathPrefix) { From c0569e5b41161ef0cc2a13e44b66a1c03e512b36 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 5 Oct 2023 16:57:49 -0500 Subject: [PATCH 455/499] chore: propagating common changes related to Env Props and Layers --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 6 +++--- src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 70dd46ad..fc9d5eef 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -28,7 +28,7 @@ true snupkg LaunchDarkly.Sdk.Client - + 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 @@ -37,9 +37,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs index 84248067..791d17f1 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -5,13 +5,11 @@ namespace LaunchDarkly.Sdk.Client.Subsystems { internal static class SdkAttributes { - internal static Layer Layer => new Layer - { - ApplicationInfo = new Props.Concrete(new ApplicationInfo( + internal static Layer Layer => new Layer(new ApplicationInfo( SdkPackage.Name, SdkPackage.Name, SdkPackage.Version, - SdkPackage.Version)) - }; + SdkPackage.Version), + null, null); } } From a65228528c2010b5937572f63866da60a15e4071 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 6 Oct 2023 10:16:39 -0700 Subject: [PATCH 456/499] chore: Update codeowners to dotnet team. (#195) --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 8b137891..3c1c6e1d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ - +# Repository Maintainers +* @launchdarkly/team-sdk-net From 5ce2e8bf689dddcf14a7a92e95e30824f25ce9d2 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Fri, 6 Oct 2023 12:19:46 -0500 Subject: [PATCH 457/499] Existing contract tests working, but autoenvattributes contract tests still failing --- contract-tests/Representations.cs | 1 + contract-tests/SdkClientEntity.cs | 2 +- contract-tests/TestService.cs | 5 +++-- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 6 +++--- src/LaunchDarkly.ClientSdk/LdClient.cs | 9 +++++++-- src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs | 3 +++ 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/contract-tests/Representations.cs b/contract-tests/Representations.cs index 56199496..1836c4dd 100644 --- a/contract-tests/Representations.cs +++ b/contract-tests/Representations.cs @@ -77,6 +77,7 @@ public class SdkClientSideParams public Context? InitialContext { get; set; } public User InitialUser { get; set; } public bool? UseReport { get; set; } + public bool? IncludeEnvironmentAttributes { get; set; } } public class CommandParams diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index fbff2825..d1577a1c 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -266,7 +266,7 @@ private ContextBuildResponse DoContextConvert(ContextConvertParams p) private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) { - var builder = Configuration.Builder(sdkParams.Credential, true); + var builder = Configuration.Builder(sdkParams.Credential, sdkParams.ClientSide.IncludeEnvironmentAttributes ?? false); builder.Logging(Components.Logging(logAdapter).BaseLoggerName(tag + ".SDK")); diff --git a/contract-tests/TestService.cs b/contract-tests/TestService.cs index c485fc8b..59940839 100644 --- a/contract-tests/TestService.cs +++ b/contract-tests/TestService.cs @@ -37,7 +37,8 @@ public class Webapp "singleton", "strongly-typed", "user-type", - "tags" + "tags", + "auto-env-attributes" }; public readonly Handler Handler; @@ -54,7 +55,7 @@ public Webapp(EventWaitHandle quitSignal) _quitSignal = quitSignal; _version = LdClient.Version.ToString(); - + var service = new SimpleJsonService(); Handler = service.Handler; diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 200d9f11..7cdb0a2c 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -28,7 +28,7 @@ true snupkg LaunchDarkly.Sdk.Client - + @@ -39,9 +39,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 9ab34807..57a37b26 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -165,8 +165,13 @@ public sealed class LdClient : ILdClient _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); var decoratedContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); - _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, _clientContext.EnvironmentReporter, _log); - _context = _autoEnvContextDecorator.DecorateContext(decoratedContext); + if (configuration.AutoEnvAttributes) + { + _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, _clientContext.EnvironmentReporter, _log); + decoratedContext = _autoEnvContextDecorator.DecorateContext(decoratedContext); + } + + _context = decoratedContext; // If we had cached data for the new context, set the current in-memory flag data state to use // that data, so that any Variation calls made before Identify has completed will use the diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index 3fb8ea9c..891543b2 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -238,6 +238,9 @@ internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuil builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } + // TODO: this platform layer being set needs to depend on the configuration so that when the + // customer opts out of auto env attributes, we do not report it through the env reporter + // The platform layer has second priority if properties aren't set by the Config layer. builder.SetPlatformLayer(PlatformAttributes.Layer); From 29853a5b57466e4b05e31601f24051dcd5d1c81a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 6 Oct 2023 10:44:23 -0700 Subject: [PATCH 458/499] fix: android platform layer - os name should include Build.Version.SdkInt --- .../PlatformSpecific/DeviceInfo.android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs index 9077e3f7..24df9aa8 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -37,7 +37,7 @@ internal static partial class DeviceInfo private static OsInfo? PlatformGetOsInfo() => new OsInfo( GetPlatform().ToString(), - GetPlatform().ToString(), + GetPlatform().ToString()+Build.VERSION.SdkInt, GetVersionString() ); From 6c46cd7991b1d8eb0c50289336515079925bed62 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 9 Oct 2023 10:01:45 -0500 Subject: [PATCH 459/499] ci:removing android hardware acceleration to fix ci --- .circleci/scripts/macos-install-android-sdk.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/scripts/macos-install-android-sdk.sh b/.circleci/scripts/macos-install-android-sdk.sh index b1a4d198..854f47ff 100755 --- a/.circleci/scripts/macos-install-android-sdk.sh +++ b/.circleci/scripts/macos-install-android-sdk.sh @@ -58,7 +58,6 @@ unzip android-sdk.zip mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest sdkmanager_args="platform-tools emulator" -sdkmanager_args="$sdkmanager_args extras;intel;Hardware_Accelerated_Execution_Manager" sdkmanager_args="$sdkmanager_args build-tools;$ANDROID_BUILD_TOOLS_VERSION" for apiver in "$@"; do sdkmanager_args="$sdkmanager_args platforms;android-$apiver" From 6c4e93c3ace1c114a07078ea8d9398fcfca9de78 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 9 Oct 2023 10:16:45 -0700 Subject: [PATCH 460/499] align iOS model with Swift SDK --- .../PlatformSpecific/DeviceInfo.ios.cs | 70 +------------------ 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs index 2ff0def0..e36d520c 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -1,31 +1,3 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - using System; using System.Diagnostics; using LaunchDarkly.Sdk.EnvReporting; @@ -50,23 +22,11 @@ internal static partial class DeviceInfo private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => new EnvReporting.LayerModels.DeviceInfo( GetManufacturer(), GetModel()); - static string GetModel() - { - try - { - return Platform.GetSystemLibraryProperty("hw.machine"); - } - catch (Exception) - { - Debug.WriteLine("Unable to query hardware model, returning current device model."); - } - return UIDevice.CurrentDevice.Model; - } - - static string GetManufacturer() => "Apple"; - static string GetDeviceName() => UIDevice.CurrentDevice.Name; + static string GetModel() => UIDevice.CurrentDevice.Model; + static string GetManufacturer() => "Apple"; + static string GetVersionString() => UIDevice.CurrentDevice.SystemVersion; static DevicePlatform GetPlatform() => @@ -77,29 +37,5 @@ static DevicePlatform GetPlatform() => #elif __WATCHOS__ DevicePlatform.watchOS; #endif - -// static DeviceIdiom GetIdiom() -// { -// #if __WATCHOS__ -// return DeviceIdiom.Watch; -// #else -// switch (UIDevice.CurrentDevice.UserInterfaceIdiom) -// { -// case UIUserInterfaceIdiom.Pad: -// return DeviceIdiom.Tablet; -// case UIUserInterfaceIdiom.Phone: -// return DeviceIdiom.Phone; -// case UIUserInterfaceIdiom.TV: -// return DeviceIdiom.TV; -// case UIUserInterfaceIdiom.CarPlay: -// case UIUserInterfaceIdiom.Unspecified: -// default: -// return DeviceIdiom.Unknown; -// } -// #endif -// } - - // static DeviceType GetDeviceType() - // => Runtime.Arch == Arch.DEVICE ? DeviceType.Physical : DeviceType.Virtual; } } From cf6731bf3f52027dadc11e6f028a3773d03fe75f Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 9 Oct 2023 14:24:17 -0700 Subject: [PATCH 461/499] return 'unknown' on all ld_device props on netstandard target --- .../DeviceInfo.netstandard.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs index 15a3386d..80f12528 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -1,30 +1,10 @@ -using System.Runtime.InteropServices; -using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.EnvReporting.LayerModels; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static OsInfo? PlatformGetOsInfo() - { - var osName = "unknown"; - var osFamily = "unknown"; - var osVersion = "unknown"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - osName = OSPlatform.Linux.ToString(); - } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - osName = OSPlatform.Windows.ToString(); - } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - osName = OSPlatform.OSX.ToString(); - } - - return new OsInfo(osFamily, osName, osVersion); - } + private static OsInfo? PlatformGetOsInfo() => null; private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => null; } From 213fb5a368de42f158babf8f475271f3cb4315f1 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 10 Oct 2023 14:37:27 -0500 Subject: [PATCH 462/499] Adds unit tests for AutoEnvContextDecorator and completes implementation to pass contract tests for Auto Env Attributes. --- .../Internal/AutoEnvContextDecorator.cs | 26 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 34 +- .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 2 +- .../ConfigurationTest.cs | 6 +- .../Integrations/TestDataTest.cs | 3 +- .../Integrations/TestDataWithClientTest.cs | 2 +- .../Internal/AutoEnvContextDecoratorTest.cs | 528 ++++-------------- .../Internal/Base64Test.cs | 26 +- 8 files changed, 162 insertions(+), 465 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 39e8dd30..5cd6e19c 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -11,19 +11,19 @@ namespace LaunchDarkly.Sdk.Client.Internal /// internal class AutoEnvContextDecorator { - private const string LD_APPLICATION_KIND = "ld_application"; - private const string LD_DEVICE_KIND = "ld_device"; - private const string ATTR_ID = "id"; - private const string ATTR_NAME = "name"; - private const string ATTR_VERSION = "version"; - private const string ATTR_VERSION_NAME = "versionName"; - private const string ATTR_MANUFACTURER = "manufacturer"; - private const string ATTR_MODEL = "model"; - private const string ATTR_LOCALE = "locale"; - private const string ATTR_OS = "os"; - private const string ATTR_FAMILY = "family"; - private const string ENV_ATTRIBUTES_VERSION = "envAttributesVersion"; - private const string SPEC_VERSION = "1.0"; + internal const string LD_APPLICATION_KIND = "ld_application"; + internal const string LD_DEVICE_KIND = "ld_device"; + internal const string ATTR_ID = "id"; + internal const string ATTR_NAME = "name"; + internal const string ATTR_VERSION = "version"; + internal const string ATTR_VERSION_NAME = "versionName"; + internal const string ATTR_MANUFACTURER = "manufacturer"; + internal const string ATTR_MODEL = "model"; + internal const string ATTR_LOCALE = "locale"; + internal const string ATTR_OS = "os"; + internal const string ATTR_FAMILY = "family"; + internal const string ENV_ATTRIBUTES_VERSION = "envAttributesVersion"; + internal const string SPEC_VERSION = "1.0"; private readonly PersistentDataStoreWrapper _persistentData; private readonly IEnvironmentReporter _environmentReporter; diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 57a37b26..19b27fba 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -141,7 +141,7 @@ public sealed class LdClient : ILdClient LdClient(Configuration configuration, Context initialContext, TimeSpan startWaitTime) { _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); - var baseContext = new LdClientContext(configuration, initialContext, this); + var baseContext = new LdClientContext(_config, initialContext, this); var diagnosticStore = _config.DiagnosticOptOut ? null : new ClientDiagnosticStore(baseContext, _config, startWaitTime); @@ -154,18 +154,18 @@ public sealed class LdClient : ILdClient _log.Info("Starting LaunchDarkly Client {0}", Version); - var persistenceConfiguration = (configuration.PersistenceConfigurationBuilder ?? Components.Persistence()) + var persistenceConfiguration = (_config.PersistenceConfigurationBuilder ?? Components.Persistence()) .Build(_clientContext); _dataStore = new FlagDataManager( - configuration.MobileKey, + _config.MobileKey, persistenceConfiguration, _log.SubLogger(LogNames.DataStoreSubLog) ); - _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, configuration.GenerateAnonymousKeys); + _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, _config.GenerateAnonymousKeys); var decoratedContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); - if (configuration.AutoEnvAttributes) + if (_config.AutoEnvAttributes) { _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, _clientContext.EnvironmentReporter, _log); decoratedContext = _autoEnvContextDecorator.DecorateContext(decoratedContext); @@ -185,7 +185,7 @@ public sealed class LdClient : ILdClient var dataSourceUpdateSink = new DataSourceUpdateSinkImpl( _dataStore, - configuration.Offline, + _config.Offline, _taskExecutor, _log.SubLogger(LogNames.DataSourceSubLog) ); @@ -194,16 +194,16 @@ public sealed class LdClient : ILdClient _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); _flagTracker = new FlagTrackerImpl(dataSourceUpdateSink); - var dataSourceFactory = configuration.DataSource ?? Components.StreamingDataSource(); + var dataSourceFactory = _config.DataSource ?? Components.StreamingDataSource(); - _connectivityStateManager = Factory.CreateConnectivityStateManager(configuration); + _connectivityStateManager = Factory.CreateConnectivityStateManager(_config); var isConnected = _connectivityStateManager.IsConnected; - diagnosticDisabler?.SetDisabled(!isConnected || configuration.Offline); + diagnosticDisabler?.SetDisabled(!isConnected || _config.Offline); - _eventProcessor = (configuration.Events ?? Components.SendEvents()) + _eventProcessor = (_config.Events ?? Components.SendEvents()) .Build(_clientContext); - _eventProcessor.SetOffline(configuration.Offline || !isConnected); + _eventProcessor.SetOffline(_config.Offline || !isConnected); _connectionManager = new ConnectionManager( _clientContext, @@ -211,13 +211,13 @@ public sealed class LdClient : ILdClient _dataSourceUpdateSink, _eventProcessor, diagnosticDisabler, - configuration.EnableBackgroundUpdating, + _config.EnableBackgroundUpdating, _context, _log ); - _connectionManager.SetForceOffline(configuration.Offline); + _connectionManager.SetForceOffline(_config.Offline); _connectionManager.SetNetworkEnabled(isConnected); - if (configuration.Offline) + if (_config.Offline) { _log.Info("Starting LaunchDarkly client in offline mode"); } @@ -230,7 +230,7 @@ public sealed class LdClient : ILdClient // Send an initial identify event, but only if we weren't explicitly set to be offline - if (!configuration.Offline) + if (!_config.Offline) { _eventProcessor.RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { @@ -699,6 +699,10 @@ public bool Identify(Context context, TimeSpan maxWaitTime) public async Task IdentifyAsync(Context context) { Context newContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(context); + if (_config.AutoEnvAttributes) + { + newContext = _autoEnvContextDecorator.DecorateContext(newContext); + } Context oldContext = newContext; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 2f58b18f..50e22da5 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -43,7 +43,7 @@ public void Dispose() // needed, protects against accidental interaction with external services and also makes it easier to // see which properties are important in a test. protected ConfigurationBuilder BasicConfig() => - Configuration.Builder(BasicMobileKey, true) + Configuration.Builder(BasicMobileKey, false) .BackgroundModeManager(new MockBackgroundModeManager()) .ConnectivityStateManager(new MockConnectivityStateManager(true)) .DataSource(new MockDataSource().AsSingletonFactory()) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index a1c9cba0..44c1f410 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -10,7 +10,7 @@ namespace LaunchDarkly.Sdk.Client public class ConfigurationTest : BaseTest { private readonly BuilderBehavior.BuildTester _tester = - BuilderBehavior.For(() => Configuration.Builder(mobileKey, true), b => b.Build()) + BuilderBehavior.For(() => Configuration.Builder(mobileKey, false), b => b.Build()) .WithCopyConstructor(c => Configuration.Builder(c)); const string mobileKey = "any-key"; @@ -27,7 +27,7 @@ public void DefaultSetsKey() [Fact] public void BuilderSetsKey() { - var config = Configuration.Builder(mobileKey, true).Build(); + var config = Configuration.Builder(mobileKey, false).Build(); Assert.Equal(mobileKey, config.MobileKey); } @@ -99,7 +99,7 @@ public void Logging() public void LoggingAdapterShortcut() { var adapter = Logs.ToWriter(Console.Out); - var config = Configuration.Builder("key", true).Logging(adapter).Build(); + var config = Configuration.Builder("key", false).Logging(adapter).Build(); var logConfig = config.LoggingConfigurationBuilder.CreateLoggingConfiguration(); Assert.Same(adapter, logConfig.LogAdapter); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index cf56b801..58808721 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -21,7 +21,8 @@ public class TestDataTest : BaseTest public TestDataTest(ITestOutputHelper testOutput) : base(testOutput) { - _context = new LdClientContext(Configuration.Builder("key", true).Logging(testLogging).Build(), _initialUser); + _context = new LdClientContext(Configuration.Builder("key", false) + .Logging(testLogging).Build(), _initialUser); } [Fact] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs index 4d96964e..9f609f1f 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs @@ -12,7 +12,7 @@ public class TestDataWithClientTest : BaseTest public TestDataWithClientTest(ITestOutputHelper testOutput) : base(testOutput) { - _config = Configuration.Builder("mobile-key", true) + _config = Configuration.Builder("mobile-key", false) .DataSource(_td) .Events(Components.NoEvents) .Build(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 18481c65..9ecc887a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -1,5 +1,3 @@ -using System.Linq; -using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.EnvReporting; @@ -9,412 +7,126 @@ namespace LaunchDarkly.Sdk.Client.Internal { public class AutoEnvContextDecoratorTest : BaseTest { - // private static readonly ContextKind Kind1 = ContextKind.Of("kind1"); - // private static readonly ContextKind Kind2 = ContextKind.Of("kind2"); - // - // [Fact] - // public void SingleKindNonAnonymousContextIsUnchanged() - // { - // var context = Context.Builder("key1").Name("name").Build(); - // - // AssertHelpers.ContextsEqual(context, - // MakeDecoratorWithoutPersistence().DecorateContext(context)); - // } - // - // [Fact] - // public void SingleKindAnonymousContextIsUnchangedIfConfigOptionIsNotSet() - // { - // var context = Context.Builder("key1").Anonymous(true).Name("name").Build(); - // - // AssertHelpers.ContextsEqual(context, - // MakeDecoratorWithoutPersistence().DecorateContext(context)); - // } - // - // [Fact] - // public void SingleKindAnonymousContextGetsGeneratedKeyIfConfigOptionIsSet() - // { - // var context = TestUtil.BuildAutoContext().Name("name").Build(); - // - // var transformed = MakeDecoratorWithoutPersistence(true).DecorateContext(context); - // - // AssertContextHasBeenTransformedWithNewKey(context, transformed); - // } - // - // [Fact] - // public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() - // { - // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - // var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); - // - // var multiContext = Context.NewMulti(c1, c2); - // - // AssertHelpers.ContextsEqual(multiContext, - // MakeDecoratorWithoutPersistence().DecorateContext(multiContext)); - // } - // - // [Fact] - // public void MultiKindContextGetsGeneratedKeyForIndividualContext() - // { - // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); - // var multiContext = Context.NewMulti(c1, c2); - // var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); - // - // Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), - // transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); - // - // transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); - // AssertHelpers.ContextsEqual(c1, c1Transformed); - // - // transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); - // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); - // - // AssertHelpers.ContextsEqual(Context.NewMulti(c1, c2Transformed), transformedMulti); - // } - // - // [Fact] - // public void MultiKindContextGetsSeparateGeneratedKeyForEachKind() - // { - // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); - // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); - // var multiContext = Context.NewMulti(c1, c2); - // var transformedMulti = MakeDecoratorWithoutPersistence(true).DecorateContext(multiContext); - // - // Assert.Equal(multiContext.MultiKindContexts.Select(c => c.Kind).ToList(), - // transformedMulti.MultiKindContexts.Select(c => c.Kind).ToList()); - // - // transformedMulti.TryGetContextByKind(c1.Kind, out var c1Transformed); - // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); - // - // transformedMulti.TryGetContextByKind(c2.Kind, out var c2Transformed); - // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); - // - // Assert.NotEqual(c1Transformed.Key, c2Transformed.Key); - // - // AssertHelpers.ContextsEqual(Context.NewMulti(c1Transformed, c2Transformed), transformedMulti); - // } - // - // [Fact] - // public void GeneratedKeysPersistPerKindIfPersistentStorageIsEnabled() - // { - // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); - // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); - // var multiContext = Context.NewMulti(c1, c2); - // - // var store = new MockPersistentDataStore(); - // - // var decorator1 = MakeDecoratorWithPersistence(store, true); - // - // var transformedMultiA = decorator1.DecorateContext(multiContext); - // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); - // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); - // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); - // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); - // - // var decorator2 = MakeDecoratorWithPersistence(store, true); - // - // var transformedMultiB = decorator2.DecorateContext(multiContext); - // AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); - // } - // - // [Fact] - // public void GeneratedKeysAreReusedDuringLifetimeOfSdkEvenIfPersistentStorageIsDisabled() - // { - // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); - // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); - // var multiContext = Context.NewMulti(c1, c2); - // - // var store = new MockPersistentDataStore(); - // - // var decorator = MakeDecoratorWithoutPersistence(true); - // - // var transformedMultiA = decorator.DecorateContext(multiContext); - // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1Transformed); - // AssertContextHasBeenTransformedWithNewKey(c1, c1Transformed); - // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2Transformed); - // AssertContextHasBeenTransformedWithNewKey(c2, c2Transformed); - // - // var transformedMultiB = decorator.DecorateContext(multiContext); - // AssertHelpers.ContextsEqual(transformedMultiA, transformedMultiB); - // } - // - // [Fact] - // public void GeneratedKeysAreNotReusedAcrossRestartsIfPersistentStorageIsDisabled() - // { - // var c1 = TestUtil.BuildAutoContext().Kind(Kind1).Anonymous(true).Name("name1").Build(); - // var c2 = TestUtil.BuildAutoContext().Kind(Kind2).Anonymous(true).Name("name2").Build(); - // var multiContext = Context.NewMulti(c1, c2); - // - // var decorator1 = MakeDecoratorWithoutPersistence(true); - // - // var transformedMultiA = decorator1.DecorateContext(multiContext); - // transformedMultiA.TryGetContextByKind(c1.Kind, out var c1TransformedA); - // AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedA); - // transformedMultiA.TryGetContextByKind(c2.Kind, out var c2TransformedA); - // AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedA); - // - // var decorator2 = MakeDecoratorWithoutPersistence(true); - // - // var transformedMultiB = decorator2.DecorateContext(multiContext); - // transformedMultiB.TryGetContextByKind(c1.Kind, out var c1TransformedB); - // AssertContextHasBeenTransformedWithNewKey(c1, c1TransformedB); - // Assert.NotEqual(c1TransformedA.Key, c1TransformedB.Key); - // transformedMultiB.TryGetContextByKind(c2.Kind, out var c2TransformedB); - // AssertContextHasBeenTransformedWithNewKey(c2, c2TransformedB); - // Assert.NotEqual(c2TransformedA.Key, c2TransformedB.Key); - // } - // - // private AutoEnvContextDecorator MakeDecoratorWithPersistence(IPersistentDataStore store) - // { - // var environmentReporter = new EnvironmentReporterBuilder().Build(); - // return new AutoEnvContextDecorator(new PersistentDataStoreWrapper(store, BasicMobileKey, testLogger), environmentReporter, testLogger); - // } - // - // private AutoEnvContextDecorator MakeDecoratorWithoutPersistence() => - // MakeDecoratorWithPersistence(new NullPersistentDataStore()); - // - // private void AssertContextHasBeenTransformedWithNewKey(Context original, Context transformed) - // { - // Assert.NotEqual(original.Key, transformed.Key); - // AssertHelpers.ContextsEqual(Context.BuilderFromContext(original).Key(transformed.Key).Build(), - // transformed); - // } - // - // public void MultiKindContextIsUnchangedIfNoIndividualContextsNeedGeneratedKey() - // { - // var c1 = Context.Builder("key1").Kind(Kind1).Name("name1").Build(); - // var c2 = Context.Builder("key2").Kind(Kind2).Anonymous(true).Name("name2").Build(); - // - // var multiContext = Context.NewMulti(c1, c2); - // - // AssertHelpers.ContextsEqual(multiContext, - // MakeDecoratorWithoutPersistence().DecorateContext(multiContext)); - // } - // - // [Fact] - // public void AdheresToSchemaTest() - // { - // var store = new MockPersistentDataStore(); - // var decoratorUnderTest = MakeDecoratorWithPersistence(store); - // - // Context input = Context.Builder("aKey").Kind(ContextKind.Of("aKind")) - // .Set("dontOverwriteMeBro", "really bro").Build(); - // LDContext output = underTest.ModifyContext(input); - // - // // Create the expected context after the code runs - // // because there will be persistence side effects - // ContextKind applicationKind = ContextKind.Of(AutoEnvContextDecorator.LD_APPLICATION_KIND); - // string expectedApplicationKey = LDUtil.UrlSafeBase64Hash(reporter.GetApplicationInfo().GetApplicationId()); - // LDContext expectedAppContext = LDContext.Builder(applicationKind, expectedApplicationKey) - // .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - // .Set(AutoEnvContextDecorator.ATTR_ID, LDPackageConsts.SDK_NAME) - // .Set(AutoEnvContextDecorator.ATTR_NAME, LDPackageConsts.SDK_NAME) - // .Set(AutoEnvContextDecorator.ATTR_VERSION, BuildConfig.VERSION_NAME) - // .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, BuildConfig.VERSION_NAME) - // .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") - // .Build(); - // - // ContextKind deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); - // LDContext expectedDeviceContext = LDContext.Builder(deviceKind, wrapper.GetOrGenerateContextKey(deviceKind)) - // .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - // .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") - // .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") - // .Set(AutoEnvContextDecorator.ATTR_OS, new LdValue.ObjectBuilder() - // .Put(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") - // .Put(AutoEnvContextDecorator.ATTR_NAME, "unknown") - // .Put(AutoEnvContextDecorator.ATTR_VERSION, "unknown") - // .Build()) - // .Build(); - // - // LDContext expectedOutput = LDContext.MultiBuilder().Add(input).Add(expectedAppContext).Add(expectedDeviceContext).Build(); - // - // Assert.AreEqual(expectedOutput, output); - // } + [Fact] + public void AdheresToSchemaTest() + { + var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer).Build(); + var store = MakeMockDataStoreWrapper(); + var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); - } -} + var input = Context.Builder("aKey").Kind(ContextKind.Of("aKind")) + .Set("dontOverwriteMeBro", "really bro").Build(); + var output = decoratorUnderTest.DecorateContext(input); + + // Create the expected context after the code runs + // because there will be persistence side effects + var applicationKind = ContextKind.Of(AutoEnvContextDecorator.LD_APPLICATION_KIND); + var expectedApplicationKey = Base64.UrlSafeSha256Hash(envReporter.ApplicationInfo.ApplicationId); + var expectedAppContext = Context.Builder(applicationKind, expectedApplicationKey) + .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) + .Set(AutoEnvContextDecorator.ATTR_ID, SdkPackage.Name) + .Set(AutoEnvContextDecorator.ATTR_NAME, SdkPackage.Name) + .Set(AutoEnvContextDecorator.ATTR_VERSION, SdkPackage.Version) + .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, SdkPackage.Version) + .Build(); + + //TODO: Set Locale - .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") + + var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); + var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) + .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) + .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") + .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") + .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() + .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") + .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") + .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") + .Build()) + .Build(); + + var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedAppContext).Add(expectedDeviceContext) + .Build(); + + Assert.Equal(expectedOutput, output); + } + + [Fact] + public void DoesNotOverwriteCustomerDataTest() + { + var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer).Build(); + var store = MakeMockDataStoreWrapper(); + var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); + + var input = Context.Builder(ContextKind.Of("ld_application"), "aKey") + .Set("dontOverwriteMeBro", "really bro").Build(); + var output = decoratorUnderTest.DecorateContext(input); + + // Create the expected device context after the code runs because of persistence side effects + var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); + var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) + .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) + .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") + .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") + .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() + .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") + .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") + .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") + .Build()) + .Build(); + + var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedDeviceContext).Build(); + Assert.Equal(expectedOutput, output); + } - // /** - // * Requirement 1.2.2.1 - Schema adherence - // * Requirement 1.2.2.3 - Adding all attributes - // * Requirement 1.2.2.5 - Schema version in _meta - // * Requirement 1.2.2.7 - Adding all context kinds - // */ - // @Test - // public void adheresToSchemaTest() { - // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); - // IEnvironmentReporter reporter = new EnvironmentReporterBuilder().build(); - // AutoEnvContextModifier underTest = new AutoEnvContextModifier( - // wrapper, - // reporter, - // LDLogger.none() - // ); - // - // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey") - // .set("dontOverwriteMeBro", "really bro").build(); - // LDContext output = underTest.modifyContext(input); - // - // // it is important that we create this expected context after the code runs because there - // // will be persistence side effects - // ContextKind applicationKind = ContextKind.of(AutoEnvContextModifier.LD_APPLICATION_KIND); - // String expectedApplicationKey = LDUtil.urlSafeBase64Hash(reporter.getApplicationInfo().getApplicationId()); - // LDContext expectedAppContext = LDContext.builder(applicationKind, expectedApplicationKey) - // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) - // .set(AutoEnvContextModifier.ATTR_ID, LDPackageConsts.SDK_NAME) - // .set(AutoEnvContextModifier.ATTR_NAME, LDPackageConsts.SDK_NAME) - // .set(AutoEnvContextModifier.ATTR_VERSION, BuildConfig.VERSION_NAME) - // .set(AutoEnvContextModifier.ATTR_VERSION_NAME, BuildConfig.VERSION_NAME) - // .set(AutoEnvContextModifier.ATTR_LOCALE, "unknown") - // .build(); - // - // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); - // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) - // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) - // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") - // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") - // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() - // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") - // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") - // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") - // .build()) - // .build(); - // - // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedAppContext).add(expectedDeviceContext).build(); - // - // Assert.assertEquals(expectedOutput, output); - // } - // - // /** - // * Requirement 1.2.2.6 - Don't add kind if already exists - // * Requirement 1.2.5.1 - Doesn't change customer provided data - // * Requirement 1.2.7.1 - Log warning when kind already exists - // */ - // @Test - // public void doesNotOverwriteCustomerDataTest() { - // - // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); - // AutoEnvContextModifier underTest = new AutoEnvContextModifier( - // wrapper, - // new EnvironmentReporterBuilder().build(), - // logging.logger - // ); - // - // LDContext input = LDContext.builder(ContextKind.of("ld_application"), "aKey") - // .set("dontOverwriteMeBro", "really bro").build(); - // LDContext output = underTest.modifyContext(input); - // - // // it is important that we create this expected context after the code runs because there - // // will be persistence side effects - // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); - // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) - // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) - // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") - // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") - // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() - // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") - // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") - // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") - // .build()) - // .build(); - // - // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedDeviceContext).build(); - // - // Assert.assertEquals(expectedOutput, output); - // logging.assertWarnLogged("Unable to automatically add environment attributes for " + - // "kind:ld_application. ld_application already exists."); - // } - // - // /** - // * Requirement 1.2.5.1 - Doesn't change customer provided data - // */ - // @Test - // public void doesNotOverwriteCustomerDataMultiContextTest() { - // - // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); - // AutoEnvContextModifier underTest = new AutoEnvContextModifier( - // wrapper, - // new EnvironmentReporterBuilder().build(), - // LDLogger.none() - // ); - // - // LDContext input1 = LDContext.builder(ContextKind.of("ld_application"), "aKey") - // .set("dontOverwriteMeBro", "really bro").build(); - // LDContext input2 = LDContext.builder(ContextKind.of("ld_device"), "anotherKey") - // .set("AndDontOverwriteThisEither", "bro").build(); - // LDContext multiContextInput = LDContext.multiBuilder().add(input1).add(input2).build(); - // LDContext output = underTest.modifyContext(multiContextInput); - // - // // input and output should be the same - // Assert.assertEquals(multiContextInput, output); - // } - // - // /** - // * Requirement 1.2.6.3 - Generated keys are consistent - // */ - // @Test - // public void generatesConsistentKeysAcrossMultipleCalls() { - // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); - // AutoEnvContextModifier underTest = new AutoEnvContextModifier( - // wrapper, - // new EnvironmentReporterBuilder().build(), - // LDLogger.none() - // ); - // - // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey") - // .set("dontOverwriteMeBro", "really bro").build(); - // - // LDContext output1 = underTest.modifyContext(input); - // String key1 = output1.getIndividualContext("ld_application").getKey(); - // - // LDContext output2 = underTest.modifyContext(input); - // String key2 = output2.getIndividualContext("ld_application").getKey(); - // - // Assert.assertEquals(key1, key2); - // } - // - // /** - // * Test that when only myID is supplied, hash is hash(myID:) and not hash(myId:null) - // */ - // @Test - // public void generatedApplicationKeyWithVersionMissing() { - // PersistentDataStoreWrapper wrapper = TestUtil.makeSimplePersistentDataStoreWrapper(); - // ApplicationInfo info = new ApplicationInfo("myID", null, null, null); - // EnvironmentReporterBuilder b = new EnvironmentReporterBuilder(); - // b.setApplicationInfo(info); - // IEnvironmentReporter reporter = b.build(); - // AutoEnvContextModifier underTest = new AutoEnvContextModifier( - // wrapper, - // reporter, - // LDLogger.none() - // ); - // - // LDContext input = LDContext.builder(ContextKind.of("aKind"), "aKey").build(); - // LDContext output = underTest.modifyContext(input); - // - // // it is important that we create this expected context after the code runs because there - // // will be persistence side effects - // ContextKind applicationKind = ContextKind.of(AutoEnvContextModifier.LD_APPLICATION_KIND); - // String expectedApplicationKey = LDUtil.urlSafeBase64Hash(reporter.getApplicationInfo().getApplicationId()); - // - // LDContext expectedAppContext = LDContext.builder(applicationKind, expectedApplicationKey) - // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) - // .set(AutoEnvContextModifier.ATTR_ID, "myID") - // .set(AutoEnvContextModifier.ATTR_LOCALE, "unknown") - // .build(); - // - // ContextKind deviceKind = ContextKind.of(AutoEnvContextModifier.LD_DEVICE_KIND); - // LDContext expectedDeviceContext = LDContext.builder(deviceKind, wrapper.getOrGenerateContextKey(deviceKind)) - // .set(AutoEnvContextModifier.ENV_ATTRIBUTES_VERSION, AutoEnvContextModifier.SPEC_VERSION) - // .set(AutoEnvContextModifier.ATTR_MANUFACTURER, "unknown") - // .set(AutoEnvContextModifier.ATTR_MODEL, "unknown") - // .set(AutoEnvContextModifier.ATTR_OS, new ObjectBuilder() - // .put(AutoEnvContextModifier.ATTR_FAMILY, "unknown") - // .put(AutoEnvContextModifier.ATTR_NAME, "unknown") - // .put(AutoEnvContextModifier.ATTR_VERSION, "unknown") - // .build()) - // .build(); - // - // LDContext expectedOutput = LDContext.multiBuilder().add(input).add(expectedAppContext).add(expectedDeviceContext).build(); - // Assert.assertEquals(expectedOutput, output); - // } + [Fact] + public void DoesNotOverwriteCustomerDataMultiContextTest() + { + var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer).Build(); + var store = MakeMockDataStoreWrapper(); + var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); + + var input1 = Context.Builder(ContextKind.Of("ld_application"), "aKey") + .Set("dontOverwriteMeBro", "really bro").Build(); + var input2 = Context.Builder(ContextKind.Of("ld_device"), "anotherKey") + .Set("AndDontOverwriteThisEither", "bro").Build(); + var multiContextInput = Context.MultiBuilder().Add(input1).Add(input2).Build(); + var output = decoratorUnderTest.DecorateContext(multiContextInput); + + // input and output should be the same + Assert.Equal(multiContextInput, output); + } + + [Fact] + public void GeneratesConsistentKeysAcrossMultipleCalls() + { + var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer).Build(); + var store = MakeMockDataStoreWrapper(); + var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); + + var input = Context.Builder(ContextKind.Of("aKind"), "aKey") + .Set("dontOverwriteMeBro", "really bro").Build(); + + var output1 = decoratorUnderTest.DecorateContext(input); + output1.TryGetContextByKind(ContextKind.Of("ld_application"), out var appContext1); + var key1 = appContext1.Key; + + var output2 = decoratorUnderTest.DecorateContext(input); + output2.TryGetContextByKind(ContextKind.Of("ld_application"), out var appContext2); + var key2 = appContext2.Key; + + Assert.Equal(key1, key2); + } + + private PersistentDataStoreWrapper MakeMockDataStoreWrapper() + { + return new PersistentDataStoreWrapper(new MockPersistentDataStore(), BasicMobileKey, testLogger); + } + + private AutoEnvContextDecorator MakeDecoratorWithPersistence(PersistentDataStoreWrapper store, + IEnvironmentReporter reporter) + { + return new AutoEnvContextDecorator(store, reporter, testLogger); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs index 32b4daaa..a0e55dcd 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/Base64Test.cs @@ -14,29 +14,9 @@ public void TestUrlSafeBase64Encode() [Fact] public void TestUrlSafeSha256Hash() { - Assert.Equal("something", Base64.UrlSafeSha256Hash("OhYeah?HashThis!!!")); + var input = "OhYeah?HashThis!!!"; // hash is KzDwVRpvTuf//jfMK27M4OMpIRTecNcJoaffvAEi+as= and it has a + and a / + var expectedOutput = "KzDwVRpvTuf__jfMK27M4OMpIRTecNcJoaffvAEi-as="; + Assert.Equal(expectedOutput, Base64.UrlSafeSha256Hash(input)); } - - // func testSha256base64() throws { - // let input = "hashThis!" - // let expectedOutput = "sfXg3HewbCAVNQLJzPZhnFKntWYvN0nAYyUWFGy24dQ=" - // let output = Util.sha256base64(input) - // XCTAssertEqual(output, expectedOutput) - // } - // - // func testSha256base64UrlEncoding() throws { - // let input = "OhYeah?HashThis!!!" // hash is KzDwVRpvTuf//jfMK27M4OMpIRTecNcJoaffvAEi+as= and it has a + and a / - // let expectedOutput = "KzDwVRpvTuf__jfMK27M4OMpIRTecNcJoaffvAEi-as=" - // let output = Util.sha256(input).base64UrlEncodedString - // XCTAssertEqual(output, expectedOutput) - // } - // - // @Test - // public void testUrlSafeBase64Hash() { - // String input = "hashThis!"; - // String expectedOutput = "sfXg3HewbCAVNQLJzPZhnFKntWYvN0nAYyUWFGy24dQ="; - // String output = LDUtil.urlSafeBase64Hash(input); - // Assert.assertEquals(expectedOutput, output); - // } } } From b02c8be921b57626062837ac32bab88d0b2a86be Mon Sep 17 00:00:00 2001 From: Todd Anderson <127344469+tanderson-ld@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:10:28 -0500 Subject: [PATCH 463/499] Updating common and internal deps to use production tagging functionality. --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index fc9d5eef..647b1b05 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + From 407723e1af32181349c1cb0bf7f852302d89cfd9 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 10 Oct 2023 16:52:20 -0500 Subject: [PATCH 464/499] Adds AutoEnvAttributes enum for API clarity --- contract-tests/SdkClientEntity.cs | 6 +- src/LaunchDarkly.ClientSdk/Configuration.cs | 18 ++-- .../ConfigurationBuilder.cs | 29 ++++- src/LaunchDarkly.ClientSdk/LdClient.cs | 102 ++++++++++++------ .../LaunchDarkly.ClientSdk.Tests/BaseTest.cs | 2 +- .../ConfigurationTest.cs | 20 ++-- .../Integrations/TestDataTest.cs | 2 +- .../Integrations/TestDataWithClientTest.cs | 2 +- .../DataSources/FeatureFlagRequestorTests.cs | 40 +++---- .../LaunchDarkly.ClientSdk.Tests/TestUtil.cs | 13 +-- 10 files changed, 155 insertions(+), 79 deletions(-) diff --git a/contract-tests/SdkClientEntity.cs b/contract-tests/SdkClientEntity.cs index d1577a1c..ec54e4b2 100644 --- a/contract-tests/SdkClientEntity.cs +++ b/contract-tests/SdkClientEntity.cs @@ -266,7 +266,11 @@ private ContextBuildResponse DoContextConvert(ContextConvertParams p) private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) { - var builder = Configuration.Builder(sdkParams.Credential, sdkParams.ClientSide.IncludeEnvironmentAttributes ?? false); + var autoEnvAttributes = (sdkParams.ClientSide.IncludeEnvironmentAttributes ?? false) + ? ConfigurationBuilder.AutoEnvAttributes.Enabled + : ConfigurationBuilder.AutoEnvAttributes.Disabled; + + var builder = Configuration.Builder(sdkParams.Credential, autoEnvAttributes); builder.Logging(Components.Logging(logAdapter).BaseLoggerName(tag + ".SDK")); diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index bb0edbe2..5946e16e 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Immutable; using LaunchDarkly.Sdk.Client.Integrations; using LaunchDarkly.Sdk.Client.Interfaces; using LaunchDarkly.Sdk.Client.Internal.Interfaces; +using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.Client.Subsystems; namespace LaunchDarkly.Sdk.Client @@ -11,8 +13,8 @@ namespace LaunchDarkly.Sdk.Client /// /// /// Instances of are immutable once created. They can be created with the factory method - /// , or using a builder pattern with - /// or . + /// , or using a builder pattern + /// with or . /// public sealed class Configuration { @@ -93,13 +95,13 @@ public sealed class Configuration /// /// HTTP configuration properties for the SDK. /// - /// + /// public HttpConfigurationBuilder HttpConfigurationBuilder { get; } /// /// Logging configuration properties for the SDK. /// - /// + /// public LoggingConfigurationBuilder LoggingConfigurationBuilder { get; } /// @@ -119,7 +121,7 @@ public sealed class Configuration /// /// Persistent storage configuration properties for the SDK. /// - /// + /// public PersistenceConfigurationBuilder PersistenceConfigurationBuilder { get; } /// @@ -134,7 +136,7 @@ public sealed class Configuration /// the SDK key for your LaunchDarkly environment /// TODOo /// a instance - public static Configuration Default(string mobileKey, bool autoEnvAttributes) + public static Configuration Default(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes) { return Builder(mobileKey, autoEnvAttributes).Build(); } @@ -159,12 +161,14 @@ public static Configuration Default(string mobileKey, bool autoEnvAttributes) /// the mobile SDK key for your LaunchDarkly environment /// TODOo /// a builder object - public static ConfigurationBuilder Builder(string mobileKey, bool autoEnvAttributes) + public static ConfigurationBuilder Builder(string mobileKey, + ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes) { if (String.IsNullOrEmpty(mobileKey)) { throw new ArgumentOutOfRangeException(nameof(mobileKey), "key is required"); } + return new ConfigurationBuilder(mobileKey, autoEnvAttributes); } diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 4763b9eb..7113d562 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -25,6 +25,31 @@ namespace LaunchDarkly.Sdk.Client /// public sealed class ConfigurationBuilder { + /// + /// Enable / disable options for Auto Environment Attributes functionality. When enabled, the SDK will automatically + /// provide data about the mobile environment where the application is running. This data makes it simpler to target + /// your mobile customers based on application name or version, or on device characteristics including manufacturer, + /// model, operating system, locale, and so on. We recommend enabling this when you configure the SDK. See TKTK + /// for more documentation. + /// For example, consider a “dark mode” feature being added to an app. Versions 10 through 14 contain early, + /// incomplete versions of the feature. These versions are available to all customers, but the “dark mode” feature is only + /// enabled for testers. With version 15, the feature is considered complete. With Auto Environment Attributes enabled, + /// you can use targeting rules to enable "dark mode" for all customers who are using version 15 or greater, and ensure + /// that customers on previous versions don't use the earlier, unfinished version of the feature. + /// + public enum AutoEnvAttributes + { + /// + /// Enables the Auto EnvironmentAttributes functionality. + /// + Enabled, + + /// + /// Disables the Auto EnvironmentAttributes functionality. + /// + Disabled + } + // This exists so that we can distinguish between leaving the HttpMessageHandler property unchanged // and explicitly setting it to null. If the property value is the exact same instance as this, we // will replace it with a platform-specific implementation. @@ -49,10 +74,10 @@ public sealed class ConfigurationBuilder internal IBackgroundModeManager _backgroundModeManager; internal IConnectivityStateManager _connectivityStateManager; - internal ConfigurationBuilder(string mobileKey, bool autoEnvAttributes) + internal ConfigurationBuilder(string mobileKey, AutoEnvAttributes autoEnvAttributes) { _mobileKey = mobileKey; - _autoEnvAttributes = autoEnvAttributes; + _autoEnvAttributes = autoEnvAttributes == AutoEnvAttributes.Enabled; // map enum to boolean } internal ConfigurationBuilder(Configuration copyFrom) diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 19b27fba..95cd0681 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -136,17 +136,19 @@ public sealed class LdClient : ILdClient // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) - LdClient() { } + LdClient() + { + } LdClient(Configuration configuration, Context initialContext, TimeSpan startWaitTime) { _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); var baseContext = new LdClientContext(_config, initialContext, this); - var diagnosticStore = _config.DiagnosticOptOut ? null : - new ClientDiagnosticStore(baseContext, _config, startWaitTime); - var diagnosticDisabler = _config.DiagnosticOptOut ? null : - new DiagnosticDisablerImpl(); + var diagnosticStore = _config.DiagnosticOptOut + ? null + : new ClientDiagnosticStore(baseContext, _config, startWaitTime); + var diagnosticDisabler = _config.DiagnosticOptOut ? null : new DiagnosticDisablerImpl(); _clientContext = baseContext.WithDiagnostics(diagnosticDisabler, diagnosticStore); _log = _clientContext.BaseLogger; @@ -160,14 +162,16 @@ public sealed class LdClient : ILdClient _config.MobileKey, persistenceConfiguration, _log.SubLogger(LogNames.DataStoreSubLog) - ); + ); - _anonymousKeyAnonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, _config.GenerateAnonymousKeys); + _anonymousKeyAnonymousKeyContextDecorator = + new AnonymousKeyContextDecorator(_dataStore.PersistentStore, _config.GenerateAnonymousKeys); var decoratedContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); if (_config.AutoEnvAttributes) { - _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, _clientContext.EnvironmentReporter, _log); + _autoEnvContextDecorator = new AutoEnvContextDecorator(_dataStore.PersistentStore, + _clientContext.EnvironmentReporter, _log); decoratedContext = _autoEnvContextDecorator.DecorateContext(decoratedContext); } @@ -180,7 +184,8 @@ public sealed class LdClient : ILdClient if (cachedData != null) { _log.Debug("Cached flag data is available for this context"); - _dataStore.Init(_context, cachedData.Value, false); // false means "don't rewrite the flags to persistent storage" + _dataStore.Init(_context, cachedData.Value, + false); // false means "don't rewrite the flags to persistent storage" } var dataSourceUpdateSink = new DataSourceUpdateSinkImpl( @@ -188,7 +193,7 @@ public sealed class LdClient : ILdClient _config.Offline, _taskExecutor, _log.SubLogger(LogNames.DataSourceSubLog) - ); + ); _dataSourceUpdateSink = dataSourceUpdateSink; _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdateSink); @@ -214,7 +219,7 @@ public sealed class LdClient : ILdClient _config.EnableBackgroundUpdating, _context, _log - ); + ); _connectionManager.SetForceOffline(_config.Offline); _connectionManager.SetNetworkEnabled(isConnected); if (_config.Offline) @@ -225,7 +230,7 @@ public sealed class LdClient : ILdClient _connectivityStateManager.ConnectionChanged += networkAvailable => { _log.Debug("Setting online to {0} due to a connectivity change event", networkAvailable); - _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result + _ = _connectionManager.SetNetworkEnabled(networkAvailable); // do not await the result }; // Send an initial identify event, but only if we weren't explicitly set to be offline @@ -287,7 +292,8 @@ async Task StartAsync() /// /// /// - public static LdClient Init(string mobileKey, bool autoEnvAttributes, Context initialContext, TimeSpan maxWaitTime) + public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, + Context initialContext, TimeSpan maxWaitTime) { var config = Configuration.Default(mobileKey, autoEnvAttributes); @@ -310,7 +316,8 @@ public static LdClient Init(string mobileKey, bool autoEnvAttributes, Context in /// /// /// - public static LdClient Init(string mobileKey, bool autoEnvAttributes, User initialUser, TimeSpan maxWaitTime) => + public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, + User initialUser, TimeSpan maxWaitTime) => Init(mobileKey, autoEnvAttributes, Context.FromUser(initialUser), maxWaitTime); /// @@ -337,7 +344,8 @@ public static LdClient Init(string mobileKey, bool autoEnvAttributes, User initi /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// a Task that resolves to the singleton LdClient instance - public static async Task InitAsync(string mobileKey, bool autoEnvAttributes, Context initialContext) + public static async Task InitAsync(string mobileKey, + ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, Context initialContext) { var config = Configuration.Default(mobileKey, autoEnvAttributes); @@ -356,7 +364,8 @@ public static async Task InitAsync(string mobileKey, bool autoEnvAttri /// TODOo /// the initial user attributes /// a Task that resolves to the singleton LdClient instance - public static Task InitAsync(string mobileKey, bool autoEnvAttributes, User initialUser) => + public static Task InitAsync(string mobileKey, + ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, User initialUser) => InitAsync(mobileKey, autoEnvAttributes, Context.FromUser(initialUser)); /// @@ -503,61 +512,71 @@ public async Task SetOfflineAsync(bool value) /// public bool BoolVariation(string key, bool defaultValue = false) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, + _eventFactoryDefault).Value; } /// public EvaluationDetail BoolVariationDetail(string key, bool defaultValue = false) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Bool, true, + _eventFactoryWithReasons); } /// public string StringVariation(string key, string defaultValue) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, + _eventFactoryDefault).Value; } /// public EvaluationDetail StringVariationDetail(string key, string defaultValue) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.String, true, + _eventFactoryWithReasons); } /// public float FloatVariation(string key, float defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, + _eventFactoryDefault).Value; } /// public EvaluationDetail FloatVariationDetail(string key, float defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Float, true, + _eventFactoryWithReasons); } /// public double DoubleVariation(string key, double defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, + _eventFactoryDefault).Value; } /// public EvaluationDetail DoubleVariationDetail(string key, double defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Double, true, + _eventFactoryWithReasons); } /// public int IntVariation(string key, int defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, _eventFactoryDefault).Value; + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, _eventFactoryDefault) + .Value; } /// public EvaluationDetail IntVariationDetail(string key, int defaultValue = 0) { - return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, _eventFactoryWithReasons); + return VariationInternal(key, LdValue.Of(defaultValue), LdValue.Convert.Int, true, + _eventFactoryWithReasons); } /// @@ -572,7 +591,8 @@ public EvaluationDetail JsonVariationDetail(string key, LdValue default return VariationInternal(key, defaultValue, LdValue.Convert.Json, false, _eventFactoryWithReasons); } - EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, LdValue.Converter converter, bool checkType, EventFactory eventFactory) + EvaluationDetail VariationInternal(string featureKey, LdValue defaultJson, LdValue.Converter converter, + bool checkType, EventFactory eventFactory) { T defaultValue = converter.ToType(defaultJson); @@ -585,14 +605,16 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (!Initialized) { _log.Warn("LaunchDarkly client has not yet been initialized. Returning default value"); - SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, defaultJson, + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, + defaultJson, EvaluationErrorKind.ClientNotReady)); return errorResult(EvaluationErrorKind.ClientNotReady); } else { _log.Info("Unknown feature flag {0}; returning default value", featureKey); - SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, defaultJson, + SendEvaluationEventIfOnline(eventFactory.NewUnknownFlagEvaluationEvent(featureKey, Context, + defaultJson, EvaluationErrorKind.FlagNotFound)); return errorResult(EvaluationErrorKind.FlagNotFound); } @@ -610,23 +632,28 @@ EvaluationDetail errorResult(EvaluationErrorKind kind) => if (flag.Value.IsNull) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, flag.Variation, flag.Reason ?? EvaluationReason.OffReason); + result = new EvaluationDetail(defaultValue, flag.Variation, + flag.Reason ?? EvaluationReason.OffReason); } else { if (checkType && !defaultJson.IsNull && flag.Value.Type != defaultJson.Type) { valueJson = defaultJson; - result = new EvaluationDetail(defaultValue, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); + result = new EvaluationDetail(defaultValue, null, + EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)); } else { valueJson = flag.Value; - result = new EvaluationDetail(converter.ToType(flag.Value), flag.Variation, flag.Reason ?? EvaluationReason.OffReason); + result = new EvaluationDetail(converter.ToType(flag.Value), flag.Variation, + flag.Reason ?? EvaluationReason.OffReason); } } + var featureEvent = eventFactory.NewEvaluationEvent(featureKey, flag, Context, - new EvaluationDetail(valueJson, flag.Variation, flag.Reason ?? EvaluationReason.OffReason), defaultJson); + new EvaluationDetail(valueJson, flag.Variation, flag.Reason ?? EvaluationReason.OffReason), + defaultJson); SendEvaluationEventIfOnline(featureEvent); return result; } @@ -644,6 +671,7 @@ public IDictionary AllFlags() { return ImmutableDictionary.Empty; } + return data.Value.Items.Where(entry => entry.Value.Item != null) .ToDictionary(p => p.Key, p => p.Value.Item.Value); } @@ -703,7 +731,10 @@ public async Task IdentifyAsync(Context context) { newContext = _autoEnvContextDecorator.DecorateContext(newContext); } - Context oldContext = newContext; // this initialization is overwritten below, it's only here to satisfy the compiler + + Context + oldContext = + newContext; // this initialization is overwritten below, it's only here to satisfy the compiler LockUtils.WithWriteLock(_stateLock, () => { @@ -723,11 +754,12 @@ public async Task IdentifyAsync(Context context) { _log.Debug("Identify found cached flag data for the new context"); } + _dataStore.Init( newContext, cachedData ?? new DataStoreTypes.FullDataSet(null), false // false means "don't rewrite the flags to persistent storage" - ); + ); EventProcessorIfEnabled().RecordIdentifyEvent(new EventProcessorTypes.IdentifyEvent { diff --git a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs index 50e22da5..a0a03822 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/BaseTest.cs @@ -43,7 +43,7 @@ public void Dispose() // needed, protects against accidental interaction with external services and also makes it easier to // see which properties are important in a test. protected ConfigurationBuilder BasicConfig() => - Configuration.Builder(BasicMobileKey, false) + Configuration.Builder(BasicMobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled) .BackgroundModeManager(new MockBackgroundModeManager()) .ConnectivityStateManager(new MockConnectivityStateManager(true)) .DataSource(new MockDataSource().AsSingletonFactory()) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs index 44c1f410..beae849e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/ConfigurationTest.cs @@ -10,24 +10,27 @@ namespace LaunchDarkly.Sdk.Client public class ConfigurationTest : BaseTest { private readonly BuilderBehavior.BuildTester _tester = - BuilderBehavior.For(() => Configuration.Builder(mobileKey, false), b => b.Build()) + BuilderBehavior.For(() => Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled), + b => b.Build()) .WithCopyConstructor(c => Configuration.Builder(c)); const string mobileKey = "any-key"; - public ConfigurationTest(ITestOutputHelper testOutput) : base(testOutput) { } + public ConfigurationTest(ITestOutputHelper testOutput) : base(testOutput) + { + } [Fact] public void DefaultSetsKey() { - var config = Configuration.Default(mobileKey, true); + var config = Configuration.Default(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled); Assert.Equal(mobileKey, config.MobileKey); } [Fact] public void BuilderSetsKey() { - var config = Configuration.Builder(mobileKey, false).Build(); + var config = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled).Build(); Assert.Equal(mobileKey, config.MobileKey); } @@ -99,7 +102,8 @@ public void Logging() public void LoggingAdapterShortcut() { var adapter = Logs.ToWriter(Console.Out); - var config = Configuration.Builder("key", false).Logging(adapter).Build(); + var config = Configuration.Builder("key", ConfigurationBuilder.AutoEnvAttributes.Disabled).Logging(adapter) + .Build(); var logConfig = config.LoggingConfigurationBuilder.CreateLoggingConfiguration(); Assert.Same(adapter, logConfig.LogAdapter); } @@ -130,13 +134,15 @@ public void Persistence() [Fact] public void MobileKeyCannotBeNull() { - Assert.Throws(() => Configuration.Default(null, true)); + Assert.Throws(() => + Configuration.Default(null, ConfigurationBuilder.AutoEnvAttributes.Disabled)); } [Fact] public void MobileKeyCannotBeEmpty() { - Assert.Throws(() => Configuration.Default("", true)); + Assert.Throws(() => + Configuration.Default("", ConfigurationBuilder.AutoEnvAttributes.Disabled)); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs index 58808721..906d8fcb 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataTest.cs @@ -21,7 +21,7 @@ public class TestDataTest : BaseTest public TestDataTest(ITestOutputHelper testOutput) : base(testOutput) { - _context = new LdClientContext(Configuration.Builder("key", false) + _context = new LdClientContext(Configuration.Builder("key", ConfigurationBuilder.AutoEnvAttributes.Disabled) .Logging(testLogging).Build(), _initialUser); } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs index 9f609f1f..ff622c7a 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/TestDataWithClientTest.cs @@ -12,7 +12,7 @@ public class TestDataWithClientTest : BaseTest public TestDataWithClientTest(ITestOutputHelper testOutput) : base(testOutput) { - _config = Configuration.Builder("mobile-key", false) + _config = Configuration.Builder("mobile-key", ConfigurationBuilder.AutoEnvAttributes.Disabled) .DataSource(_td) .Events(Components.NoEvents) .Build(); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 7904571f..27b02681 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -5,7 +5,6 @@ using LaunchDarkly.TestHelpers.HttpTest; using Xunit; using Xunit.Abstractions; - using static LaunchDarkly.TestHelpers.JsonAssertions; namespace LaunchDarkly.Sdk.Client.Internal.DataSources @@ -16,7 +15,9 @@ namespace LaunchDarkly.Sdk.Client.Internal.DataSources public class FeatureFlagRequestorTests : BaseTest { - public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput) { } + public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput) + { + } private const string _mobileKey = "FAKE_KEY"; @@ -26,7 +27,9 @@ public FeatureFlagRequestorTests(ITestOutputHelper testOutput) : base(testOutput // Note that in a real use case, the user encoding may vary depending on the target platform, because the SDK adds custom // user attributes like "os". But the lower-level FeatureFlagRequestor component does not do that. - private const string _allDataJson = "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response + private const string + _allDataJson = + "{}"; // Note that in this implementation, unlike the .NET SDK, FeatureFlagRequestor does not unmarshal the response [Theory] [InlineData("", false, "/msdk/evalx/contexts/", "")] @@ -40,20 +43,20 @@ public async Task GetFlagsUsesCorrectUriAndMethodInGetModeAsync( bool withReasons, string expectedPathWithoutUser, string expectedQuery - ) + ) { using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) { var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); - var config = Configuration.Default(_mobileKey, true); + var config = Configuration.Default(_mobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled); using (var requestor = new FeatureFlagRequestor( - baseUri, - _context, - withReasons, - new LdClientContext(config, _context).Http, - testLogger)) + baseUri, + _context, + withReasons, + new LdClientContext(config, _context).Http, + testLogger)) { var resp = await requestor.FeatureFlagsAsync(); Assert.Equal(200, resp.statusCode); @@ -61,7 +64,8 @@ string expectedQuery var req = server.Recorder.RequireRequest(); Assert.Equal("GET", req.Method); - AssertHelpers.ContextsEqual(_context, TestUtil.Base64ContextFromUrlPath(req.Path, expectedPathWithoutUser)); + AssertHelpers.ContextsEqual(_context, + TestUtil.Base64ContextFromUrlPath(req.Path, expectedPathWithoutUser)); Assert.Equal(expectedQuery, req.Query); Assert.Equal(_mobileKey, req.Headers["Authorization"]); Assert.Equal("", req.Body); @@ -83,22 +87,22 @@ public async Task GetFlagsUsesCorrectUriAndMethodInReportModeAsync( bool withReasons, string expectedPath, string expectedQuery - ) + ) { using (var server = HttpServer.Start(Handlers.BodyJson(_allDataJson))) { var baseUri = new Uri(server.Uri.ToString().TrimEnd('/') + baseUriExtraPath); - var config = Configuration.Builder(_mobileKey, true) + var config = Configuration.Builder(_mobileKey, ConfigurationBuilder.AutoEnvAttributes.Disabled) .Http(Components.HttpConfiguration().UseReport(true)) .Build(); using (var requestor = new FeatureFlagRequestor( - baseUri, - _context, - withReasons, - new LdClientContext(config, _context).Http, - testLogger)) + baseUri, + _context, + withReasons, + new LdClientContext(config, _context).Http, + testLogger)) { var resp = await requestor.FeatureFlagsAsync(); Assert.Equal(200, resp.statusCode); diff --git a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs index 9e4488bb..de00e482 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/TestUtil.cs @@ -6,7 +6,6 @@ using LaunchDarkly.Sdk.Internal; using LaunchDarkly.Sdk.Json; using Xunit; - using static LaunchDarkly.Sdk.Client.DataModel; using static LaunchDarkly.Sdk.Client.Subsystems.DataStoreTypes; @@ -20,7 +19,8 @@ public static class TestUtil private static ThreadLocal InClientLock = new ThreadLocal(); - public static LdClientContext SimpleContext => new LdClientContext(Configuration.Default("key", true), Context.New("userkey")); + public static LdClientContext SimpleContext => new LdClientContext( + Configuration.Default("key", ConfigurationBuilder.AutoEnvAttributes.Enabled), Context.New("userkey")); public static Context Base64ContextFromUrlPath(string path, string pathPrefix) { @@ -42,6 +42,7 @@ public static T WithClientLock(Func f) { return f.Invoke(); } + ClientInstanceLock.Wait(); try { @@ -62,6 +63,7 @@ public static void WithClientLock(Action a) a.Invoke(); return; } + ClientInstanceLock.Wait(); try { @@ -81,6 +83,7 @@ public static async Task WithClientLockAsync(Func> f) { return await f.Invoke(); } + await ClientInstanceLock.WaitAsync(); try { @@ -124,10 +127,7 @@ public static async Task CreateClientAsync(Configuration config, Conte public static void ClearClient() { - WithClientLock(() => - { - LdClient.Instance?.Dispose(); - }); + WithClientLock(() => { LdClient.Instance?.Dispose(); }); } internal static string MakeJsonData(FullDataSet data) @@ -143,6 +143,7 @@ internal static string MakeJsonData(FullDataSet data) FeatureFlagJsonConverter.WriteJsonValue(item.Value.Item, w); } } + w.WriteEndObject(); }); } From 9dca50eff3cadccbc3b3493f9bd0e6f1c04ea386 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 10 Oct 2023 15:24:55 -0700 Subject: [PATCH 465/499] feat: propagate locale in SDK attribute layer (#196) - [x] Requires a new release of Common (6.2.0?) before merging, currently on Common's `main` Plumbs the `CultureInfo.CurrentCulture` locale string into the SDK layer. The platform layer provides `null` since this isn't a cross-platform thing and is available in standard .NET. Caveat: I haven't tested this on Android or iOS. It's possible the standard `CultureInfo.CurrentCulture` just doesn't work on those platforms, or something, and we actually _do_ need to use special APIs. Also note: if the culture isn't set, then .NET uses the "InvariantCulture". In this case, we'll pass "en" as the `locale` string because it's English-like without a specific region. This is less confusing than omitting or using an empty string. --- .../Internal/AutoEnvContextDecorator.cs | 6 ++--- .../LaunchDarkly.ClientSdk.csproj | 4 ++-- .../Subsystems/PlatformAttributes.cs | 9 +++++++- .../Subsystems/SdkAttributes.cs | 3 ++- .../Internal/AutoEnvContextDecoratorTest.cs | 23 +++++++++++++++++-- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 5cd6e19c..526b8802 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -102,12 +102,10 @@ private List MakeRecipeList() { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationId) }, { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationName) }, { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersion) }, - { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) } + { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) }, + { ATTR_LOCALE, () => LdValue.Of(_environmentReporter.Locale) } }; - // TODO: missing locale in environment reporter implementation - // applicationCallables.Add(ATTR_LOCALE, () => LDValue.Of(environmentReporter.GetLocale())); - var ldDeviceKind = ContextKind.Of(LD_DEVICE_KIND); var deviceCallables = new Dictionary> { diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 7cdb0a2c..fb3db23b 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -39,9 +39,9 @@ - + - + diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs index 63ec5d65..ff0b43aa 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/PlatformAttributes.cs @@ -1,3 +1,4 @@ +using System.Globalization; using LaunchDarkly.Sdk.Client.PlatformSpecific; using LaunchDarkly.Sdk.EnvReporting; @@ -8,7 +9,13 @@ internal static class PlatformAttributes internal static Layer Layer => new Layer( AppInfo.GetAppInfo(), DeviceInfo.GetOsInfo(), - DeviceInfo.GetDeviceInfo() + DeviceInfo.GetDeviceInfo(), + // The InvariantCulture is default if none is set by the application. Microsoft says: + // "...it is associated with the English language but not with any country/region.." + // Source: https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.invariantculture + // In order to avoid returning an empty string (their representation of InvariantCulture) as a context attribute, + // we will return "en" instead as the closest representation. + CultureInfo.CurrentCulture.Equals(CultureInfo.InvariantCulture) ? "en" : CultureInfo.CurrentCulture.ToString() ); } } diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs index 791d17f1..23a2d6f9 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/SdkAttributes.cs @@ -1,3 +1,4 @@ +using System.Globalization; using LaunchDarkly.Sdk.Client.Internal; using LaunchDarkly.Sdk.EnvReporting; @@ -10,6 +11,6 @@ internal static class SdkAttributes SdkPackage.Name, SdkPackage.Version, SdkPackage.Version), - null, null); + null, null, null); } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 9ecc887a..2a5e1a6e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -1,3 +1,4 @@ +using System.Globalization; using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.EnvReporting; @@ -28,10 +29,9 @@ public void AdheresToSchemaTest() .Set(AutoEnvContextDecorator.ATTR_NAME, SdkPackage.Name) .Set(AutoEnvContextDecorator.ATTR_VERSION, SdkPackage.Version) .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, SdkPackage.Version) + .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") .Build(); - //TODO: Set Locale - .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") - var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) @@ -50,6 +50,25 @@ public void AdheresToSchemaTest() Assert.Equal(expectedOutput, output); } + [Fact] + public void CustomCultureInPlatformLayerIsPropagated() + { + var platform = new Layer(null, null, null, "en-GB"); + + var envReporter = new EnvironmentReporterBuilder().SetPlatformLayer(platform).Build(); + var store = MakeMockDataStoreWrapper(); + var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); + + var input = Context.Builder("aKey").Kind(ContextKind.Of("aKind")).Build(); + var output = decoratorUnderTest.DecorateContext(input); + + + Context outContext; + Assert.True(output.TryGetContextByKind(new ContextKind(AutoEnvContextDecorator.LD_APPLICATION_KIND), out outContext)); + + Assert.Equal("en-GB", outContext.GetValue("locale").AsString); + } + [Fact] public void DoesNotOverwriteCustomerDataTest() { From d1c3c1e12c2b7170048df3d50eafae9301dd6eef Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 11 Oct 2023 11:12:29 -0700 Subject: [PATCH 466/499] fix: Match the client's Xamarin.Android.Support.v4 package Android project (#198) The support package used by the client's `.csproj` was a slightly different version than what is used in the Android test project. I've matched it to the Android project and the `build_android_and_ios` succeeds. --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index fb3db23b..ea63a9ed 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -95,13 +95,13 @@ - + - + - + ../../LaunchDarkly.ClientSdk.snk From 5b1f8483f30b47bcdff57dba1eb0930cf5b51a6a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 11 Oct 2023 12:04:45 -0700 Subject: [PATCH 467/499] revert some unnecessary changes in ClientSdk.csproj --- .../LaunchDarkly.ClientSdk.csproj | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ea63a9ed..b20af706 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -69,16 +69,7 @@ - - - - - - - - - - + From 57bb551fce2a8ef74a3c473edced08a97a0de49f Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 11 Oct 2023 15:55:59 -0500 Subject: [PATCH 468/499] Adding additional tests for LdClient and LdClientContext --- .../ConfigurationBuilder.cs | 11 + .../Subsystems/LdClientContext.cs | 19 +- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 1 + .../LdClientContextTests.cs | 56 +++ .../Resources/Resource.designer.cs | 127 +++++- .../LdClientTests.cs | 397 ++++++++++-------- 6 files changed, 423 insertions(+), 188 deletions(-) create mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 7113d562..c4cc0f2a 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -120,6 +120,17 @@ public ConfigurationBuilder ApplicationInfo(ApplicationInfoBuilder applicationIn return this; } + /// + /// TODO + /// + /// + /// + public ConfigurationBuilder AutoEnvironmentAttributes(AutoEnvAttributes autoEnvAttributes) + { + _autoEnvAttributes = autoEnvAttributes == AutoEnvAttributes.Enabled; // map enum to boolean + return this; + } + /// /// Sets the implementation of the component that receives feature flag data from LaunchDarkly, /// using a factory object. diff --git a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs index ad518306..eea983d7 100644 --- a/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs +++ b/src/LaunchDarkly.ClientSdk/Subsystems/LdClientContext.cs @@ -96,7 +96,7 @@ public LdClientContext( ) { var logger = MakeLogger(configuration); - var environmentReporter = MakeEnvironmentReporter(configuration.ApplicationInfo); + var environmentReporter = MakeEnvironmentReporter(configuration); MobileKey = configuration.MobileKey; BaseLogger = logger; @@ -230,23 +230,26 @@ internal static Logger MakeLogger(Configuration configuration) return logAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.Base); } - internal static IEnvironmentReporter MakeEnvironmentReporter(ApplicationInfoBuilder applicationInfoBuilder) + internal static IEnvironmentReporter MakeEnvironmentReporter(Configuration configuration) { + var applicationInfoBuilder = configuration.ApplicationInfo; + var builder = new EnvironmentReporterBuilder(); if (applicationInfoBuilder != null) { var applicationInfo = applicationInfoBuilder.Build(); - + // If AppInfo is provided by the user, then the Config layer has first priority in the environment reporter. builder.SetConfigLayer(new ConfigLayerBuilder().SetAppInfo(applicationInfo).Build()); } - // TODO: this platform layer being set needs to depend on the configuration so that when the - // customer opts out of auto env attributes, we do not report it through the env reporter + // Enable the platform layer if auto env attributes is opted in. + if (configuration.AutoEnvAttributes) + { + // The platform layer has second priority if properties aren't set by the Config layer. + builder.SetPlatformLayer(PlatformAttributes.Layer); + } - // The platform layer has second priority if properties aren't set by the Config layer. - builder.SetPlatformLayer(PlatformAttributes.Layer); - // The SDK layer has third priority if properties aren't set by the Platform layer. builder.SetSdkLayer(SdkAttributes.Layer); diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj index ea6aef1d..b124682c 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj @@ -93,6 +93,7 @@ project file format, they need to be referenced explicitly even though they're in the project directory. --> + diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs new file mode 100644 index 00000000..7b2b61fa --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs @@ -0,0 +1,56 @@ +using Xunit; + +namespace LaunchDarkly.Sdk.Client.Subsystems +{ + /// + /// These tests are for the . Since some of the behavior is platform dependent + /// and the .NET Standard does not support some platform capabilities we want to test, this test class is + /// in the Android tests package. + /// + public class LdClientContextTests + { + [Fact] + public void TestMakeEnvironmentReporterUsesApplicationInfoWhenSet() + { + var configuration = Configuration.Builder("aKey", ConfigurationBuilder.AutoEnvAttributes.Disabled) + .ApplicationInfo( + Components.ApplicationInfo().ApplicationId("mockId").ApplicationName("mockName") + ).Build(); + + var output = LdClientContext.MakeEnvironmentReporter(configuration); + Assert.Equal("mockId", output.ApplicationInfo.ApplicationId); + } + + [Fact] + public void TestMakeEnvironmentReporterDefaultsToSdkLayerWhenNothingSet() + { + var configuration = Configuration.Builder("aKey", ConfigurationBuilder.AutoEnvAttributes.Disabled) + .Build(); + + var output = LdClientContext.MakeEnvironmentReporter(configuration); + Assert.Equal(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo.ApplicationId); + } + + [Fact] + public void TestMakeEnvironmentReporterUsesPlatformLayerWhenAutoEnvEnabled() + { + var configuration = Configuration.Builder("aKey", ConfigurationBuilder.AutoEnvAttributes.Enabled) + .Build(); + + var output = LdClientContext.MakeEnvironmentReporter(configuration); + Assert.NotEqual(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo.ApplicationId); + } + + [Fact] + public void TestMakeEnvironmentReporterUsesApplicationInfoWhenSetAndAutoEnvEnabled() + { + var configuration = Configuration.Builder("aKey", ConfigurationBuilder.AutoEnvAttributes.Enabled) + .ApplicationInfo( + Components.ApplicationInfo().ApplicationId("mockId").ApplicationName("mockName") + ).Build(); + + var output = LdClientContext.MakeEnvironmentReporter(configuration); + Assert.Equal("mockId", output.ApplicationInfo.ApplicationId); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index 7f4e9317..6bbb3baa 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,7 +2,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -15,7 +14,7 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.1.111")] public partial class Resource { @@ -26,6 +25,130 @@ static Resource() public static void UpdateIdValues() { + global::LaunchDarkly.Sdk.Client.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; + global::LaunchDarkly.Sdk.Client.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; + global::LaunchDarkly.Sdk.Client.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; + global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; + global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::LaunchDarkly.Sdk.Client.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; + global::LaunchDarkly.Sdk.Client.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; + global::LaunchDarkly.Sdk.Client.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; + global::LaunchDarkly.Sdk.Client.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; + global::LaunchDarkly.Sdk.Client.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; + global::LaunchDarkly.Sdk.Client.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; + global::LaunchDarkly.Sdk.Client.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; + global::LaunchDarkly.Sdk.Client.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; + global::LaunchDarkly.Sdk.Client.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; + global::LaunchDarkly.Sdk.Client.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; + global::LaunchDarkly.Sdk.Client.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; + global::LaunchDarkly.Sdk.Client.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; + global::LaunchDarkly.Sdk.Client.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; + global::LaunchDarkly.Sdk.Client.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; + global::LaunchDarkly.Sdk.Client.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; + global::LaunchDarkly.Sdk.Client.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; + global::LaunchDarkly.Sdk.Client.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; + global::LaunchDarkly.Sdk.Client.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; + global::LaunchDarkly.Sdk.Client.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; + global::LaunchDarkly.Sdk.Client.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; + global::LaunchDarkly.Sdk.Client.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; + global::LaunchDarkly.Sdk.Client.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; + global::LaunchDarkly.Sdk.Client.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; + global::LaunchDarkly.Sdk.Client.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; + global::LaunchDarkly.Sdk.Client.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; + global::LaunchDarkly.Sdk.Client.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; + global::LaunchDarkly.Sdk.Client.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index f627a400..00e61223 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -9,11 +9,11 @@ namespace LaunchDarkly.Sdk.Client { public class LdClientTests : BaseTest { - private static readonly Context AnonUser = Context.Builder("anon-placeholder-key") - .Anonymous(true) - .Set("email", "example") - .Set("other", 3) - .Build(); + private static readonly Context AnonUser = Context.Builder("anon-placeholder-key") + .Anonymous(true) + .Set("email", "example") + .Set("other", 3) + .Build(); public LdClientTests(ITestOutputHelper testOutput) : base(testOutput) { } @@ -54,87 +54,87 @@ public async void InitPassesUserToDataSource() Assert.Equal(BasicUser.Key, actualUser.Key); Assert.Equal(actualUser, dataSourceConfig.ReceivedClientContext.CurrentContext); } - } - - [Fact] - public async Task InitWithAnonUserAddsRandomizedKey() - { - // Note, we don't care about polling mode vs. streaming mode for this functionality. - var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); - - string key1; - - using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) - { - key1 = client.Context.Key; - Assert.NotNull(key1); - Assert.NotEqual("", key1); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key1).Build(), - client.Context); - } - - // Starting again should generate a new key, since we've turned off persistence - using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) - { - var key2 = client.Context.Key; - Assert.NotNull(key2); - Assert.NotEqual("", key2); - Assert.NotEqual(key1, key2); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key2).Build(), - client.Context); - } - } - - [Fact] - public async Task InitWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() - { - // Note, we don't care about polling mode vs. streaming mode for this functionality. - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); - - using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) - { - AssertHelpers.ContextsEqual(AnonUser, client.Context); - } - } - - [Fact] - public async Task InitWithAnonUserCanReusePreviousRandomizedKey() - { - // Note, we don't care about polling mode vs. streaming mode for this functionality. - var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage( - store.AsSingletonFactory())) - .GenerateAnonymousKeys(true).Build(); - - string key1; - - using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) - { - key1 = client.Context.Key; - Assert.NotNull(key1); - Assert.NotEqual("", key1); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key1).Build(), - client.Context); - } - - // Starting again should reuse the persisted key - using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) - { - Assert.Equal(key1, client.Context.Key); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key1).Build(), - client.Context); - } - } - + } + + [Fact] + public async Task InitWithAnonUserAddsRandomizedKey() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); + + string key1; + + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key1).Build(), + client.Context); + } + + // Starting again should generate a new key, since we've turned off persistence + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + var key2 = client.Context.Key; + Assert.NotNull(key2); + Assert.NotEqual("", key2); + Assert.NotEqual(key1, key2); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key2).Build(), + client.Context); + } + } + + [Fact] + public async Task InitWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + AssertHelpers.ContextsEqual(AnonUser, client.Context); + } + } + + [Fact] + public async Task InitWithAnonUserCanReusePreviousRandomizedKey() + { + // Note, we don't care about polling mode vs. streaming mode for this functionality. + var store = new MockPersistentDataStore(); + var config = BasicConfig().Persistence(Components.Persistence().Storage( + store.AsSingletonFactory())) + .GenerateAnonymousKeys(true).Build(); + + string key1; + + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key1).Build(), + client.Context); + } + + // Starting again should reuse the persisted key + using (var client = await TestUtil.CreateClientAsync(config, AnonUser)) + { + Assert.Equal(key1, client.Context.Key); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key1).Build(), + client.Context); + } + } + [Fact] public async void InitWithAnonUserPassesGeneratedUserToDataSource() { - MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); - + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); var config = BasicConfig() .DataSource(dataSourceConfig) @@ -146,8 +146,49 @@ public async void InitWithAnonUserPassesGeneratedUserToDataSource() var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.NotEqual(AnonUser, receivedContext); Assert.Equal(client.Context, receivedContext); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(receivedContext.Key).Build(), + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(receivedContext.Key).Build(), + receivedContext); + } + } + + [Fact] + public async void InitWithAutoEnvAttributesEnabledAddsContexts() + { + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); + var config = BasicConfig() + .AutoEnvironmentAttributes(ConfigurationBuilder.AutoEnvAttributes.Enabled) + .DataSource(dataSourceConfig) + .GenerateAnonymousKeys(true) + .Build(); + + using (var client = await LdClient.InitAsync(config, AnonUser)) + { + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + } + } + + [Fact] + public async void InitWithAutoEnvAttributesDisabledNoAddedContexts() + { + MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); + var config = BasicConfig() + .AutoEnvironmentAttributes(ConfigurationBuilder.AutoEnvAttributes.Disabled) + .DataSource(dataSourceConfig) + .GenerateAnonymousKeys(true) + .Build(); + + using (var client = await LdClient.InitAsync(config, AnonUser)) + { + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.NotEqual(AnonUser, receivedContext); + Assert.Equal(client.Context, receivedContext); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(receivedContext.Key).Build(), receivedContext); } } @@ -255,95 +296,95 @@ public async void IdentifyPassesUserToDataSource() AssertHelpers.ContextsEqual(newUser, client.Context); Assert.Equal(client.Context, receivedContext); } - } - - [Fact] - public async Task IdentifyWithAnonUserAddsRandomizedKey() - { - var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); - - string key1; - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(AnonUser); - - key1 = client.Context.Key; - Assert.NotNull(key1); - Assert.NotEqual("", key1); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key1).Build(), - client.Context); - - var anonUser2 = TestUtil.BuildAutoContext().Name("other").Build(); - await client.IdentifyAsync(anonUser2); - var key2 = client.Context.Key; - Assert.Equal(key1, key2); // Even though persistence is disabled, the key is stable during the lifetime of the SDK client. - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(anonUser2).Key(key2).Build(), - client.Context); - } - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(AnonUser); - - var key3 = client.Context.Key; - Assert.NotNull(key3); - Assert.NotEqual("", key3); - Assert.NotEqual(key1, key3); // The previously generated key was discarded with the previous client. - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key3).Build(), - client.Context); - } - } - - [Fact] - public async Task IdentifyWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() - { - var config = BasicConfig().Persistence(Components.NoPersistence).Build(); - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(AnonUser); - - AssertHelpers.ContextsEqual(AnonUser, client.Context); - } - } - - [Fact] - public async Task IdentifyWithAnonUserCanReusePersistedRandomizedKey() - { - var store = new MockPersistentDataStore(); - var config = BasicConfig().Persistence(Components.Persistence().Storage( - store.AsSingletonFactory())) - .GenerateAnonymousKeys(true).Build(); - - string key1; - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(AnonUser); - - key1 = client.Context.Key; - Assert.NotNull(key1); - Assert.NotEqual("", key1); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key1).Build(), - client.Context); - } - - using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) - { - await client.IdentifyAsync(AnonUser); - - var key2 = client.Context.Key; - Assert.Equal(key1, key2); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(key2).Build(), - client.Context); - } - } + } + + [Fact] + public async Task IdentifyWithAnonUserAddsRandomizedKey() + { + var config = BasicConfig().Persistence(Components.NoPersistence).GenerateAnonymousKeys(true).Build(); + + string key1; + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key1).Build(), + client.Context); + + var anonUser2 = TestUtil.BuildAutoContext().Name("other").Build(); + await client.IdentifyAsync(anonUser2); + var key2 = client.Context.Key; + Assert.Equal(key1, key2); // Even though persistence is disabled, the key is stable during the lifetime of the SDK client. + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(anonUser2).Key(key2).Build(), + client.Context); + } + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + var key3 = client.Context.Key; + Assert.NotNull(key3); + Assert.NotEqual("", key3); + Assert.NotEqual(key1, key3); // The previously generated key was discarded with the previous client. + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key3).Build(), + client.Context); + } + } + + [Fact] + public async Task IdentifyWithAnonUserDoesNotChangeKeyIfConfigOptionIsNotSet() + { + var config = BasicConfig().Persistence(Components.NoPersistence).Build(); + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + AssertHelpers.ContextsEqual(AnonUser, client.Context); + } + } + + [Fact] + public async Task IdentifyWithAnonUserCanReusePersistedRandomizedKey() + { + var store = new MockPersistentDataStore(); + var config = BasicConfig().Persistence(Components.Persistence().Storage( + store.AsSingletonFactory())) + .GenerateAnonymousKeys(true).Build(); + + string key1; + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + key1 = client.Context.Key; + Assert.NotNull(key1); + Assert.NotEqual("", key1); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key1).Build(), + client.Context); + } + + using (var client = await TestUtil.CreateClientAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + var key2 = client.Context.Key; + Assert.Equal(key1, key2); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(key2).Build(), + client.Context); + } + } [Fact] public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() @@ -363,8 +404,8 @@ public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.NotEqual(AnonUser, receivedContext); Assert.Equal(client.Context, receivedContext); - AssertHelpers.ContextsEqual( - Context.BuilderFromContext(AnonUser).Key(client.Context.Key).Build(), + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(client.Context.Key).Build(), receivedContext); } } @@ -421,8 +462,8 @@ public void FlagsAreLoadedFromPersistentStorageByDefault() var config = BasicConfig() .Persistence(Components.Persistence().Storage(storage.AsSingletonFactory())) .Offline(true) - .Build(); - storage.SetupUserData(config.MobileKey, BasicUser.Key, data); + .Build(); + storage.SetupUserData(config.MobileKey, BasicUser.Key, data); using (var client = TestUtil.CreateClient(config, BasicUser)) { From 5d3e0ac4dd4a8555b1c3c7cce073926cbbdc6021 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 11 Oct 2023 17:11:09 -0500 Subject: [PATCH 469/499] Fixing documentation errors. --- src/LaunchDarkly.ClientSdk/Configuration.cs | 19 ++++++++++++++--- .../ConfigurationBuilder.cs | 21 ++++++++++++------- .../Internal/AutoEnvContextDecorator.cs | 19 +++++++++++------ .../LaunchDarkly.ClientSdk.csproj | 4 +--- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Configuration.cs b/src/LaunchDarkly.ClientSdk/Configuration.cs index 5946e16e..9b457674 100644 --- a/src/LaunchDarkly.ClientSdk/Configuration.cs +++ b/src/LaunchDarkly.ClientSdk/Configuration.cs @@ -24,7 +24,8 @@ public sealed class Configuration public ApplicationInfoBuilder ApplicationInfo { get; } /// - /// TODO + /// True if Auto Environment Attributes functionality is enabled. When enabled, the SDK will automatically + /// provide data about the environment where the application is running. /// public bool AutoEnvAttributes { get; } @@ -134,7 +135,13 @@ public sealed class Configuration /// Creates a configuration with all parameters set to the default. /// /// the SDK key for your LaunchDarkly environment - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// a instance public static Configuration Default(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes) { @@ -159,7 +166,13 @@ public static Configuration Default(string mobileKey, ConfigurationBuilder.AutoE /// /// /// the mobile SDK key for your LaunchDarkly environment - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// a builder object public static ConfigurationBuilder Builder(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes) diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index c4cc0f2a..75b9ed72 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -11,7 +11,7 @@ namespace LaunchDarkly.Sdk.Client /// /// /// - /// Obtain an instance of this class by calling . + /// Obtain an instance of this class by calling . /// /// /// All of the builder methods for setting a configuration property return a reference to the same builder, so they can be @@ -27,10 +27,11 @@ public sealed class ConfigurationBuilder { /// /// Enable / disable options for Auto Environment Attributes functionality. When enabled, the SDK will automatically - /// provide data about the mobile environment where the application is running. This data makes it simpler to target + /// provide data about the environment where the application is running. This data makes it simpler to target /// your mobile customers based on application name or version, or on device characteristics including manufacturer, - /// model, operating system, locale, and so on. We recommend enabling this when you configure the SDK. See TKTK - /// for more documentation. + /// model, operating system, locale, and so on. We recommend enabling this when you configure the SDK. See + /// our documentation + /// for more details. /// For example, consider a “dark mode” feature being added to an app. Versions 10 through 14 contain early, /// incomplete versions of the feature. These versions are available to all customers, but the “dark mode” feature is only /// enabled for testers. With version 15, the feature is considered complete. With Auto Environment Attributes enabled, @@ -121,9 +122,15 @@ public ConfigurationBuilder ApplicationInfo(ApplicationInfoBuilder applicationIn } /// - /// TODO + /// Specifies whether the SDK will use Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// - /// + /// Enable / disable Auto Environment Attributes functionality. /// public ConfigurationBuilder AutoEnvironmentAttributes(AutoEnvAttributes autoEnvAttributes) { @@ -244,7 +251,7 @@ public ConfigurationBuilder Events(IComponentConfigurer eventsC /// /// /// If enabled, this option changes the SDK's behavior whenever the (as given to - /// methods like or + /// methods like or /// ) has an /// property of , as follows: /// diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 526b8802..356be6b3 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -7,7 +7,8 @@ namespace LaunchDarkly.Sdk.Client.Internal { /// - /// TODO + /// This class can decorate a context by adding additional contexts to it using auto environment attributes + /// gotten via the provided . /// internal class AutoEnvContextDecorator { @@ -30,11 +31,13 @@ internal class AutoEnvContextDecorator private readonly Logger _logger; /// - /// TODO + /// Creates a . /// - /// - /// - /// + /// the data source that will be used for retrieving/saving information related + /// to the generated contexts. Example data includes the stable key of the ld_device context kind. + /// the environment reporter that will be used to source the + /// environment attributes + /// the humble logger public AutoEnvContextDecorator( PersistentDataStoreWrapper persistentData, IEnvironmentReporter environmentReporter, @@ -45,6 +48,11 @@ public AutoEnvContextDecorator( _logger = logger; } + /// + /// Decorates the provided context with additional contexts containing environment attributes. + /// + /// the context to be decorated + /// the decorated context public Context DecorateContext(Context context) { var builder = Context.MultiBuilder(); @@ -139,7 +147,6 @@ private List MakeRecipeList() }; } - // TODO: commonize this with duplicate implementation in AnonymousKeyContextDecorator private string GetOrCreateAutoContextKey(PersistentDataStoreWrapper store, ContextKind contextKind) { var uniqueId = store.GetGeneratedContextKey(contextKind); diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index b20af706..ed255d26 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -30,9 +30,7 @@ LaunchDarkly.Sdk.Client - - - 1570,1571,1572,1573,1580,1581,1584,1591,1710,1711,1712 + 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 From 984cfe3a43f3cfebd63985d692784a928353b9d7 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 12 Oct 2023 10:31:56 -0500 Subject: [PATCH 470/499] Adding tests, documentation and comment fixes, addressing review comments. --- .../Internal/AutoEnvContextDecorator.cs | 16 +- .../LaunchDarkly.ClientSdk.csproj | 3 - src/LaunchDarkly.ClientSdk/LdClient.cs | 110 +- .../Resources/Resource.designer.cs | 4799 ++++++++--------- .../LdClientTests.cs | 45 + 5 files changed, 2461 insertions(+), 2512 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 356be6b3..9586df46 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -8,7 +8,7 @@ namespace LaunchDarkly.Sdk.Client.Internal { /// /// This class can decorate a context by adding additional contexts to it using auto environment attributes - /// gotten via the provided . + /// provided by . /// internal class AutoEnvContextDecorator { @@ -37,7 +37,7 @@ internal class AutoEnvContextDecorator /// to the generated contexts. Example data includes the stable key of the ld_device context kind. /// the environment reporter that will be used to source the /// environment attributes - /// the humble logger + /// the logger public AutoEnvContextDecorator( PersistentDataStoreWrapper persistentData, IEnvironmentReporter environmentReporter, @@ -77,16 +77,16 @@ public Context DecorateContext(Context context) private class ContextRecipe { - public ContextKind Kind; - public Func KeyCallable; - public Dictionary> AttributeCallables; + public ContextKind Kind { get; } + public Func KeyCallable { get; } + public Dictionary> AttributeCallables { get; } public ContextRecipe(ContextKind kind, Func keyCallable, Dictionary> attributeCallables) { - this.Kind = kind; - this.KeyCallable = keyCallable; - this.AttributeCallables = attributeCallables; + Kind = kind; + KeyCallable = keyCallable; + AttributeCallables = attributeCallables; } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index ed255d26..d366ce6d 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -43,9 +43,6 @@ - - Code - diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 95cd0681..6034dfa2 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -25,8 +25,8 @@ namespace LaunchDarkly.Sdk.Client /// /// /// Like all client-side LaunchDarkly SDKs, the LdClient always has a single current . - /// You specify this context at initialization time, and you can change it later with - /// or . All subsequent calls to evaluation methods like + /// You specify this context at initialization time, and you can change it later with + /// or . All subsequent calls to evaluation methods like /// refer to the flag values for the current context. /// /// @@ -59,7 +59,7 @@ public sealed class LdClient : ILdClient readonly IEventProcessor _eventProcessor; readonly IFlagTracker _flagTracker; readonly TaskExecutor _taskExecutor; - readonly AnonymousKeyContextDecorator _anonymousKeyAnonymousKeyContextDecorator; + readonly AnonymousKeyContextDecorator _anonymousKeyContextDecorator; private readonly AutoEnvContextDecorator _autoEnvContextDecorator; private readonly Logger _log; @@ -73,8 +73,8 @@ public sealed class LdClient : ILdClient /// create a new client instance unless you first call on this one. /// /// - /// Use the static factory methods or - /// to set this instance. + /// Use the static factory methods or + /// to set this instance. /// public static LdClient Instance => _instance; @@ -92,9 +92,9 @@ public sealed class LdClient : ILdClient /// The current evaluation context for all SDK operations. /// /// - /// This is initially the context specified for or - /// , but can be changed later with - /// or . + /// This is initially the context specified for or + /// , but can be changed later with + /// or . /// public Context Context => LockUtils.WithReadLock(_stateLock, () => _context); @@ -164,9 +164,9 @@ public sealed class LdClient : ILdClient _log.SubLogger(LogNames.DataStoreSubLog) ); - _anonymousKeyAnonymousKeyContextDecorator = + _anonymousKeyContextDecorator = new AnonymousKeyContextDecorator(_dataStore.PersistentStore, _config.GenerateAnonymousKeys); - var decoratedContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(initialContext); + var decoratedContext = _anonymousKeyContextDecorator.DecorateContext(initialContext); if (_config.AutoEnvAttributes) { @@ -274,9 +274,10 @@ async Task StartAsync() /// an property of . /// /// - /// If you would rather this happen asynchronously, use . To + /// If you would rather this happen asynchronously, use + /// . To /// specify additional configuration options rather than just the mobile key, use - /// or . + /// or . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -284,14 +285,20 @@ async Task StartAsync() /// /// /// the mobile key given to you by LaunchDarkly - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize /// the singleton instance - /// - /// - /// + /// + /// + /// public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, Context initialContext, TimeSpan maxWaitTime) { @@ -304,18 +311,25 @@ public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttrib /// Creates a new singleton instance and attempts to initialize feature flags. /// /// - /// This is equivalent to , but using the + /// This is equivalent to + /// , but using the /// type instead of . /// /// the mobile key given to you by LaunchDarkly - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// the initial user attributes; see for more /// about setting the context and optionally requesting a unique key for it /// the maximum length of time to wait for the client to initialize /// the singleton instance /// - /// - /// + /// + /// public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttributes autoEnvAttributes, User initialUser, TimeSpan maxWaitTime) => Init(mobileKey, autoEnvAttributes, Context.FromUser(initialUser), maxWaitTime); @@ -330,9 +344,10 @@ public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttrib /// the LaunchDarkly service is returned (or immediately if it is in offline mode). /// /// - /// If you would rather this happen synchronously, use . To + /// If you would rather this happen synchronously, use + /// . To /// specify additional configuration options rather than just the mobile key, you can use - /// or . + /// or . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -340,7 +355,13 @@ public static LdClient Init(string mobileKey, ConfigurationBuilder.AutoEnvAttrib /// /// /// the mobile key given to you by LaunchDarkly - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// the initial evaluation context; see for more /// about setting the context and optionally requesting a unique key for it /// a Task that resolves to the singleton LdClient instance @@ -357,11 +378,18 @@ public static async Task InitAsync(string mobileKey, /// asynchronously. /// /// - /// This is equivalent to , but using the + /// This is equivalent to + /// , but using the /// type instead of . /// /// the mobile key given to you by LaunchDarkly - /// TODOo + /// Enable / disable Auto Environment Attributes functionality. When enabled, + /// the SDK will automatically provide data about the environment where the application is running. + /// This data makes it simpler to target your mobile customers based on application name or version, or on + /// device characteristics including manufacturer, model, operating system, locale, and so on. We recommend + /// enabling this when you configure the SDK. See + /// our documentation for + /// more details. /// the initial user attributes /// a Task that resolves to the singleton LdClient instance public static Task InitAsync(string mobileKey, @@ -380,9 +408,10 @@ public static Task InitAsync(string mobileKey, /// an property of . /// /// - /// If you would rather this happen asynchronously, use . + /// If you would rather this happen asynchronously, use . /// If you do not need to specify configuration options other than the mobile key, you can use - /// or . + /// or + /// . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -397,8 +426,8 @@ public static Task InitAsync(string mobileKey, /// an uninitialized state /// the singleton LdClient instance /// - /// - /// + /// + /// public static LdClient Init(Configuration config, Context initialContext, TimeSpan maxWaitTime) { if (maxWaitTime.Ticks < 0 && maxWaitTime != Timeout.InfiniteTimeSpan) @@ -416,7 +445,7 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp /// fetching Feature Flags. /// /// - /// This is equivalent to , but using the + /// This is equivalent to , but using the /// type instead of . /// /// the client configuration @@ -425,8 +454,8 @@ public static LdClient Init(Configuration config, Context initialContext, TimeSp /// if this time elapses, the method will not throw an exception but will return the client in /// an uninitialized state /// the singleton LdClient instance - /// - /// + /// + /// /// public static LdClient Init(Configuration config, User initialUser, TimeSpan maxWaitTime) => Init(config, Context.FromUser(initialUser), maxWaitTime); @@ -441,9 +470,10 @@ public static LdClient Init(Configuration config, User initialUser, TimeSpan max /// the LaunchDarkly service is returned (or immediately if it is in offline mode). /// /// - /// If you would rather this happen synchronously, use . + /// If you would rather this happen synchronously, use . /// If you do not need to specify configuration options other than the mobile key, you can use - /// or . + /// or + /// . /// /// /// You must use one of these static factory methods to instantiate the single instance of LdClient @@ -455,8 +485,8 @@ public static LdClient Init(Configuration config, User initialUser, TimeSpan max /// about setting the context and optionally requesting a unique key for it /// a Task that resolves to the singleton LdClient instance /// - /// - /// + /// + /// public static async Task InitAsync(Configuration config, Context initialContext) { var c = CreateInstance(config, initialContext, TimeSpan.Zero); @@ -469,14 +499,14 @@ public static async Task InitAsync(Configuration config, Context initi /// asynchronously. /// /// - /// This is equivalent to , but using the + /// This is equivalent to , but using the /// type instead of . /// /// the client configuration /// the initial user attributes /// a Task that resolves to the singleton LdClient instance - /// - /// + /// + /// /// public static Task InitAsync(Configuration config, User initialUser) => InitAsync(config, Context.FromUser(initialUser)); @@ -726,7 +756,7 @@ public bool Identify(Context context, TimeSpan maxWaitTime) /// public async Task IdentifyAsync(Context context) { - Context newContext = _anonymousKeyAnonymousKeyContextDecorator.DecorateContext(context); + Context newContext = _anonymousKeyContextDecorator.DecorateContext(context); if (_config.AutoEnvAttributes) { newContext = _autoEnvContextDecorator.DecorateContext(newContext); diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index 6bbb3baa..046c4004 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,6 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,143 +13,19 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.1.111")] + + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] public partial class Resource { - + static Resource() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + public static void UpdateIdValues() { - global::LaunchDarkly.Sdk.Client.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; - global::LaunchDarkly.Sdk.Client.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; - global::LaunchDarkly.Sdk.Client.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; - global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; - global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::LaunchDarkly.Sdk.Client.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; - global::LaunchDarkly.Sdk.Client.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; - global::LaunchDarkly.Sdk.Client.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; - global::LaunchDarkly.Sdk.Client.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; - global::LaunchDarkly.Sdk.Client.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; - global::LaunchDarkly.Sdk.Client.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; - global::LaunchDarkly.Sdk.Client.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; - global::LaunchDarkly.Sdk.Client.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; - global::LaunchDarkly.Sdk.Client.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; - global::LaunchDarkly.Sdk.Client.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; - global::LaunchDarkly.Sdk.Client.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; - global::LaunchDarkly.Sdk.Client.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; - global::LaunchDarkly.Sdk.Client.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; - global::LaunchDarkly.Sdk.Client.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; - global::LaunchDarkly.Sdk.Client.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; - global::LaunchDarkly.Sdk.Client.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; - global::LaunchDarkly.Sdk.Client.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; - global::LaunchDarkly.Sdk.Client.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; - global::LaunchDarkly.Sdk.Client.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; - global::LaunchDarkly.Sdk.Client.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; - global::LaunchDarkly.Sdk.Client.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; - global::LaunchDarkly.Sdk.Client.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; - global::LaunchDarkly.Sdk.Client.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; - global::LaunchDarkly.Sdk.Client.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; - global::LaunchDarkly.Sdk.Client.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; - global::LaunchDarkly.Sdk.Client.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; - global::LaunchDarkly.Sdk.Client.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; @@ -2136,5379 +2013,5379 @@ public static void UpdateIdValues() global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; } - + public partial class Animation { - + // aapt resource value: 0x7F010000 public const int abc_fade_in = 2130771968; - + // aapt resource value: 0x7F010001 public const int abc_fade_out = 2130771969; - + // aapt resource value: 0x7F010002 public const int abc_grow_fade_in_from_bottom = 2130771970; - + // aapt resource value: 0x7F010003 public const int abc_popup_enter = 2130771971; - + // aapt resource value: 0x7F010004 public const int abc_popup_exit = 2130771972; - + // aapt resource value: 0x7F010005 public const int abc_shrink_fade_out_from_bottom = 2130771973; - + // aapt resource value: 0x7F010006 public const int abc_slide_in_bottom = 2130771974; - + // aapt resource value: 0x7F010007 public const int abc_slide_in_top = 2130771975; - + // aapt resource value: 0x7F010008 public const int abc_slide_out_bottom = 2130771976; - + // aapt resource value: 0x7F010009 public const int abc_slide_out_top = 2130771977; - + // aapt resource value: 0x7F01000A public const int design_bottom_sheet_slide_in = 2130771978; - + // aapt resource value: 0x7F01000B public const int design_bottom_sheet_slide_out = 2130771979; - + // aapt resource value: 0x7F01000C public const int design_snackbar_in = 2130771980; - + // aapt resource value: 0x7F01000D public const int design_snackbar_out = 2130771981; - + // aapt resource value: 0x7F01000E public const int EnterFromLeft = 2130771982; - + // aapt resource value: 0x7F01000F public const int EnterFromRight = 2130771983; - + // aapt resource value: 0x7F010010 public const int ExitToLeft = 2130771984; - + // aapt resource value: 0x7F010011 public const int ExitToRight = 2130771985; - + // aapt resource value: 0x7F010012 public const int tooltip_enter = 2130771986; - + // aapt resource value: 0x7F010013 public const int tooltip_exit = 2130771987; - + static Animation() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animation() { } } - + public partial class Animator { - + // aapt resource value: 0x7F020000 public const int design_appbar_state_list_animator = 2130837504; - + static Animator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animator() { } } - + public partial class Attribute { - + // aapt resource value: 0x7F030000 public const int actionBarDivider = 2130903040; - + // aapt resource value: 0x7F030001 public const int actionBarItemBackground = 2130903041; - + // aapt resource value: 0x7F030002 public const int actionBarPopupTheme = 2130903042; - + // aapt resource value: 0x7F030003 public const int actionBarSize = 2130903043; - + // aapt resource value: 0x7F030004 public const int actionBarSplitStyle = 2130903044; - + // aapt resource value: 0x7F030005 public const int actionBarStyle = 2130903045; - + // aapt resource value: 0x7F030006 public const int actionBarTabBarStyle = 2130903046; - + // aapt resource value: 0x7F030007 public const int actionBarTabStyle = 2130903047; - + // aapt resource value: 0x7F030008 public const int actionBarTabTextStyle = 2130903048; - + // aapt resource value: 0x7F030009 public const int actionBarTheme = 2130903049; - + // aapt resource value: 0x7F03000A public const int actionBarWidgetTheme = 2130903050; - + // aapt resource value: 0x7F03000B public const int actionButtonStyle = 2130903051; - + // aapt resource value: 0x7F03000C public const int actionDropDownStyle = 2130903052; - + // aapt resource value: 0x7F03000D public const int actionLayout = 2130903053; - + // aapt resource value: 0x7F03000E public const int actionMenuTextAppearance = 2130903054; - + // aapt resource value: 0x7F03000F public const int actionMenuTextColor = 2130903055; - + // aapt resource value: 0x7F030010 public const int actionModeBackground = 2130903056; - + // aapt resource value: 0x7F030011 public const int actionModeCloseButtonStyle = 2130903057; - + // aapt resource value: 0x7F030012 public const int actionModeCloseDrawable = 2130903058; - + // aapt resource value: 0x7F030013 public const int actionModeCopyDrawable = 2130903059; - + // aapt resource value: 0x7F030014 public const int actionModeCutDrawable = 2130903060; - + // aapt resource value: 0x7F030015 public const int actionModeFindDrawable = 2130903061; - + // aapt resource value: 0x7F030016 public const int actionModePasteDrawable = 2130903062; - + // aapt resource value: 0x7F030017 public const int actionModePopupWindowStyle = 2130903063; - + // aapt resource value: 0x7F030018 public const int actionModeSelectAllDrawable = 2130903064; - + // aapt resource value: 0x7F030019 public const int actionModeShareDrawable = 2130903065; - + // aapt resource value: 0x7F03001A public const int actionModeSplitBackground = 2130903066; - + // aapt resource value: 0x7F03001B public const int actionModeStyle = 2130903067; - + // aapt resource value: 0x7F03001C public const int actionModeWebSearchDrawable = 2130903068; - + // aapt resource value: 0x7F03001D public const int actionOverflowButtonStyle = 2130903069; - + // aapt resource value: 0x7F03001E public const int actionOverflowMenuStyle = 2130903070; - + // aapt resource value: 0x7F03001F public const int actionProviderClass = 2130903071; - + // aapt resource value: 0x7F030020 public const int actionViewClass = 2130903072; - + // aapt resource value: 0x7F030021 public const int activityChooserViewStyle = 2130903073; - + // aapt resource value: 0x7F030022 public const int alertDialogButtonGroupStyle = 2130903074; - + // aapt resource value: 0x7F030023 public const int alertDialogCenterButtons = 2130903075; - + // aapt resource value: 0x7F030024 public const int alertDialogStyle = 2130903076; - + // aapt resource value: 0x7F030025 public const int alertDialogTheme = 2130903077; - + // aapt resource value: 0x7F030026 public const int allowStacking = 2130903078; - + // aapt resource value: 0x7F030027 public const int alpha = 2130903079; - + // aapt resource value: 0x7F030028 public const int alphabeticModifiers = 2130903080; - + // aapt resource value: 0x7F030029 public const int arrowHeadLength = 2130903081; - + // aapt resource value: 0x7F03002A public const int arrowShaftLength = 2130903082; - + // aapt resource value: 0x7F03002B public const int autoCompleteTextViewStyle = 2130903083; - + // aapt resource value: 0x7F03002C public const int autoSizeMaxTextSize = 2130903084; - + // aapt resource value: 0x7F03002D public const int autoSizeMinTextSize = 2130903085; - + // aapt resource value: 0x7F03002E public const int autoSizePresetSizes = 2130903086; - + // aapt resource value: 0x7F03002F public const int autoSizeStepGranularity = 2130903087; - + // aapt resource value: 0x7F030030 public const int autoSizeTextType = 2130903088; - + // aapt resource value: 0x7F030031 public const int background = 2130903089; - + // aapt resource value: 0x7F030032 public const int backgroundSplit = 2130903090; - + // aapt resource value: 0x7F030033 public const int backgroundStacked = 2130903091; - + // aapt resource value: 0x7F030034 public const int backgroundTint = 2130903092; - + // aapt resource value: 0x7F030035 public const int backgroundTintMode = 2130903093; - + // aapt resource value: 0x7F030036 public const int barLength = 2130903094; - + // aapt resource value: 0x7F030037 public const int behavior_autoHide = 2130903095; - + // aapt resource value: 0x7F030038 public const int behavior_hideable = 2130903096; - + // aapt resource value: 0x7F030039 public const int behavior_overlapTop = 2130903097; - + // aapt resource value: 0x7F03003A public const int behavior_peekHeight = 2130903098; - + // aapt resource value: 0x7F03003B public const int behavior_skipCollapsed = 2130903099; - + // aapt resource value: 0x7F03003D public const int borderlessButtonStyle = 2130903101; - + // aapt resource value: 0x7F03003C public const int borderWidth = 2130903100; - + // aapt resource value: 0x7F03003E public const int bottomSheetDialogTheme = 2130903102; - + // aapt resource value: 0x7F03003F public const int bottomSheetStyle = 2130903103; - + // aapt resource value: 0x7F030040 public const int buttonBarButtonStyle = 2130903104; - + // aapt resource value: 0x7F030041 public const int buttonBarNegativeButtonStyle = 2130903105; - + // aapt resource value: 0x7F030042 public const int buttonBarNeutralButtonStyle = 2130903106; - + // aapt resource value: 0x7F030043 public const int buttonBarPositiveButtonStyle = 2130903107; - + // aapt resource value: 0x7F030044 public const int buttonBarStyle = 2130903108; - + // aapt resource value: 0x7F030045 public const int buttonGravity = 2130903109; - + // aapt resource value: 0x7F030046 public const int buttonPanelSideLayout = 2130903110; - + // aapt resource value: 0x7F030047 public const int buttonStyle = 2130903111; - + // aapt resource value: 0x7F030048 public const int buttonStyleSmall = 2130903112; - + // aapt resource value: 0x7F030049 public const int buttonTint = 2130903113; - + // aapt resource value: 0x7F03004A public const int buttonTintMode = 2130903114; - + // aapt resource value: 0x7F03004B public const int cardBackgroundColor = 2130903115; - + // aapt resource value: 0x7F03004C public const int cardCornerRadius = 2130903116; - + // aapt resource value: 0x7F03004D public const int cardElevation = 2130903117; - + // aapt resource value: 0x7F03004E public const int cardMaxElevation = 2130903118; - + // aapt resource value: 0x7F03004F public const int cardPreventCornerOverlap = 2130903119; - + // aapt resource value: 0x7F030050 public const int cardUseCompatPadding = 2130903120; - + // aapt resource value: 0x7F030051 public const int checkboxStyle = 2130903121; - + // aapt resource value: 0x7F030052 public const int checkedTextViewStyle = 2130903122; - + // aapt resource value: 0x7F030053 public const int closeIcon = 2130903123; - + // aapt resource value: 0x7F030054 public const int closeItemLayout = 2130903124; - + // aapt resource value: 0x7F030055 public const int collapseContentDescription = 2130903125; - + // aapt resource value: 0x7F030057 public const int collapsedTitleGravity = 2130903127; - + // aapt resource value: 0x7F030058 public const int collapsedTitleTextAppearance = 2130903128; - + // aapt resource value: 0x7F030056 public const int collapseIcon = 2130903126; - + // aapt resource value: 0x7F030059 public const int color = 2130903129; - + // aapt resource value: 0x7F03005A public const int colorAccent = 2130903130; - + // aapt resource value: 0x7F03005B public const int colorBackgroundFloating = 2130903131; - + // aapt resource value: 0x7F03005C public const int colorButtonNormal = 2130903132; - + // aapt resource value: 0x7F03005D public const int colorControlActivated = 2130903133; - + // aapt resource value: 0x7F03005E public const int colorControlHighlight = 2130903134; - + // aapt resource value: 0x7F03005F public const int colorControlNormal = 2130903135; - + // aapt resource value: 0x7F030060 public const int colorError = 2130903136; - + // aapt resource value: 0x7F030061 public const int colorPrimary = 2130903137; - + // aapt resource value: 0x7F030062 public const int colorPrimaryDark = 2130903138; - + // aapt resource value: 0x7F030063 public const int colorSwitchThumbNormal = 2130903139; - + // aapt resource value: 0x7F030064 public const int commitIcon = 2130903140; - + // aapt resource value: 0x7F030065 public const int contentDescription = 2130903141; - + // aapt resource value: 0x7F030066 public const int contentInsetEnd = 2130903142; - + // aapt resource value: 0x7F030067 public const int contentInsetEndWithActions = 2130903143; - + // aapt resource value: 0x7F030068 public const int contentInsetLeft = 2130903144; - + // aapt resource value: 0x7F030069 public const int contentInsetRight = 2130903145; - + // aapt resource value: 0x7F03006A public const int contentInsetStart = 2130903146; - + // aapt resource value: 0x7F03006B public const int contentInsetStartWithNavigation = 2130903147; - + // aapt resource value: 0x7F03006C public const int contentPadding = 2130903148; - + // aapt resource value: 0x7F03006D public const int contentPaddingBottom = 2130903149; - + // aapt resource value: 0x7F03006E public const int contentPaddingLeft = 2130903150; - + // aapt resource value: 0x7F03006F public const int contentPaddingRight = 2130903151; - + // aapt resource value: 0x7F030070 public const int contentPaddingTop = 2130903152; - + // aapt resource value: 0x7F030071 public const int contentScrim = 2130903153; - + // aapt resource value: 0x7F030072 public const int controlBackground = 2130903154; - + // aapt resource value: 0x7F030073 public const int counterEnabled = 2130903155; - + // aapt resource value: 0x7F030074 public const int counterMaxLength = 2130903156; - + // aapt resource value: 0x7F030075 public const int counterOverflowTextAppearance = 2130903157; - + // aapt resource value: 0x7F030076 public const int counterTextAppearance = 2130903158; - + // aapt resource value: 0x7F030077 public const int customNavigationLayout = 2130903159; - + // aapt resource value: 0x7F030078 public const int defaultQueryHint = 2130903160; - + // aapt resource value: 0x7F030079 public const int dialogPreferredPadding = 2130903161; - + // aapt resource value: 0x7F03007A public const int dialogTheme = 2130903162; - + // aapt resource value: 0x7F03007B public const int displayOptions = 2130903163; - + // aapt resource value: 0x7F03007C public const int divider = 2130903164; - + // aapt resource value: 0x7F03007D public const int dividerHorizontal = 2130903165; - + // aapt resource value: 0x7F03007E public const int dividerPadding = 2130903166; - + // aapt resource value: 0x7F03007F public const int dividerVertical = 2130903167; - + // aapt resource value: 0x7F030080 public const int drawableSize = 2130903168; - + // aapt resource value: 0x7F030081 public const int drawerArrowStyle = 2130903169; - + // aapt resource value: 0x7F030083 public const int dropdownListPreferredItemHeight = 2130903171; - + // aapt resource value: 0x7F030082 public const int dropDownListViewStyle = 2130903170; - + // aapt resource value: 0x7F030084 public const int editTextBackground = 2130903172; - + // aapt resource value: 0x7F030085 public const int editTextColor = 2130903173; - + // aapt resource value: 0x7F030086 public const int editTextStyle = 2130903174; - + // aapt resource value: 0x7F030087 public const int elevation = 2130903175; - + // aapt resource value: 0x7F030088 public const int errorEnabled = 2130903176; - + // aapt resource value: 0x7F030089 public const int errorTextAppearance = 2130903177; - + // aapt resource value: 0x7F03008A public const int expandActivityOverflowButtonDrawable = 2130903178; - + // aapt resource value: 0x7F03008B public const int expanded = 2130903179; - + // aapt resource value: 0x7F03008C public const int expandedTitleGravity = 2130903180; - + // aapt resource value: 0x7F03008D public const int expandedTitleMargin = 2130903181; - + // aapt resource value: 0x7F03008E public const int expandedTitleMarginBottom = 2130903182; - + // aapt resource value: 0x7F03008F public const int expandedTitleMarginEnd = 2130903183; - + // aapt resource value: 0x7F030090 public const int expandedTitleMarginStart = 2130903184; - + // aapt resource value: 0x7F030091 public const int expandedTitleMarginTop = 2130903185; - + // aapt resource value: 0x7F030092 public const int expandedTitleTextAppearance = 2130903186; - + // aapt resource value: 0x7F030093 public const int externalRouteEnabledDrawable = 2130903187; - + // aapt resource value: 0x7F030094 public const int fabSize = 2130903188; - + // aapt resource value: 0x7F030095 public const int fastScrollEnabled = 2130903189; - + // aapt resource value: 0x7F030096 public const int fastScrollHorizontalThumbDrawable = 2130903190; - + // aapt resource value: 0x7F030097 public const int fastScrollHorizontalTrackDrawable = 2130903191; - + // aapt resource value: 0x7F030098 public const int fastScrollVerticalThumbDrawable = 2130903192; - + // aapt resource value: 0x7F030099 public const int fastScrollVerticalTrackDrawable = 2130903193; - + // aapt resource value: 0x7F03009A public const int font = 2130903194; - + // aapt resource value: 0x7F03009B public const int fontFamily = 2130903195; - + // aapt resource value: 0x7F03009C public const int fontProviderAuthority = 2130903196; - + // aapt resource value: 0x7F03009D public const int fontProviderCerts = 2130903197; - + // aapt resource value: 0x7F03009E public const int fontProviderFetchStrategy = 2130903198; - + // aapt resource value: 0x7F03009F public const int fontProviderFetchTimeout = 2130903199; - + // aapt resource value: 0x7F0300A0 public const int fontProviderPackage = 2130903200; - + // aapt resource value: 0x7F0300A1 public const int fontProviderQuery = 2130903201; - + // aapt resource value: 0x7F0300A2 public const int fontStyle = 2130903202; - + // aapt resource value: 0x7F0300A3 public const int fontWeight = 2130903203; - + // aapt resource value: 0x7F0300A4 public const int foregroundInsidePadding = 2130903204; - + // aapt resource value: 0x7F0300A5 public const int gapBetweenBars = 2130903205; - + // aapt resource value: 0x7F0300A6 public const int goIcon = 2130903206; - + // aapt resource value: 0x7F0300A7 public const int headerLayout = 2130903207; - + // aapt resource value: 0x7F0300A8 public const int height = 2130903208; - + // aapt resource value: 0x7F0300A9 public const int hideOnContentScroll = 2130903209; - + // aapt resource value: 0x7F0300AA public const int hintAnimationEnabled = 2130903210; - + // aapt resource value: 0x7F0300AB public const int hintEnabled = 2130903211; - + // aapt resource value: 0x7F0300AC public const int hintTextAppearance = 2130903212; - + // aapt resource value: 0x7F0300AD public const int homeAsUpIndicator = 2130903213; - + // aapt resource value: 0x7F0300AE public const int homeLayout = 2130903214; - + // aapt resource value: 0x7F0300AF public const int icon = 2130903215; - + // aapt resource value: 0x7F0300B2 public const int iconifiedByDefault = 2130903218; - + // aapt resource value: 0x7F0300B0 public const int iconTint = 2130903216; - + // aapt resource value: 0x7F0300B1 public const int iconTintMode = 2130903217; - + // aapt resource value: 0x7F0300B3 public const int imageButtonStyle = 2130903219; - + // aapt resource value: 0x7F0300B4 public const int indeterminateProgressStyle = 2130903220; - + // aapt resource value: 0x7F0300B5 public const int initialActivityCount = 2130903221; - + // aapt resource value: 0x7F0300B6 public const int insetForeground = 2130903222; - + // aapt resource value: 0x7F0300B7 public const int isLightTheme = 2130903223; - + // aapt resource value: 0x7F0300B8 public const int itemBackground = 2130903224; - + // aapt resource value: 0x7F0300B9 public const int itemIconTint = 2130903225; - + // aapt resource value: 0x7F0300BA public const int itemPadding = 2130903226; - + // aapt resource value: 0x7F0300BB public const int itemTextAppearance = 2130903227; - + // aapt resource value: 0x7F0300BC public const int itemTextColor = 2130903228; - + // aapt resource value: 0x7F0300BD public const int keylines = 2130903229; - + // aapt resource value: 0x7F0300BE public const int layout = 2130903230; - + // aapt resource value: 0x7F0300BF public const int layoutManager = 2130903231; - + // aapt resource value: 0x7F0300C0 public const int layout_anchor = 2130903232; - + // aapt resource value: 0x7F0300C1 public const int layout_anchorGravity = 2130903233; - + // aapt resource value: 0x7F0300C2 public const int layout_behavior = 2130903234; - + // aapt resource value: 0x7F0300C3 public const int layout_collapseMode = 2130903235; - + // aapt resource value: 0x7F0300C4 public const int layout_collapseParallaxMultiplier = 2130903236; - + // aapt resource value: 0x7F0300C5 public const int layout_dodgeInsetEdges = 2130903237; - + // aapt resource value: 0x7F0300C6 public const int layout_insetEdge = 2130903238; - + // aapt resource value: 0x7F0300C7 public const int layout_keyline = 2130903239; - + // aapt resource value: 0x7F0300C8 public const int layout_scrollFlags = 2130903240; - + // aapt resource value: 0x7F0300C9 public const int layout_scrollInterpolator = 2130903241; - + // aapt resource value: 0x7F0300CA public const int listChoiceBackgroundIndicator = 2130903242; - + // aapt resource value: 0x7F0300CB public const int listDividerAlertDialog = 2130903243; - + // aapt resource value: 0x7F0300CC public const int listItemLayout = 2130903244; - + // aapt resource value: 0x7F0300CD public const int listLayout = 2130903245; - + // aapt resource value: 0x7F0300CE public const int listMenuViewStyle = 2130903246; - + // aapt resource value: 0x7F0300CF public const int listPopupWindowStyle = 2130903247; - + // aapt resource value: 0x7F0300D0 public const int listPreferredItemHeight = 2130903248; - + // aapt resource value: 0x7F0300D1 public const int listPreferredItemHeightLarge = 2130903249; - + // aapt resource value: 0x7F0300D2 public const int listPreferredItemHeightSmall = 2130903250; - + // aapt resource value: 0x7F0300D3 public const int listPreferredItemPaddingLeft = 2130903251; - + // aapt resource value: 0x7F0300D4 public const int listPreferredItemPaddingRight = 2130903252; - + // aapt resource value: 0x7F0300D5 public const int logo = 2130903253; - + // aapt resource value: 0x7F0300D6 public const int logoDescription = 2130903254; - + // aapt resource value: 0x7F0300D7 public const int maxActionInlineWidth = 2130903255; - + // aapt resource value: 0x7F0300D8 public const int maxButtonHeight = 2130903256; - + // aapt resource value: 0x7F0300D9 public const int measureWithLargestChild = 2130903257; - + // aapt resource value: 0x7F0300DA public const int mediaRouteAudioTrackDrawable = 2130903258; - + // aapt resource value: 0x7F0300DB public const int mediaRouteButtonStyle = 2130903259; - + // aapt resource value: 0x7F0300DC public const int mediaRouteButtonTint = 2130903260; - + // aapt resource value: 0x7F0300DD public const int mediaRouteCloseDrawable = 2130903261; - + // aapt resource value: 0x7F0300DE public const int mediaRouteControlPanelThemeOverlay = 2130903262; - + // aapt resource value: 0x7F0300DF public const int mediaRouteDefaultIconDrawable = 2130903263; - + // aapt resource value: 0x7F0300E0 public const int mediaRoutePauseDrawable = 2130903264; - + // aapt resource value: 0x7F0300E1 public const int mediaRoutePlayDrawable = 2130903265; - + // aapt resource value: 0x7F0300E2 public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - + // aapt resource value: 0x7F0300E3 public const int mediaRouteSpeakerIconDrawable = 2130903267; - + // aapt resource value: 0x7F0300E4 public const int mediaRouteStopDrawable = 2130903268; - + // aapt resource value: 0x7F0300E5 public const int mediaRouteTheme = 2130903269; - + // aapt resource value: 0x7F0300E6 public const int mediaRouteTvIconDrawable = 2130903270; - + // aapt resource value: 0x7F0300E7 public const int menu = 2130903271; - + // aapt resource value: 0x7F0300E8 public const int multiChoiceItemLayout = 2130903272; - + // aapt resource value: 0x7F0300E9 public const int navigationContentDescription = 2130903273; - + // aapt resource value: 0x7F0300EA public const int navigationIcon = 2130903274; - + // aapt resource value: 0x7F0300EB public const int navigationMode = 2130903275; - + // aapt resource value: 0x7F0300EC public const int numericModifiers = 2130903276; - + // aapt resource value: 0x7F0300ED public const int overlapAnchor = 2130903277; - + // aapt resource value: 0x7F0300EE public const int paddingBottomNoButtons = 2130903278; - + // aapt resource value: 0x7F0300EF public const int paddingEnd = 2130903279; - + // aapt resource value: 0x7F0300F0 public const int paddingStart = 2130903280; - + // aapt resource value: 0x7F0300F1 public const int paddingTopNoTitle = 2130903281; - + // aapt resource value: 0x7F0300F2 public const int panelBackground = 2130903282; - + // aapt resource value: 0x7F0300F3 public const int panelMenuListTheme = 2130903283; - + // aapt resource value: 0x7F0300F4 public const int panelMenuListWidth = 2130903284; - + // aapt resource value: 0x7F0300F5 public const int passwordToggleContentDescription = 2130903285; - + // aapt resource value: 0x7F0300F6 public const int passwordToggleDrawable = 2130903286; - + // aapt resource value: 0x7F0300F7 public const int passwordToggleEnabled = 2130903287; - + // aapt resource value: 0x7F0300F8 public const int passwordToggleTint = 2130903288; - + // aapt resource value: 0x7F0300F9 public const int passwordToggleTintMode = 2130903289; - + // aapt resource value: 0x7F0300FA public const int popupMenuStyle = 2130903290; - + // aapt resource value: 0x7F0300FB public const int popupTheme = 2130903291; - + // aapt resource value: 0x7F0300FC public const int popupWindowStyle = 2130903292; - + // aapt resource value: 0x7F0300FD public const int preserveIconSpacing = 2130903293; - + // aapt resource value: 0x7F0300FE public const int pressedTranslationZ = 2130903294; - + // aapt resource value: 0x7F0300FF public const int progressBarPadding = 2130903295; - + // aapt resource value: 0x7F030100 public const int progressBarStyle = 2130903296; - + // aapt resource value: 0x7F030101 public const int queryBackground = 2130903297; - + // aapt resource value: 0x7F030102 public const int queryHint = 2130903298; - + // aapt resource value: 0x7F030103 public const int radioButtonStyle = 2130903299; - + // aapt resource value: 0x7F030104 public const int ratingBarStyle = 2130903300; - + // aapt resource value: 0x7F030105 public const int ratingBarStyleIndicator = 2130903301; - + // aapt resource value: 0x7F030106 public const int ratingBarStyleSmall = 2130903302; - + // aapt resource value: 0x7F030107 public const int reverseLayout = 2130903303; - + // aapt resource value: 0x7F030108 public const int rippleColor = 2130903304; - + // aapt resource value: 0x7F030109 public const int scrimAnimationDuration = 2130903305; - + // aapt resource value: 0x7F03010A public const int scrimVisibleHeightTrigger = 2130903306; - + // aapt resource value: 0x7F03010B public const int searchHintIcon = 2130903307; - + // aapt resource value: 0x7F03010C public const int searchIcon = 2130903308; - + // aapt resource value: 0x7F03010D public const int searchViewStyle = 2130903309; - + // aapt resource value: 0x7F03010E public const int seekBarStyle = 2130903310; - + // aapt resource value: 0x7F03010F public const int selectableItemBackground = 2130903311; - + // aapt resource value: 0x7F030110 public const int selectableItemBackgroundBorderless = 2130903312; - + // aapt resource value: 0x7F030111 public const int showAsAction = 2130903313; - + // aapt resource value: 0x7F030112 public const int showDividers = 2130903314; - + // aapt resource value: 0x7F030113 public const int showText = 2130903315; - + // aapt resource value: 0x7F030114 public const int showTitle = 2130903316; - + // aapt resource value: 0x7F030115 public const int singleChoiceItemLayout = 2130903317; - + // aapt resource value: 0x7F030116 public const int spanCount = 2130903318; - + // aapt resource value: 0x7F030117 public const int spinBars = 2130903319; - + // aapt resource value: 0x7F030118 public const int spinnerDropDownItemStyle = 2130903320; - + // aapt resource value: 0x7F030119 public const int spinnerStyle = 2130903321; - + // aapt resource value: 0x7F03011A public const int splitTrack = 2130903322; - + // aapt resource value: 0x7F03011B public const int srcCompat = 2130903323; - + // aapt resource value: 0x7F03011C public const int stackFromEnd = 2130903324; - + // aapt resource value: 0x7F03011D public const int state_above_anchor = 2130903325; - + // aapt resource value: 0x7F03011E public const int state_collapsed = 2130903326; - + // aapt resource value: 0x7F03011F public const int state_collapsible = 2130903327; - + // aapt resource value: 0x7F030120 public const int statusBarBackground = 2130903328; - + // aapt resource value: 0x7F030121 public const int statusBarScrim = 2130903329; - + // aapt resource value: 0x7F030122 public const int subMenuArrow = 2130903330; - + // aapt resource value: 0x7F030123 public const int submitBackground = 2130903331; - + // aapt resource value: 0x7F030124 public const int subtitle = 2130903332; - + // aapt resource value: 0x7F030125 public const int subtitleTextAppearance = 2130903333; - + // aapt resource value: 0x7F030126 public const int subtitleTextColor = 2130903334; - + // aapt resource value: 0x7F030127 public const int subtitleTextStyle = 2130903335; - + // aapt resource value: 0x7F030128 public const int suggestionRowLayout = 2130903336; - + // aapt resource value: 0x7F030129 public const int switchMinWidth = 2130903337; - + // aapt resource value: 0x7F03012A public const int switchPadding = 2130903338; - + // aapt resource value: 0x7F03012B public const int switchStyle = 2130903339; - + // aapt resource value: 0x7F03012C public const int switchTextAppearance = 2130903340; - + // aapt resource value: 0x7F03012D public const int tabBackground = 2130903341; - + // aapt resource value: 0x7F03012E public const int tabContentStart = 2130903342; - + // aapt resource value: 0x7F03012F public const int tabGravity = 2130903343; - + // aapt resource value: 0x7F030130 public const int tabIndicatorColor = 2130903344; - + // aapt resource value: 0x7F030131 public const int tabIndicatorHeight = 2130903345; - + // aapt resource value: 0x7F030132 public const int tabMaxWidth = 2130903346; - + // aapt resource value: 0x7F030133 public const int tabMinWidth = 2130903347; - + // aapt resource value: 0x7F030134 public const int tabMode = 2130903348; - + // aapt resource value: 0x7F030135 public const int tabPadding = 2130903349; - + // aapt resource value: 0x7F030136 public const int tabPaddingBottom = 2130903350; - + // aapt resource value: 0x7F030137 public const int tabPaddingEnd = 2130903351; - + // aapt resource value: 0x7F030138 public const int tabPaddingStart = 2130903352; - + // aapt resource value: 0x7F030139 public const int tabPaddingTop = 2130903353; - + // aapt resource value: 0x7F03013A public const int tabSelectedTextColor = 2130903354; - + // aapt resource value: 0x7F03013B public const int tabTextAppearance = 2130903355; - + // aapt resource value: 0x7F03013C public const int tabTextColor = 2130903356; - + // aapt resource value: 0x7F03013D public const int textAllCaps = 2130903357; - + // aapt resource value: 0x7F03013E public const int textAppearanceLargePopupMenu = 2130903358; - + // aapt resource value: 0x7F03013F public const int textAppearanceListItem = 2130903359; - + // aapt resource value: 0x7F030140 public const int textAppearanceListItemSecondary = 2130903360; - + // aapt resource value: 0x7F030141 public const int textAppearanceListItemSmall = 2130903361; - + // aapt resource value: 0x7F030142 public const int textAppearancePopupMenuHeader = 2130903362; - + // aapt resource value: 0x7F030143 public const int textAppearanceSearchResultSubtitle = 2130903363; - + // aapt resource value: 0x7F030144 public const int textAppearanceSearchResultTitle = 2130903364; - + // aapt resource value: 0x7F030145 public const int textAppearanceSmallPopupMenu = 2130903365; - + // aapt resource value: 0x7F030146 public const int textColorAlertDialogListItem = 2130903366; - + // aapt resource value: 0x7F030147 public const int textColorError = 2130903367; - + // aapt resource value: 0x7F030148 public const int textColorSearchUrl = 2130903368; - + // aapt resource value: 0x7F030149 public const int theme = 2130903369; - + // aapt resource value: 0x7F03014A public const int thickness = 2130903370; - + // aapt resource value: 0x7F03014B public const int thumbTextPadding = 2130903371; - + // aapt resource value: 0x7F03014C public const int thumbTint = 2130903372; - + // aapt resource value: 0x7F03014D public const int thumbTintMode = 2130903373; - + // aapt resource value: 0x7F03014E public const int tickMark = 2130903374; - + // aapt resource value: 0x7F03014F public const int tickMarkTint = 2130903375; - + // aapt resource value: 0x7F030150 public const int tickMarkTintMode = 2130903376; - + // aapt resource value: 0x7F030151 public const int tint = 2130903377; - + // aapt resource value: 0x7F030152 public const int tintMode = 2130903378; - + // aapt resource value: 0x7F030153 public const int title = 2130903379; - + // aapt resource value: 0x7F030154 public const int titleEnabled = 2130903380; - + // aapt resource value: 0x7F030155 public const int titleMargin = 2130903381; - + // aapt resource value: 0x7F030156 public const int titleMarginBottom = 2130903382; - + // aapt resource value: 0x7F030157 public const int titleMarginEnd = 2130903383; - + // aapt resource value: 0x7F03015A public const int titleMargins = 2130903386; - + // aapt resource value: 0x7F030158 public const int titleMarginStart = 2130903384; - + // aapt resource value: 0x7F030159 public const int titleMarginTop = 2130903385; - + // aapt resource value: 0x7F03015B public const int titleTextAppearance = 2130903387; - + // aapt resource value: 0x7F03015C public const int titleTextColor = 2130903388; - + // aapt resource value: 0x7F03015D public const int titleTextStyle = 2130903389; - + // aapt resource value: 0x7F03015E public const int toolbarId = 2130903390; - + // aapt resource value: 0x7F03015F public const int toolbarNavigationButtonStyle = 2130903391; - + // aapt resource value: 0x7F030160 public const int toolbarStyle = 2130903392; - + // aapt resource value: 0x7F030161 public const int tooltipForegroundColor = 2130903393; - + // aapt resource value: 0x7F030162 public const int tooltipFrameBackground = 2130903394; - + // aapt resource value: 0x7F030163 public const int tooltipText = 2130903395; - + // aapt resource value: 0x7F030164 public const int track = 2130903396; - + // aapt resource value: 0x7F030165 public const int trackTint = 2130903397; - + // aapt resource value: 0x7F030166 public const int trackTintMode = 2130903398; - + // aapt resource value: 0x7F030167 public const int useCompatPadding = 2130903399; - + // aapt resource value: 0x7F030168 public const int voiceIcon = 2130903400; - + // aapt resource value: 0x7F030169 public const int windowActionBar = 2130903401; - + // aapt resource value: 0x7F03016A public const int windowActionBarOverlay = 2130903402; - + // aapt resource value: 0x7F03016B public const int windowActionModeOverlay = 2130903403; - + // aapt resource value: 0x7F03016C public const int windowFixedHeightMajor = 2130903404; - + // aapt resource value: 0x7F03016D public const int windowFixedHeightMinor = 2130903405; - + // aapt resource value: 0x7F03016E public const int windowFixedWidthMajor = 2130903406; - + // aapt resource value: 0x7F03016F public const int windowFixedWidthMinor = 2130903407; - + // aapt resource value: 0x7F030170 public const int windowMinWidthMajor = 2130903408; - + // aapt resource value: 0x7F030171 public const int windowMinWidthMinor = 2130903409; - + // aapt resource value: 0x7F030172 public const int windowNoTitle = 2130903410; - + static Attribute() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Attribute() { } } - + public partial class Boolean { - + // aapt resource value: 0x7F040000 public const int abc_action_bar_embed_tabs = 2130968576; - + // aapt resource value: 0x7F040001 public const int abc_allow_stacked_button_bar = 2130968577; - + // aapt resource value: 0x7F040002 public const int abc_config_actionMenuItemAllCaps = 2130968578; - + // aapt resource value: 0x7F040003 public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - + // aapt resource value: 0x7F040004 public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; - + static Boolean() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Boolean() { } } - + public partial class Color { - + // aapt resource value: 0x7F050000 public const int abc_background_cache_hint_selector_material_dark = 2131034112; - + // aapt resource value: 0x7F050001 public const int abc_background_cache_hint_selector_material_light = 2131034113; - + // aapt resource value: 0x7F050002 public const int abc_btn_colored_borderless_text_material = 2131034114; - + // aapt resource value: 0x7F050003 public const int abc_btn_colored_text_material = 2131034115; - + // aapt resource value: 0x7F050004 public const int abc_color_highlight_material = 2131034116; - + // aapt resource value: 0x7F050005 public const int abc_hint_foreground_material_dark = 2131034117; - + // aapt resource value: 0x7F050006 public const int abc_hint_foreground_material_light = 2131034118; - + // aapt resource value: 0x7F050007 public const int abc_input_method_navigation_guard = 2131034119; - + // aapt resource value: 0x7F050008 public const int abc_primary_text_disable_only_material_dark = 2131034120; - + // aapt resource value: 0x7F050009 public const int abc_primary_text_disable_only_material_light = 2131034121; - + // aapt resource value: 0x7F05000A public const int abc_primary_text_material_dark = 2131034122; - + // aapt resource value: 0x7F05000B public const int abc_primary_text_material_light = 2131034123; - + // aapt resource value: 0x7F05000C public const int abc_search_url_text = 2131034124; - + // aapt resource value: 0x7F05000D public const int abc_search_url_text_normal = 2131034125; - + // aapt resource value: 0x7F05000E public const int abc_search_url_text_pressed = 2131034126; - + // aapt resource value: 0x7F05000F public const int abc_search_url_text_selected = 2131034127; - + // aapt resource value: 0x7F050010 public const int abc_secondary_text_material_dark = 2131034128; - + // aapt resource value: 0x7F050011 public const int abc_secondary_text_material_light = 2131034129; - + // aapt resource value: 0x7F050012 public const int abc_tint_btn_checkable = 2131034130; - + // aapt resource value: 0x7F050013 public const int abc_tint_default = 2131034131; - + // aapt resource value: 0x7F050014 public const int abc_tint_edittext = 2131034132; - + // aapt resource value: 0x7F050015 public const int abc_tint_seek_thumb = 2131034133; - + // aapt resource value: 0x7F050016 public const int abc_tint_spinner = 2131034134; - + // aapt resource value: 0x7F050017 public const int abc_tint_switch_track = 2131034135; - + // aapt resource value: 0x7F050018 public const int accent_material_dark = 2131034136; - + // aapt resource value: 0x7F050019 public const int accent_material_light = 2131034137; - + // aapt resource value: 0x7F05001A public const int background_floating_material_dark = 2131034138; - + // aapt resource value: 0x7F05001B public const int background_floating_material_light = 2131034139; - + // aapt resource value: 0x7F05001C public const int background_material_dark = 2131034140; - + // aapt resource value: 0x7F05001D public const int background_material_light = 2131034141; - + // aapt resource value: 0x7F05001E public const int bright_foreground_disabled_material_dark = 2131034142; - + // aapt resource value: 0x7F05001F public const int bright_foreground_disabled_material_light = 2131034143; - + // aapt resource value: 0x7F050020 public const int bright_foreground_inverse_material_dark = 2131034144; - + // aapt resource value: 0x7F050021 public const int bright_foreground_inverse_material_light = 2131034145; - + // aapt resource value: 0x7F050022 public const int bright_foreground_material_dark = 2131034146; - + // aapt resource value: 0x7F050023 public const int bright_foreground_material_light = 2131034147; - + // aapt resource value: 0x7F050024 public const int button_material_dark = 2131034148; - + // aapt resource value: 0x7F050025 public const int button_material_light = 2131034149; - + // aapt resource value: 0x7F050026 public const int cardview_dark_background = 2131034150; - + // aapt resource value: 0x7F050027 public const int cardview_light_background = 2131034151; - + // aapt resource value: 0x7F050028 public const int cardview_shadow_end_color = 2131034152; - + // aapt resource value: 0x7F050029 public const int cardview_shadow_start_color = 2131034153; - + // aapt resource value: 0x7F05002A public const int colorAccent = 2131034154; - + // aapt resource value: 0x7F05002B public const int colorPrimary = 2131034155; - + // aapt resource value: 0x7F05002C public const int colorPrimaryDark = 2131034156; - + // aapt resource value: 0x7F05002D public const int design_bottom_navigation_shadow_color = 2131034157; - + // aapt resource value: 0x7F05002E public const int design_error = 2131034158; - + // aapt resource value: 0x7F05002F public const int design_fab_shadow_end_color = 2131034159; - + // aapt resource value: 0x7F050030 public const int design_fab_shadow_mid_color = 2131034160; - + // aapt resource value: 0x7F050031 public const int design_fab_shadow_start_color = 2131034161; - + // aapt resource value: 0x7F050032 public const int design_fab_stroke_end_inner_color = 2131034162; - + // aapt resource value: 0x7F050033 public const int design_fab_stroke_end_outer_color = 2131034163; - + // aapt resource value: 0x7F050034 public const int design_fab_stroke_top_inner_color = 2131034164; - + // aapt resource value: 0x7F050035 public const int design_fab_stroke_top_outer_color = 2131034165; - + // aapt resource value: 0x7F050036 public const int design_snackbar_background_color = 2131034166; - + // aapt resource value: 0x7F050037 public const int design_tint_password_toggle = 2131034167; - + // aapt resource value: 0x7F050038 public const int dim_foreground_disabled_material_dark = 2131034168; - + // aapt resource value: 0x7F050039 public const int dim_foreground_disabled_material_light = 2131034169; - + // aapt resource value: 0x7F05003A public const int dim_foreground_material_dark = 2131034170; - + // aapt resource value: 0x7F05003B public const int dim_foreground_material_light = 2131034171; - + // aapt resource value: 0x7F05003C public const int error_color_material = 2131034172; - + // aapt resource value: 0x7F05003D public const int foreground_material_dark = 2131034173; - + // aapt resource value: 0x7F05003E public const int foreground_material_light = 2131034174; - + // aapt resource value: 0x7F05003F public const int highlighted_text_material_dark = 2131034175; - + // aapt resource value: 0x7F050040 public const int highlighted_text_material_light = 2131034176; - + // aapt resource value: 0x7F050041 public const int ic_launcher_background = 2131034177; - + // aapt resource value: 0x7F050042 public const int material_blue_grey_800 = 2131034178; - + // aapt resource value: 0x7F050043 public const int material_blue_grey_900 = 2131034179; - + // aapt resource value: 0x7F050044 public const int material_blue_grey_950 = 2131034180; - + // aapt resource value: 0x7F050045 public const int material_deep_teal_200 = 2131034181; - + // aapt resource value: 0x7F050046 public const int material_deep_teal_500 = 2131034182; - + // aapt resource value: 0x7F050047 public const int material_grey_100 = 2131034183; - + // aapt resource value: 0x7F050048 public const int material_grey_300 = 2131034184; - + // aapt resource value: 0x7F050049 public const int material_grey_50 = 2131034185; - + // aapt resource value: 0x7F05004A public const int material_grey_600 = 2131034186; - + // aapt resource value: 0x7F05004B public const int material_grey_800 = 2131034187; - + // aapt resource value: 0x7F05004C public const int material_grey_850 = 2131034188; - + // aapt resource value: 0x7F05004D public const int material_grey_900 = 2131034189; - + // aapt resource value: 0x7F05004E public const int notification_action_color_filter = 2131034190; - + // aapt resource value: 0x7F05004F public const int notification_icon_bg_color = 2131034191; - + // aapt resource value: 0x7F050050 public const int notification_material_background_media_default_color = 2131034192; - + // aapt resource value: 0x7F050051 public const int primary_dark_material_dark = 2131034193; - + // aapt resource value: 0x7F050052 public const int primary_dark_material_light = 2131034194; - + // aapt resource value: 0x7F050053 public const int primary_material_dark = 2131034195; - + // aapt resource value: 0x7F050054 public const int primary_material_light = 2131034196; - + // aapt resource value: 0x7F050055 public const int primary_text_default_material_dark = 2131034197; - + // aapt resource value: 0x7F050056 public const int primary_text_default_material_light = 2131034198; - + // aapt resource value: 0x7F050057 public const int primary_text_disabled_material_dark = 2131034199; - + // aapt resource value: 0x7F050058 public const int primary_text_disabled_material_light = 2131034200; - + // aapt resource value: 0x7F050059 public const int ripple_material_dark = 2131034201; - + // aapt resource value: 0x7F05005A public const int ripple_material_light = 2131034202; - + // aapt resource value: 0x7F05005B public const int secondary_text_default_material_dark = 2131034203; - + // aapt resource value: 0x7F05005C public const int secondary_text_default_material_light = 2131034204; - + // aapt resource value: 0x7F05005D public const int secondary_text_disabled_material_dark = 2131034205; - + // aapt resource value: 0x7F05005E public const int secondary_text_disabled_material_light = 2131034206; - + // aapt resource value: 0x7F05005F public const int switch_thumb_disabled_material_dark = 2131034207; - + // aapt resource value: 0x7F050060 public const int switch_thumb_disabled_material_light = 2131034208; - + // aapt resource value: 0x7F050061 public const int switch_thumb_material_dark = 2131034209; - + // aapt resource value: 0x7F050062 public const int switch_thumb_material_light = 2131034210; - + // aapt resource value: 0x7F050063 public const int switch_thumb_normal_material_dark = 2131034211; - + // aapt resource value: 0x7F050064 public const int switch_thumb_normal_material_light = 2131034212; - + // aapt resource value: 0x7F050065 public const int tooltip_background_dark = 2131034213; - + // aapt resource value: 0x7F050066 public const int tooltip_background_light = 2131034214; - + static Color() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Color() { } } - + public partial class Dimension { - + // aapt resource value: 0x7F060000 public const int abc_action_bar_content_inset_material = 2131099648; - + // aapt resource value: 0x7F060001 public const int abc_action_bar_content_inset_with_nav = 2131099649; - + // aapt resource value: 0x7F060002 public const int abc_action_bar_default_height_material = 2131099650; - + // aapt resource value: 0x7F060003 public const int abc_action_bar_default_padding_end_material = 2131099651; - + // aapt resource value: 0x7F060004 public const int abc_action_bar_default_padding_start_material = 2131099652; - + // aapt resource value: 0x7F060005 public const int abc_action_bar_elevation_material = 2131099653; - + // aapt resource value: 0x7F060006 public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - + // aapt resource value: 0x7F060007 public const int abc_action_bar_overflow_padding_end_material = 2131099655; - + // aapt resource value: 0x7F060008 public const int abc_action_bar_overflow_padding_start_material = 2131099656; - + // aapt resource value: 0x7F060009 public const int abc_action_bar_progress_bar_size = 2131099657; - + // aapt resource value: 0x7F06000A public const int abc_action_bar_stacked_max_height = 2131099658; - + // aapt resource value: 0x7F06000B public const int abc_action_bar_stacked_tab_max_width = 2131099659; - + // aapt resource value: 0x7F06000C public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - + // aapt resource value: 0x7F06000D public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - + // aapt resource value: 0x7F06000E public const int abc_action_button_min_height_material = 2131099662; - + // aapt resource value: 0x7F06000F public const int abc_action_button_min_width_material = 2131099663; - + // aapt resource value: 0x7F060010 public const int abc_action_button_min_width_overflow_material = 2131099664; - + // aapt resource value: 0x7F060011 public const int abc_alert_dialog_button_bar_height = 2131099665; - + // aapt resource value: 0x7F060012 public const int abc_button_inset_horizontal_material = 2131099666; - + // aapt resource value: 0x7F060013 public const int abc_button_inset_vertical_material = 2131099667; - + // aapt resource value: 0x7F060014 public const int abc_button_padding_horizontal_material = 2131099668; - + // aapt resource value: 0x7F060015 public const int abc_button_padding_vertical_material = 2131099669; - + // aapt resource value: 0x7F060016 public const int abc_cascading_menus_min_smallest_width = 2131099670; - + // aapt resource value: 0x7F060017 public const int abc_config_prefDialogWidth = 2131099671; - + // aapt resource value: 0x7F060018 public const int abc_control_corner_material = 2131099672; - + // aapt resource value: 0x7F060019 public const int abc_control_inset_material = 2131099673; - + // aapt resource value: 0x7F06001A public const int abc_control_padding_material = 2131099674; - + // aapt resource value: 0x7F06001B public const int abc_dialog_fixed_height_major = 2131099675; - + // aapt resource value: 0x7F06001C public const int abc_dialog_fixed_height_minor = 2131099676; - + // aapt resource value: 0x7F06001D public const int abc_dialog_fixed_width_major = 2131099677; - + // aapt resource value: 0x7F06001E public const int abc_dialog_fixed_width_minor = 2131099678; - + // aapt resource value: 0x7F06001F public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - + // aapt resource value: 0x7F060020 public const int abc_dialog_list_padding_top_no_title = 2131099680; - + // aapt resource value: 0x7F060021 public const int abc_dialog_min_width_major = 2131099681; - + // aapt resource value: 0x7F060022 public const int abc_dialog_min_width_minor = 2131099682; - + // aapt resource value: 0x7F060023 public const int abc_dialog_padding_material = 2131099683; - + // aapt resource value: 0x7F060024 public const int abc_dialog_padding_top_material = 2131099684; - + // aapt resource value: 0x7F060025 public const int abc_dialog_title_divider_material = 2131099685; - + // aapt resource value: 0x7F060026 public const int abc_disabled_alpha_material_dark = 2131099686; - + // aapt resource value: 0x7F060027 public const int abc_disabled_alpha_material_light = 2131099687; - + // aapt resource value: 0x7F060028 public const int abc_dropdownitem_icon_width = 2131099688; - + // aapt resource value: 0x7F060029 public const int abc_dropdownitem_text_padding_left = 2131099689; - + // aapt resource value: 0x7F06002A public const int abc_dropdownitem_text_padding_right = 2131099690; - + // aapt resource value: 0x7F06002B public const int abc_edit_text_inset_bottom_material = 2131099691; - + // aapt resource value: 0x7F06002C public const int abc_edit_text_inset_horizontal_material = 2131099692; - + // aapt resource value: 0x7F06002D public const int abc_edit_text_inset_top_material = 2131099693; - + // aapt resource value: 0x7F06002E public const int abc_floating_window_z = 2131099694; - + // aapt resource value: 0x7F06002F public const int abc_list_item_padding_horizontal_material = 2131099695; - + // aapt resource value: 0x7F060030 public const int abc_panel_menu_list_width = 2131099696; - + // aapt resource value: 0x7F060031 public const int abc_progress_bar_height_material = 2131099697; - + // aapt resource value: 0x7F060032 public const int abc_search_view_preferred_height = 2131099698; - + // aapt resource value: 0x7F060033 public const int abc_search_view_preferred_width = 2131099699; - + // aapt resource value: 0x7F060034 public const int abc_seekbar_track_background_height_material = 2131099700; - + // aapt resource value: 0x7F060035 public const int abc_seekbar_track_progress_height_material = 2131099701; - + // aapt resource value: 0x7F060036 public const int abc_select_dialog_padding_start_material = 2131099702; - + // aapt resource value: 0x7F060037 public const int abc_switch_padding = 2131099703; - + // aapt resource value: 0x7F060038 public const int abc_text_size_body_1_material = 2131099704; - + // aapt resource value: 0x7F060039 public const int abc_text_size_body_2_material = 2131099705; - + // aapt resource value: 0x7F06003A public const int abc_text_size_button_material = 2131099706; - + // aapt resource value: 0x7F06003B public const int abc_text_size_caption_material = 2131099707; - + // aapt resource value: 0x7F06003C public const int abc_text_size_display_1_material = 2131099708; - + // aapt resource value: 0x7F06003D public const int abc_text_size_display_2_material = 2131099709; - + // aapt resource value: 0x7F06003E public const int abc_text_size_display_3_material = 2131099710; - + // aapt resource value: 0x7F06003F public const int abc_text_size_display_4_material = 2131099711; - + // aapt resource value: 0x7F060040 public const int abc_text_size_headline_material = 2131099712; - + // aapt resource value: 0x7F060041 public const int abc_text_size_large_material = 2131099713; - + // aapt resource value: 0x7F060042 public const int abc_text_size_medium_material = 2131099714; - + // aapt resource value: 0x7F060043 public const int abc_text_size_menu_header_material = 2131099715; - + // aapt resource value: 0x7F060044 public const int abc_text_size_menu_material = 2131099716; - + // aapt resource value: 0x7F060045 public const int abc_text_size_small_material = 2131099717; - + // aapt resource value: 0x7F060046 public const int abc_text_size_subhead_material = 2131099718; - + // aapt resource value: 0x7F060047 public const int abc_text_size_subtitle_material_toolbar = 2131099719; - + // aapt resource value: 0x7F060048 public const int abc_text_size_title_material = 2131099720; - + // aapt resource value: 0x7F060049 public const int abc_text_size_title_material_toolbar = 2131099721; - + // aapt resource value: 0x7F06004A public const int cardview_compat_inset_shadow = 2131099722; - + // aapt resource value: 0x7F06004B public const int cardview_default_elevation = 2131099723; - + // aapt resource value: 0x7F06004C public const int cardview_default_radius = 2131099724; - + // aapt resource value: 0x7F06004D public const int compat_button_inset_horizontal_material = 2131099725; - + // aapt resource value: 0x7F06004E public const int compat_button_inset_vertical_material = 2131099726; - + // aapt resource value: 0x7F06004F public const int compat_button_padding_horizontal_material = 2131099727; - + // aapt resource value: 0x7F060050 public const int compat_button_padding_vertical_material = 2131099728; - + // aapt resource value: 0x7F060051 public const int compat_control_corner_material = 2131099729; - + // aapt resource value: 0x7F060052 public const int design_appbar_elevation = 2131099730; - + // aapt resource value: 0x7F060053 public const int design_bottom_navigation_active_item_max_width = 2131099731; - + // aapt resource value: 0x7F060054 public const int design_bottom_navigation_active_text_size = 2131099732; - + // aapt resource value: 0x7F060055 public const int design_bottom_navigation_elevation = 2131099733; - + // aapt resource value: 0x7F060056 public const int design_bottom_navigation_height = 2131099734; - + // aapt resource value: 0x7F060057 public const int design_bottom_navigation_item_max_width = 2131099735; - + // aapt resource value: 0x7F060058 public const int design_bottom_navigation_item_min_width = 2131099736; - + // aapt resource value: 0x7F060059 public const int design_bottom_navigation_margin = 2131099737; - + // aapt resource value: 0x7F06005A public const int design_bottom_navigation_shadow_height = 2131099738; - + // aapt resource value: 0x7F06005B public const int design_bottom_navigation_text_size = 2131099739; - + // aapt resource value: 0x7F06005C public const int design_bottom_sheet_modal_elevation = 2131099740; - + // aapt resource value: 0x7F06005D public const int design_bottom_sheet_peek_height_min = 2131099741; - + // aapt resource value: 0x7F06005E public const int design_fab_border_width = 2131099742; - + // aapt resource value: 0x7F06005F public const int design_fab_elevation = 2131099743; - + // aapt resource value: 0x7F060060 public const int design_fab_image_size = 2131099744; - + // aapt resource value: 0x7F060061 public const int design_fab_size_mini = 2131099745; - + // aapt resource value: 0x7F060062 public const int design_fab_size_normal = 2131099746; - + // aapt resource value: 0x7F060063 public const int design_fab_translation_z_pressed = 2131099747; - + // aapt resource value: 0x7F060064 public const int design_navigation_elevation = 2131099748; - + // aapt resource value: 0x7F060065 public const int design_navigation_icon_padding = 2131099749; - + // aapt resource value: 0x7F060066 public const int design_navigation_icon_size = 2131099750; - + // aapt resource value: 0x7F060067 public const int design_navigation_max_width = 2131099751; - + // aapt resource value: 0x7F060068 public const int design_navigation_padding_bottom = 2131099752; - + // aapt resource value: 0x7F060069 public const int design_navigation_separator_vertical_padding = 2131099753; - + // aapt resource value: 0x7F06006A public const int design_snackbar_action_inline_max_width = 2131099754; - + // aapt resource value: 0x7F06006B public const int design_snackbar_background_corner_radius = 2131099755; - + // aapt resource value: 0x7F06006C public const int design_snackbar_elevation = 2131099756; - + // aapt resource value: 0x7F06006D public const int design_snackbar_extra_spacing_horizontal = 2131099757; - + // aapt resource value: 0x7F06006E public const int design_snackbar_max_width = 2131099758; - + // aapt resource value: 0x7F06006F public const int design_snackbar_min_width = 2131099759; - + // aapt resource value: 0x7F060070 public const int design_snackbar_padding_horizontal = 2131099760; - + // aapt resource value: 0x7F060071 public const int design_snackbar_padding_vertical = 2131099761; - + // aapt resource value: 0x7F060072 public const int design_snackbar_padding_vertical_2lines = 2131099762; - + // aapt resource value: 0x7F060073 public const int design_snackbar_text_size = 2131099763; - + // aapt resource value: 0x7F060074 public const int design_tab_max_width = 2131099764; - + // aapt resource value: 0x7F060075 public const int design_tab_scrollable_min_width = 2131099765; - + // aapt resource value: 0x7F060076 public const int design_tab_text_size = 2131099766; - + // aapt resource value: 0x7F060077 public const int design_tab_text_size_2line = 2131099767; - + // aapt resource value: 0x7F060078 public const int disabled_alpha_material_dark = 2131099768; - + // aapt resource value: 0x7F060079 public const int disabled_alpha_material_light = 2131099769; - + // aapt resource value: 0x7F06007A public const int fastscroll_default_thickness = 2131099770; - + // aapt resource value: 0x7F06007B public const int fastscroll_margin = 2131099771; - + // aapt resource value: 0x7F06007C public const int fastscroll_minimum_range = 2131099772; - + // aapt resource value: 0x7F06007D public const int highlight_alpha_material_colored = 2131099773; - + // aapt resource value: 0x7F06007E public const int highlight_alpha_material_dark = 2131099774; - + // aapt resource value: 0x7F06007F public const int highlight_alpha_material_light = 2131099775; - + // aapt resource value: 0x7F060080 public const int hint_alpha_material_dark = 2131099776; - + // aapt resource value: 0x7F060081 public const int hint_alpha_material_light = 2131099777; - + // aapt resource value: 0x7F060082 public const int hint_pressed_alpha_material_dark = 2131099778; - + // aapt resource value: 0x7F060083 public const int hint_pressed_alpha_material_light = 2131099779; - + // aapt resource value: 0x7F060084 public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - + // aapt resource value: 0x7F060085 public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - + // aapt resource value: 0x7F060086 public const int item_touch_helper_swipe_escape_velocity = 2131099782; - + // aapt resource value: 0x7F060087 public const int mr_controller_volume_group_list_item_height = 2131099783; - + // aapt resource value: 0x7F060088 public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - + // aapt resource value: 0x7F060089 public const int mr_controller_volume_group_list_max_height = 2131099785; - + // aapt resource value: 0x7F06008A public const int mr_controller_volume_group_list_padding_top = 2131099786; - + // aapt resource value: 0x7F06008B public const int mr_dialog_fixed_width_major = 2131099787; - + // aapt resource value: 0x7F06008C public const int mr_dialog_fixed_width_minor = 2131099788; - + // aapt resource value: 0x7F06008D public const int notification_action_icon_size = 2131099789; - + // aapt resource value: 0x7F06008E public const int notification_action_text_size = 2131099790; - + // aapt resource value: 0x7F06008F public const int notification_big_circle_margin = 2131099791; - + // aapt resource value: 0x7F060090 public const int notification_content_margin_start = 2131099792; - + // aapt resource value: 0x7F060091 public const int notification_large_icon_height = 2131099793; - + // aapt resource value: 0x7F060092 public const int notification_large_icon_width = 2131099794; - + // aapt resource value: 0x7F060093 public const int notification_main_column_padding_top = 2131099795; - + // aapt resource value: 0x7F060094 public const int notification_media_narrow_margin = 2131099796; - + // aapt resource value: 0x7F060095 public const int notification_right_icon_size = 2131099797; - + // aapt resource value: 0x7F060096 public const int notification_right_side_padding_top = 2131099798; - + // aapt resource value: 0x7F060097 public const int notification_small_icon_background_padding = 2131099799; - + // aapt resource value: 0x7F060098 public const int notification_small_icon_size_as_large = 2131099800; - + // aapt resource value: 0x7F060099 public const int notification_subtext_size = 2131099801; - + // aapt resource value: 0x7F06009A public const int notification_top_pad = 2131099802; - + // aapt resource value: 0x7F06009B public const int notification_top_pad_large_text = 2131099803; - + // aapt resource value: 0x7F06009C public const int tooltip_corner_radius = 2131099804; - + // aapt resource value: 0x7F06009D public const int tooltip_horizontal_padding = 2131099805; - + // aapt resource value: 0x7F06009E public const int tooltip_margin = 2131099806; - + // aapt resource value: 0x7F06009F public const int tooltip_precise_anchor_extra_offset = 2131099807; - + // aapt resource value: 0x7F0600A0 public const int tooltip_precise_anchor_threshold = 2131099808; - + // aapt resource value: 0x7F0600A1 public const int tooltip_vertical_padding = 2131099809; - + // aapt resource value: 0x7F0600A2 public const int tooltip_y_offset_non_touch = 2131099810; - + // aapt resource value: 0x7F0600A3 public const int tooltip_y_offset_touch = 2131099811; - + static Dimension() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Dimension() { } } - + public partial class Drawable { - + // aapt resource value: 0x7F070006 public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - + // aapt resource value: 0x7F070007 public const int abc_action_bar_item_background_material = 2131165191; - + // aapt resource value: 0x7F070008 public const int abc_btn_borderless_material = 2131165192; - + // aapt resource value: 0x7F070009 public const int abc_btn_check_material = 2131165193; - + // aapt resource value: 0x7F07000A public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - + // aapt resource value: 0x7F07000B public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - + // aapt resource value: 0x7F07000C public const int abc_btn_colored_material = 2131165196; - + // aapt resource value: 0x7F07000D public const int abc_btn_default_mtrl_shape = 2131165197; - + // aapt resource value: 0x7F07000E public const int abc_btn_radio_material = 2131165198; - + // aapt resource value: 0x7F07000F public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - + // aapt resource value: 0x7F070010 public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - + // aapt resource value: 0x7F070011 public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - + // aapt resource value: 0x7F070012 public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - + // aapt resource value: 0x7F070013 public const int abc_cab_background_internal_bg = 2131165203; - + // aapt resource value: 0x7F070014 public const int abc_cab_background_top_material = 2131165204; - + // aapt resource value: 0x7F070015 public const int abc_cab_background_top_mtrl_alpha = 2131165205; - + // aapt resource value: 0x7F070016 public const int abc_control_background_material = 2131165206; - + // aapt resource value: 0x7F070017 public const int abc_dialog_material_background = 2131165207; - + // aapt resource value: 0x7F070018 public const int abc_edit_text_material = 2131165208; - + // aapt resource value: 0x7F070019 public const int abc_ic_ab_back_material = 2131165209; - + // aapt resource value: 0x7F07001A public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - + // aapt resource value: 0x7F07001B public const int abc_ic_clear_material = 2131165211; - + // aapt resource value: 0x7F07001C public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - + // aapt resource value: 0x7F07001D public const int abc_ic_go_search_api_material = 2131165213; - + // aapt resource value: 0x7F07001E public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - + // aapt resource value: 0x7F07001F public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - + // aapt resource value: 0x7F070020 public const int abc_ic_menu_overflow_material = 2131165216; - + // aapt resource value: 0x7F070021 public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - + // aapt resource value: 0x7F070022 public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - + // aapt resource value: 0x7F070023 public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - + // aapt resource value: 0x7F070024 public const int abc_ic_search_api_material = 2131165220; - + // aapt resource value: 0x7F070025 public const int abc_ic_star_black_16dp = 2131165221; - + // aapt resource value: 0x7F070026 public const int abc_ic_star_black_36dp = 2131165222; - + // aapt resource value: 0x7F070027 public const int abc_ic_star_black_48dp = 2131165223; - + // aapt resource value: 0x7F070028 public const int abc_ic_star_half_black_16dp = 2131165224; - + // aapt resource value: 0x7F070029 public const int abc_ic_star_half_black_36dp = 2131165225; - + // aapt resource value: 0x7F07002A public const int abc_ic_star_half_black_48dp = 2131165226; - + // aapt resource value: 0x7F07002B public const int abc_ic_voice_search_api_material = 2131165227; - + // aapt resource value: 0x7F07002C public const int abc_item_background_holo_dark = 2131165228; - + // aapt resource value: 0x7F07002D public const int abc_item_background_holo_light = 2131165229; - + // aapt resource value: 0x7F07002E public const int abc_list_divider_mtrl_alpha = 2131165230; - + // aapt resource value: 0x7F07002F public const int abc_list_focused_holo = 2131165231; - + // aapt resource value: 0x7F070030 public const int abc_list_longpressed_holo = 2131165232; - + // aapt resource value: 0x7F070031 public const int abc_list_pressed_holo_dark = 2131165233; - + // aapt resource value: 0x7F070032 public const int abc_list_pressed_holo_light = 2131165234; - + // aapt resource value: 0x7F070033 public const int abc_list_selector_background_transition_holo_dark = 2131165235; - + // aapt resource value: 0x7F070034 public const int abc_list_selector_background_transition_holo_light = 2131165236; - + // aapt resource value: 0x7F070035 public const int abc_list_selector_disabled_holo_dark = 2131165237; - + // aapt resource value: 0x7F070036 public const int abc_list_selector_disabled_holo_light = 2131165238; - + // aapt resource value: 0x7F070037 public const int abc_list_selector_holo_dark = 2131165239; - + // aapt resource value: 0x7F070038 public const int abc_list_selector_holo_light = 2131165240; - + // aapt resource value: 0x7F070039 public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - + // aapt resource value: 0x7F07003A public const int abc_popup_background_mtrl_mult = 2131165242; - + // aapt resource value: 0x7F07003B public const int abc_ratingbar_indicator_material = 2131165243; - + // aapt resource value: 0x7F07003C public const int abc_ratingbar_material = 2131165244; - + // aapt resource value: 0x7F07003D public const int abc_ratingbar_small_material = 2131165245; - + // aapt resource value: 0x7F07003E public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - + // aapt resource value: 0x7F07003F public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - + // aapt resource value: 0x7F070040 public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - + // aapt resource value: 0x7F070041 public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - + // aapt resource value: 0x7F070042 public const int abc_scrubber_track_mtrl_alpha = 2131165250; - + // aapt resource value: 0x7F070043 public const int abc_seekbar_thumb_material = 2131165251; - + // aapt resource value: 0x7F070044 public const int abc_seekbar_tick_mark_material = 2131165252; - + // aapt resource value: 0x7F070045 public const int abc_seekbar_track_material = 2131165253; - + // aapt resource value: 0x7F070046 public const int abc_spinner_mtrl_am_alpha = 2131165254; - + // aapt resource value: 0x7F070047 public const int abc_spinner_textfield_background_material = 2131165255; - + // aapt resource value: 0x7F070048 public const int abc_switch_thumb_material = 2131165256; - + // aapt resource value: 0x7F070049 public const int abc_switch_track_mtrl_alpha = 2131165257; - + // aapt resource value: 0x7F07004A public const int abc_tab_indicator_material = 2131165258; - + // aapt resource value: 0x7F07004B public const int abc_tab_indicator_mtrl_alpha = 2131165259; - + // aapt resource value: 0x7F070053 public const int abc_textfield_activated_mtrl_alpha = 2131165267; - + // aapt resource value: 0x7F070054 public const int abc_textfield_default_mtrl_alpha = 2131165268; - + // aapt resource value: 0x7F070055 public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - + // aapt resource value: 0x7F070056 public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - + // aapt resource value: 0x7F070057 public const int abc_textfield_search_material = 2131165271; - + // aapt resource value: 0x7F07004C public const int abc_text_cursor_material = 2131165260; - + // aapt resource value: 0x7F07004D public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - + // aapt resource value: 0x7F07004E public const int abc_text_select_handle_left_mtrl_light = 2131165262; - + // aapt resource value: 0x7F07004F public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - + // aapt resource value: 0x7F070050 public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - + // aapt resource value: 0x7F070051 public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - + // aapt resource value: 0x7F070052 public const int abc_text_select_handle_right_mtrl_light = 2131165266; - + // aapt resource value: 0x7F070058 public const int abc_vector_test = 2131165272; - + // aapt resource value: 0x7F070059 public const int avd_hide_password = 2131165273; - + // aapt resource value: 0x7F07005A public const int avd_show_password = 2131165274; - + // aapt resource value: 0x7F07005B public const int design_bottom_navigation_item_background = 2131165275; - + // aapt resource value: 0x7F07005C public const int design_fab_background = 2131165276; - + // aapt resource value: 0x7F07005D public const int design_ic_visibility = 2131165277; - + // aapt resource value: 0x7F07005E public const int design_ic_visibility_off = 2131165278; - + // aapt resource value: 0x7F07005F public const int design_password_eye = 2131165279; - + // aapt resource value: 0x7F070060 public const int design_snackbar_background = 2131165280; - + // aapt resource value: 0x7F070061 public const int ic_audiotrack_dark = 2131165281; - + // aapt resource value: 0x7F070062 public const int ic_audiotrack_light = 2131165282; - + // aapt resource value: 0x7F070063 public const int ic_dialog_close_dark = 2131165283; - + // aapt resource value: 0x7F070064 public const int ic_dialog_close_light = 2131165284; - + // aapt resource value: 0x7F070065 public const int ic_group_collapse_00 = 2131165285; - + // aapt resource value: 0x7F070066 public const int ic_group_collapse_01 = 2131165286; - + // aapt resource value: 0x7F070067 public const int ic_group_collapse_02 = 2131165287; - + // aapt resource value: 0x7F070068 public const int ic_group_collapse_03 = 2131165288; - + // aapt resource value: 0x7F070069 public const int ic_group_collapse_04 = 2131165289; - + // aapt resource value: 0x7F07006A public const int ic_group_collapse_05 = 2131165290; - + // aapt resource value: 0x7F07006B public const int ic_group_collapse_06 = 2131165291; - + // aapt resource value: 0x7F07006C public const int ic_group_collapse_07 = 2131165292; - + // aapt resource value: 0x7F07006D public const int ic_group_collapse_08 = 2131165293; - + // aapt resource value: 0x7F07006E public const int ic_group_collapse_09 = 2131165294; - + // aapt resource value: 0x7F07006F public const int ic_group_collapse_10 = 2131165295; - + // aapt resource value: 0x7F070070 public const int ic_group_collapse_11 = 2131165296; - + // aapt resource value: 0x7F070071 public const int ic_group_collapse_12 = 2131165297; - + // aapt resource value: 0x7F070072 public const int ic_group_collapse_13 = 2131165298; - + // aapt resource value: 0x7F070073 public const int ic_group_collapse_14 = 2131165299; - + // aapt resource value: 0x7F070074 public const int ic_group_collapse_15 = 2131165300; - + // aapt resource value: 0x7F070075 public const int ic_group_expand_00 = 2131165301; - + // aapt resource value: 0x7F070076 public const int ic_group_expand_01 = 2131165302; - + // aapt resource value: 0x7F070077 public const int ic_group_expand_02 = 2131165303; - + // aapt resource value: 0x7F070078 public const int ic_group_expand_03 = 2131165304; - + // aapt resource value: 0x7F070079 public const int ic_group_expand_04 = 2131165305; - + // aapt resource value: 0x7F07007A public const int ic_group_expand_05 = 2131165306; - + // aapt resource value: 0x7F07007B public const int ic_group_expand_06 = 2131165307; - + // aapt resource value: 0x7F07007C public const int ic_group_expand_07 = 2131165308; - + // aapt resource value: 0x7F07007D public const int ic_group_expand_08 = 2131165309; - + // aapt resource value: 0x7F07007E public const int ic_group_expand_09 = 2131165310; - + // aapt resource value: 0x7F07007F public const int ic_group_expand_10 = 2131165311; - + // aapt resource value: 0x7F070080 public const int ic_group_expand_11 = 2131165312; - + // aapt resource value: 0x7F070081 public const int ic_group_expand_12 = 2131165313; - + // aapt resource value: 0x7F070082 public const int ic_group_expand_13 = 2131165314; - + // aapt resource value: 0x7F070083 public const int ic_group_expand_14 = 2131165315; - + // aapt resource value: 0x7F070084 public const int ic_group_expand_15 = 2131165316; - + // aapt resource value: 0x7F070085 public const int ic_media_pause_dark = 2131165317; - + // aapt resource value: 0x7F070086 public const int ic_media_pause_light = 2131165318; - + // aapt resource value: 0x7F070087 public const int ic_media_play_dark = 2131165319; - + // aapt resource value: 0x7F070088 public const int ic_media_play_light = 2131165320; - + // aapt resource value: 0x7F070089 public const int ic_media_stop_dark = 2131165321; - + // aapt resource value: 0x7F07008A public const int ic_media_stop_light = 2131165322; - + // aapt resource value: 0x7F07008B public const int ic_mr_button_connected_00_dark = 2131165323; - + // aapt resource value: 0x7F07008C public const int ic_mr_button_connected_00_light = 2131165324; - + // aapt resource value: 0x7F07008D public const int ic_mr_button_connected_01_dark = 2131165325; - + // aapt resource value: 0x7F07008E public const int ic_mr_button_connected_01_light = 2131165326; - + // aapt resource value: 0x7F07008F public const int ic_mr_button_connected_02_dark = 2131165327; - + // aapt resource value: 0x7F070090 public const int ic_mr_button_connected_02_light = 2131165328; - + // aapt resource value: 0x7F070091 public const int ic_mr_button_connected_03_dark = 2131165329; - + // aapt resource value: 0x7F070092 public const int ic_mr_button_connected_03_light = 2131165330; - + // aapt resource value: 0x7F070093 public const int ic_mr_button_connected_04_dark = 2131165331; - + // aapt resource value: 0x7F070094 public const int ic_mr_button_connected_04_light = 2131165332; - + // aapt resource value: 0x7F070095 public const int ic_mr_button_connected_05_dark = 2131165333; - + // aapt resource value: 0x7F070096 public const int ic_mr_button_connected_05_light = 2131165334; - + // aapt resource value: 0x7F070097 public const int ic_mr_button_connected_06_dark = 2131165335; - + // aapt resource value: 0x7F070098 public const int ic_mr_button_connected_06_light = 2131165336; - + // aapt resource value: 0x7F070099 public const int ic_mr_button_connected_07_dark = 2131165337; - + // aapt resource value: 0x7F07009A public const int ic_mr_button_connected_07_light = 2131165338; - + // aapt resource value: 0x7F07009B public const int ic_mr_button_connected_08_dark = 2131165339; - + // aapt resource value: 0x7F07009C public const int ic_mr_button_connected_08_light = 2131165340; - + // aapt resource value: 0x7F07009D public const int ic_mr_button_connected_09_dark = 2131165341; - + // aapt resource value: 0x7F07009E public const int ic_mr_button_connected_09_light = 2131165342; - + // aapt resource value: 0x7F07009F public const int ic_mr_button_connected_10_dark = 2131165343; - + // aapt resource value: 0x7F0700A0 public const int ic_mr_button_connected_10_light = 2131165344; - + // aapt resource value: 0x7F0700A1 public const int ic_mr_button_connected_11_dark = 2131165345; - + // aapt resource value: 0x7F0700A2 public const int ic_mr_button_connected_11_light = 2131165346; - + // aapt resource value: 0x7F0700A3 public const int ic_mr_button_connected_12_dark = 2131165347; - + // aapt resource value: 0x7F0700A4 public const int ic_mr_button_connected_12_light = 2131165348; - + // aapt resource value: 0x7F0700A5 public const int ic_mr_button_connected_13_dark = 2131165349; - + // aapt resource value: 0x7F0700A6 public const int ic_mr_button_connected_13_light = 2131165350; - + // aapt resource value: 0x7F0700A7 public const int ic_mr_button_connected_14_dark = 2131165351; - + // aapt resource value: 0x7F0700A8 public const int ic_mr_button_connected_14_light = 2131165352; - + // aapt resource value: 0x7F0700A9 public const int ic_mr_button_connected_15_dark = 2131165353; - + // aapt resource value: 0x7F0700AA public const int ic_mr_button_connected_15_light = 2131165354; - + // aapt resource value: 0x7F0700AB public const int ic_mr_button_connected_16_dark = 2131165355; - + // aapt resource value: 0x7F0700AC public const int ic_mr_button_connected_16_light = 2131165356; - + // aapt resource value: 0x7F0700AD public const int ic_mr_button_connected_17_dark = 2131165357; - + // aapt resource value: 0x7F0700AE public const int ic_mr_button_connected_17_light = 2131165358; - + // aapt resource value: 0x7F0700AF public const int ic_mr_button_connected_18_dark = 2131165359; - + // aapt resource value: 0x7F0700B0 public const int ic_mr_button_connected_18_light = 2131165360; - + // aapt resource value: 0x7F0700B1 public const int ic_mr_button_connected_19_dark = 2131165361; - + // aapt resource value: 0x7F0700B2 public const int ic_mr_button_connected_19_light = 2131165362; - + // aapt resource value: 0x7F0700B3 public const int ic_mr_button_connected_20_dark = 2131165363; - + // aapt resource value: 0x7F0700B4 public const int ic_mr_button_connected_20_light = 2131165364; - + // aapt resource value: 0x7F0700B5 public const int ic_mr_button_connected_21_dark = 2131165365; - + // aapt resource value: 0x7F0700B6 public const int ic_mr_button_connected_21_light = 2131165366; - + // aapt resource value: 0x7F0700B7 public const int ic_mr_button_connected_22_dark = 2131165367; - + // aapt resource value: 0x7F0700B8 public const int ic_mr_button_connected_22_light = 2131165368; - + // aapt resource value: 0x7F0700B9 public const int ic_mr_button_connected_23_dark = 2131165369; - + // aapt resource value: 0x7F0700BA public const int ic_mr_button_connected_23_light = 2131165370; - + // aapt resource value: 0x7F0700BB public const int ic_mr_button_connected_24_dark = 2131165371; - + // aapt resource value: 0x7F0700BC public const int ic_mr_button_connected_24_light = 2131165372; - + // aapt resource value: 0x7F0700BD public const int ic_mr_button_connected_25_dark = 2131165373; - + // aapt resource value: 0x7F0700BE public const int ic_mr_button_connected_25_light = 2131165374; - + // aapt resource value: 0x7F0700BF public const int ic_mr_button_connected_26_dark = 2131165375; - + // aapt resource value: 0x7F0700C0 public const int ic_mr_button_connected_26_light = 2131165376; - + // aapt resource value: 0x7F0700C1 public const int ic_mr_button_connected_27_dark = 2131165377; - + // aapt resource value: 0x7F0700C2 public const int ic_mr_button_connected_27_light = 2131165378; - + // aapt resource value: 0x7F0700C3 public const int ic_mr_button_connected_28_dark = 2131165379; - + // aapt resource value: 0x7F0700C4 public const int ic_mr_button_connected_28_light = 2131165380; - + // aapt resource value: 0x7F0700C5 public const int ic_mr_button_connected_29_dark = 2131165381; - + // aapt resource value: 0x7F0700C6 public const int ic_mr_button_connected_29_light = 2131165382; - + // aapt resource value: 0x7F0700C7 public const int ic_mr_button_connected_30_dark = 2131165383; - + // aapt resource value: 0x7F0700C8 public const int ic_mr_button_connected_30_light = 2131165384; - + // aapt resource value: 0x7F0700C9 public const int ic_mr_button_connecting_00_dark = 2131165385; - + // aapt resource value: 0x7F0700CA public const int ic_mr_button_connecting_00_light = 2131165386; - + // aapt resource value: 0x7F0700CB public const int ic_mr_button_connecting_01_dark = 2131165387; - + // aapt resource value: 0x7F0700CC public const int ic_mr_button_connecting_01_light = 2131165388; - + // aapt resource value: 0x7F0700CD public const int ic_mr_button_connecting_02_dark = 2131165389; - + // aapt resource value: 0x7F0700CE public const int ic_mr_button_connecting_02_light = 2131165390; - + // aapt resource value: 0x7F0700CF public const int ic_mr_button_connecting_03_dark = 2131165391; - + // aapt resource value: 0x7F0700D0 public const int ic_mr_button_connecting_03_light = 2131165392; - + // aapt resource value: 0x7F0700D1 public const int ic_mr_button_connecting_04_dark = 2131165393; - + // aapt resource value: 0x7F0700D2 public const int ic_mr_button_connecting_04_light = 2131165394; - + // aapt resource value: 0x7F0700D3 public const int ic_mr_button_connecting_05_dark = 2131165395; - + // aapt resource value: 0x7F0700D4 public const int ic_mr_button_connecting_05_light = 2131165396; - + // aapt resource value: 0x7F0700D5 public const int ic_mr_button_connecting_06_dark = 2131165397; - + // aapt resource value: 0x7F0700D6 public const int ic_mr_button_connecting_06_light = 2131165398; - + // aapt resource value: 0x7F0700D7 public const int ic_mr_button_connecting_07_dark = 2131165399; - + // aapt resource value: 0x7F0700D8 public const int ic_mr_button_connecting_07_light = 2131165400; - + // aapt resource value: 0x7F0700D9 public const int ic_mr_button_connecting_08_dark = 2131165401; - + // aapt resource value: 0x7F0700DA public const int ic_mr_button_connecting_08_light = 2131165402; - + // aapt resource value: 0x7F0700DB public const int ic_mr_button_connecting_09_dark = 2131165403; - + // aapt resource value: 0x7F0700DC public const int ic_mr_button_connecting_09_light = 2131165404; - + // aapt resource value: 0x7F0700DD public const int ic_mr_button_connecting_10_dark = 2131165405; - + // aapt resource value: 0x7F0700DE public const int ic_mr_button_connecting_10_light = 2131165406; - + // aapt resource value: 0x7F0700DF public const int ic_mr_button_connecting_11_dark = 2131165407; - + // aapt resource value: 0x7F0700E0 public const int ic_mr_button_connecting_11_light = 2131165408; - + // aapt resource value: 0x7F0700E1 public const int ic_mr_button_connecting_12_dark = 2131165409; - + // aapt resource value: 0x7F0700E2 public const int ic_mr_button_connecting_12_light = 2131165410; - + // aapt resource value: 0x7F0700E3 public const int ic_mr_button_connecting_13_dark = 2131165411; - + // aapt resource value: 0x7F0700E4 public const int ic_mr_button_connecting_13_light = 2131165412; - + // aapt resource value: 0x7F0700E5 public const int ic_mr_button_connecting_14_dark = 2131165413; - + // aapt resource value: 0x7F0700E6 public const int ic_mr_button_connecting_14_light = 2131165414; - + // aapt resource value: 0x7F0700E7 public const int ic_mr_button_connecting_15_dark = 2131165415; - + // aapt resource value: 0x7F0700E8 public const int ic_mr_button_connecting_15_light = 2131165416; - + // aapt resource value: 0x7F0700E9 public const int ic_mr_button_connecting_16_dark = 2131165417; - + // aapt resource value: 0x7F0700EA public const int ic_mr_button_connecting_16_light = 2131165418; - + // aapt resource value: 0x7F0700EB public const int ic_mr_button_connecting_17_dark = 2131165419; - + // aapt resource value: 0x7F0700EC public const int ic_mr_button_connecting_17_light = 2131165420; - + // aapt resource value: 0x7F0700ED public const int ic_mr_button_connecting_18_dark = 2131165421; - + // aapt resource value: 0x7F0700EE public const int ic_mr_button_connecting_18_light = 2131165422; - + // aapt resource value: 0x7F0700EF public const int ic_mr_button_connecting_19_dark = 2131165423; - + // aapt resource value: 0x7F0700F0 public const int ic_mr_button_connecting_19_light = 2131165424; - + // aapt resource value: 0x7F0700F1 public const int ic_mr_button_connecting_20_dark = 2131165425; - + // aapt resource value: 0x7F0700F2 public const int ic_mr_button_connecting_20_light = 2131165426; - + // aapt resource value: 0x7F0700F3 public const int ic_mr_button_connecting_21_dark = 2131165427; - + // aapt resource value: 0x7F0700F4 public const int ic_mr_button_connecting_21_light = 2131165428; - + // aapt resource value: 0x7F0700F5 public const int ic_mr_button_connecting_22_dark = 2131165429; - + // aapt resource value: 0x7F0700F6 public const int ic_mr_button_connecting_22_light = 2131165430; - + // aapt resource value: 0x7F0700F7 public const int ic_mr_button_connecting_23_dark = 2131165431; - + // aapt resource value: 0x7F0700F8 public const int ic_mr_button_connecting_23_light = 2131165432; - + // aapt resource value: 0x7F0700F9 public const int ic_mr_button_connecting_24_dark = 2131165433; - + // aapt resource value: 0x7F0700FA public const int ic_mr_button_connecting_24_light = 2131165434; - + // aapt resource value: 0x7F0700FB public const int ic_mr_button_connecting_25_dark = 2131165435; - + // aapt resource value: 0x7F0700FC public const int ic_mr_button_connecting_25_light = 2131165436; - + // aapt resource value: 0x7F0700FD public const int ic_mr_button_connecting_26_dark = 2131165437; - + // aapt resource value: 0x7F0700FE public const int ic_mr_button_connecting_26_light = 2131165438; - + // aapt resource value: 0x7F0700FF public const int ic_mr_button_connecting_27_dark = 2131165439; - + // aapt resource value: 0x7F070100 public const int ic_mr_button_connecting_27_light = 2131165440; - + // aapt resource value: 0x7F070101 public const int ic_mr_button_connecting_28_dark = 2131165441; - + // aapt resource value: 0x7F070102 public const int ic_mr_button_connecting_28_light = 2131165442; - + // aapt resource value: 0x7F070103 public const int ic_mr_button_connecting_29_dark = 2131165443; - + // aapt resource value: 0x7F070104 public const int ic_mr_button_connecting_29_light = 2131165444; - + // aapt resource value: 0x7F070105 public const int ic_mr_button_connecting_30_dark = 2131165445; - + // aapt resource value: 0x7F070106 public const int ic_mr_button_connecting_30_light = 2131165446; - + // aapt resource value: 0x7F070107 public const int ic_mr_button_disabled_dark = 2131165447; - + // aapt resource value: 0x7F070108 public const int ic_mr_button_disabled_light = 2131165448; - + // aapt resource value: 0x7F070109 public const int ic_mr_button_disconnected_dark = 2131165449; - + // aapt resource value: 0x7F07010A public const int ic_mr_button_disconnected_light = 2131165450; - + // aapt resource value: 0x7F07010B public const int ic_mr_button_grey = 2131165451; - + // aapt resource value: 0x7F07010C public const int ic_vol_type_speaker_dark = 2131165452; - + // aapt resource value: 0x7F07010D public const int ic_vol_type_speaker_group_dark = 2131165453; - + // aapt resource value: 0x7F07010E public const int ic_vol_type_speaker_group_light = 2131165454; - + // aapt resource value: 0x7F07010F public const int ic_vol_type_speaker_light = 2131165455; - + // aapt resource value: 0x7F070110 public const int ic_vol_type_tv_dark = 2131165456; - + // aapt resource value: 0x7F070111 public const int ic_vol_type_tv_light = 2131165457; - + // aapt resource value: 0x7F070112 public const int mr_button_connected_dark = 2131165458; - + // aapt resource value: 0x7F070113 public const int mr_button_connected_light = 2131165459; - + // aapt resource value: 0x7F070114 public const int mr_button_connecting_dark = 2131165460; - + // aapt resource value: 0x7F070115 public const int mr_button_connecting_light = 2131165461; - + // aapt resource value: 0x7F070116 public const int mr_button_dark = 2131165462; - + // aapt resource value: 0x7F070117 public const int mr_button_light = 2131165463; - + // aapt resource value: 0x7F070118 public const int mr_dialog_close_dark = 2131165464; - + // aapt resource value: 0x7F070119 public const int mr_dialog_close_light = 2131165465; - + // aapt resource value: 0x7F07011A public const int mr_dialog_material_background_dark = 2131165466; - + // aapt resource value: 0x7F07011B public const int mr_dialog_material_background_light = 2131165467; - + // aapt resource value: 0x7F07011C public const int mr_group_collapse = 2131165468; - + // aapt resource value: 0x7F07011D public const int mr_group_expand = 2131165469; - + // aapt resource value: 0x7F07011E public const int mr_media_pause_dark = 2131165470; - + // aapt resource value: 0x7F07011F public const int mr_media_pause_light = 2131165471; - + // aapt resource value: 0x7F070120 public const int mr_media_play_dark = 2131165472; - + // aapt resource value: 0x7F070121 public const int mr_media_play_light = 2131165473; - + // aapt resource value: 0x7F070122 public const int mr_media_stop_dark = 2131165474; - + // aapt resource value: 0x7F070123 public const int mr_media_stop_light = 2131165475; - + // aapt resource value: 0x7F070124 public const int mr_vol_type_audiotrack_dark = 2131165476; - + // aapt resource value: 0x7F070125 public const int mr_vol_type_audiotrack_light = 2131165477; - + // aapt resource value: 0x7F070126 public const int navigation_empty_icon = 2131165478; - + // aapt resource value: 0x7F070127 public const int notification_action_background = 2131165479; - + // aapt resource value: 0x7F070128 public const int notification_bg = 2131165480; - + // aapt resource value: 0x7F070129 public const int notification_bg_low = 2131165481; - + // aapt resource value: 0x7F07012A public const int notification_bg_low_normal = 2131165482; - + // aapt resource value: 0x7F07012B public const int notification_bg_low_pressed = 2131165483; - + // aapt resource value: 0x7F07012C public const int notification_bg_normal = 2131165484; - + // aapt resource value: 0x7F07012D public const int notification_bg_normal_pressed = 2131165485; - + // aapt resource value: 0x7F07012E public const int notification_icon_background = 2131165486; - + // aapt resource value: 0x7F07012F public const int notification_template_icon_bg = 2131165487; - + // aapt resource value: 0x7F070130 public const int notification_template_icon_low_bg = 2131165488; - + // aapt resource value: 0x7F070131 public const int notification_tile_bg = 2131165489; - + // aapt resource value: 0x7F070132 public const int notify_panel_notification_icon_bg = 2131165490; - + // aapt resource value: 0x7F070133 public const int tooltip_frame_dark = 2131165491; - + // aapt resource value: 0x7F070134 public const int tooltip_frame_light = 2131165492; - + static Drawable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Drawable() { } } - + public partial class Id { - + // aapt resource value: 0x7F080006 public const int action0 = 2131230726; - + // aapt resource value: 0x7F080018 public const int actions = 2131230744; - + // aapt resource value: 0x7F080007 public const int action_bar = 2131230727; - + // aapt resource value: 0x7F080008 public const int action_bar_activity_content = 2131230728; - + // aapt resource value: 0x7F080009 public const int action_bar_container = 2131230729; - + // aapt resource value: 0x7F08000A public const int action_bar_root = 2131230730; - + // aapt resource value: 0x7F08000B public const int action_bar_spinner = 2131230731; - + // aapt resource value: 0x7F08000C public const int action_bar_subtitle = 2131230732; - + // aapt resource value: 0x7F08000D public const int action_bar_title = 2131230733; - + // aapt resource value: 0x7F08000E public const int action_container = 2131230734; - + // aapt resource value: 0x7F08000F public const int action_context_bar = 2131230735; - + // aapt resource value: 0x7F080010 public const int action_divider = 2131230736; - + // aapt resource value: 0x7F080011 public const int action_image = 2131230737; - + // aapt resource value: 0x7F080012 public const int action_menu_divider = 2131230738; - + // aapt resource value: 0x7F080013 public const int action_menu_presenter = 2131230739; - + // aapt resource value: 0x7F080014 public const int action_mode_bar = 2131230740; - + // aapt resource value: 0x7F080015 public const int action_mode_bar_stub = 2131230741; - + // aapt resource value: 0x7F080016 public const int action_mode_close_button = 2131230742; - + // aapt resource value: 0x7F080017 public const int action_text = 2131230743; - + // aapt resource value: 0x7F080019 public const int activity_chooser_view_content = 2131230745; - + // aapt resource value: 0x7F08001A public const int add = 2131230746; - + // aapt resource value: 0x7F08001B public const int alertTitle = 2131230747; - + // aapt resource value: 0x7F08001C public const int all = 2131230748; - + // aapt resource value: 0x7F080000 public const int ALT = 2131230720; - + // aapt resource value: 0x7F08001D public const int always = 2131230749; - + // aapt resource value: 0x7F08001E public const int async = 2131230750; - + // aapt resource value: 0x7F08001F public const int auto = 2131230751; - + // aapt resource value: 0x7F080020 public const int beginning = 2131230752; - + // aapt resource value: 0x7F080021 public const int blocking = 2131230753; - + // aapt resource value: 0x7F080022 public const int bottom = 2131230754; - + // aapt resource value: 0x7F080023 public const int bottomtab_navarea = 2131230755; - + // aapt resource value: 0x7F080024 public const int bottomtab_tabbar = 2131230756; - + // aapt resource value: 0x7F080025 public const int buttonPanel = 2131230757; - + // aapt resource value: 0x7F080026 public const int cancel_action = 2131230758; - + // aapt resource value: 0x7F080027 public const int center = 2131230759; - + // aapt resource value: 0x7F080028 public const int center_horizontal = 2131230760; - + // aapt resource value: 0x7F080029 public const int center_vertical = 2131230761; - + // aapt resource value: 0x7F08002A public const int checkbox = 2131230762; - + // aapt resource value: 0x7F08002B public const int chronometer = 2131230763; - + // aapt resource value: 0x7F08002C public const int clip_horizontal = 2131230764; - + // aapt resource value: 0x7F08002D public const int clip_vertical = 2131230765; - + // aapt resource value: 0x7F08002E public const int collapseActionView = 2131230766; - + // aapt resource value: 0x7F08002F public const int container = 2131230767; - + // aapt resource value: 0x7F080030 public const int contentPanel = 2131230768; - + // aapt resource value: 0x7F080031 public const int coordinator = 2131230769; - + // aapt resource value: 0x7F080001 public const int CTRL = 2131230721; - + // aapt resource value: 0x7F080032 public const int custom = 2131230770; - + // aapt resource value: 0x7F080033 public const int customPanel = 2131230771; - + // aapt resource value: 0x7F080034 public const int decor_content_parent = 2131230772; - + // aapt resource value: 0x7F080035 public const int default_activity_button = 2131230773; - + // aapt resource value: 0x7F080036 public const int design_bottom_sheet = 2131230774; - + // aapt resource value: 0x7F080037 public const int design_menu_item_action_area = 2131230775; - + // aapt resource value: 0x7F080038 public const int design_menu_item_action_area_stub = 2131230776; - + // aapt resource value: 0x7F080039 public const int design_menu_item_text = 2131230777; - + // aapt resource value: 0x7F08003A public const int design_navigation_view = 2131230778; - + // aapt resource value: 0x7F08003B public const int disableHome = 2131230779; - + // aapt resource value: 0x7F08003C public const int edit_query = 2131230780; - + // aapt resource value: 0x7F08003D public const int end = 2131230781; - + // aapt resource value: 0x7F08003E public const int end_padder = 2131230782; - + // aapt resource value: 0x7F08003F public const int enterAlways = 2131230783; - + // aapt resource value: 0x7F080040 public const int enterAlwaysCollapsed = 2131230784; - + // aapt resource value: 0x7F080041 public const int exitUntilCollapsed = 2131230785; - + // aapt resource value: 0x7F080043 public const int expanded_menu = 2131230787; - + // aapt resource value: 0x7F080042 public const int expand_activities_button = 2131230786; - + // aapt resource value: 0x7F080044 public const int fill = 2131230788; - + // aapt resource value: 0x7F080045 public const int fill_horizontal = 2131230789; - + // aapt resource value: 0x7F080046 public const int fill_vertical = 2131230790; - + // aapt resource value: 0x7F080047 public const int @fixed = 2131230791; - + // aapt resource value: 0x7F080048 public const int flyoutcontent_appbar = 2131230792; - + // aapt resource value: 0x7F080049 public const int flyoutcontent_recycler = 2131230793; - + // aapt resource value: 0x7F08004A public const int forever = 2131230794; - + // aapt resource value: 0x7F080002 public const int FUNCTION = 2131230722; - + // aapt resource value: 0x7F08004B public const int ghost_view = 2131230795; - + // aapt resource value: 0x7F08004C public const int home = 2131230796; - + // aapt resource value: 0x7F08004D public const int homeAsUp = 2131230797; - + // aapt resource value: 0x7F08004E public const int icon = 2131230798; - + // aapt resource value: 0x7F08004F public const int icon_group = 2131230799; - + // aapt resource value: 0x7F080050 public const int ifRoom = 2131230800; - + // aapt resource value: 0x7F080051 public const int image = 2131230801; - + // aapt resource value: 0x7F080052 public const int info = 2131230802; - + // aapt resource value: 0x7F080053 public const int italic = 2131230803; - + // aapt resource value: 0x7F080054 public const int item_touch_helper_previous_elevation = 2131230804; - + // aapt resource value: 0x7F080055 public const int largeLabel = 2131230805; - + // aapt resource value: 0x7F080056 public const int left = 2131230806; - + // aapt resource value: 0x7F080057 public const int line1 = 2131230807; - + // aapt resource value: 0x7F080058 public const int line3 = 2131230808; - + // aapt resource value: 0x7F080059 public const int listMode = 2131230809; - + // aapt resource value: 0x7F08005A public const int list_item = 2131230810; - + // aapt resource value: 0x7F08005B public const int main_appbar = 2131230811; - + // aapt resource value: 0x7F08005C public const int main_scrollview = 2131230812; - + // aapt resource value: 0x7F08005D public const int main_tablayout = 2131230813; - + // aapt resource value: 0x7F08005E public const int main_toolbar = 2131230814; - + // aapt resource value: 0x7F08005F public const int masked = 2131230815; - + // aapt resource value: 0x7F080060 public const int media_actions = 2131230816; - + // aapt resource value: 0x7F080061 public const int message = 2131230817; - + // aapt resource value: 0x7F080003 public const int META = 2131230723; - + // aapt resource value: 0x7F080062 public const int middle = 2131230818; - + // aapt resource value: 0x7F080063 public const int mini = 2131230819; - + // aapt resource value: 0x7F080064 public const int mr_art = 2131230820; - + // aapt resource value: 0x7F080065 public const int mr_chooser_list = 2131230821; - + // aapt resource value: 0x7F080066 public const int mr_chooser_route_desc = 2131230822; - + // aapt resource value: 0x7F080067 public const int mr_chooser_route_icon = 2131230823; - + // aapt resource value: 0x7F080068 public const int mr_chooser_route_name = 2131230824; - + // aapt resource value: 0x7F080069 public const int mr_chooser_title = 2131230825; - + // aapt resource value: 0x7F08006A public const int mr_close = 2131230826; - + // aapt resource value: 0x7F08006B public const int mr_control_divider = 2131230827; - + // aapt resource value: 0x7F08006C public const int mr_control_playback_ctrl = 2131230828; - + // aapt resource value: 0x7F08006D public const int mr_control_subtitle = 2131230829; - + // aapt resource value: 0x7F08006E public const int mr_control_title = 2131230830; - + // aapt resource value: 0x7F08006F public const int mr_control_title_container = 2131230831; - + // aapt resource value: 0x7F080070 public const int mr_custom_control = 2131230832; - + // aapt resource value: 0x7F080071 public const int mr_default_control = 2131230833; - + // aapt resource value: 0x7F080072 public const int mr_dialog_area = 2131230834; - + // aapt resource value: 0x7F080073 public const int mr_expandable_area = 2131230835; - + // aapt resource value: 0x7F080074 public const int mr_group_expand_collapse = 2131230836; - + // aapt resource value: 0x7F080075 public const int mr_media_main_control = 2131230837; - + // aapt resource value: 0x7F080076 public const int mr_name = 2131230838; - + // aapt resource value: 0x7F080077 public const int mr_playback_control = 2131230839; - + // aapt resource value: 0x7F080078 public const int mr_title_bar = 2131230840; - + // aapt resource value: 0x7F080079 public const int mr_volume_control = 2131230841; - + // aapt resource value: 0x7F08007A public const int mr_volume_group_list = 2131230842; - + // aapt resource value: 0x7F08007B public const int mr_volume_item_icon = 2131230843; - + // aapt resource value: 0x7F08007C public const int mr_volume_slider = 2131230844; - + // aapt resource value: 0x7F08007D public const int multiply = 2131230845; - + // aapt resource value: 0x7F08007E public const int navigation_header_container = 2131230846; - + // aapt resource value: 0x7F08007F public const int never = 2131230847; - + // aapt resource value: 0x7F080080 public const int none = 2131230848; - + // aapt resource value: 0x7F080081 public const int normal = 2131230849; - + // aapt resource value: 0x7F080082 public const int notification_background = 2131230850; - + // aapt resource value: 0x7F080083 public const int notification_main_column = 2131230851; - + // aapt resource value: 0x7F080084 public const int notification_main_column_container = 2131230852; - + // aapt resource value: 0x7F080085 public const int parallax = 2131230853; - + // aapt resource value: 0x7F080086 public const int parentPanel = 2131230854; - + // aapt resource value: 0x7F080087 public const int parent_matrix = 2131230855; - + // aapt resource value: 0x7F080088 public const int pin = 2131230856; - + // aapt resource value: 0x7F080089 public const int progress_circular = 2131230857; - + // aapt resource value: 0x7F08008A public const int progress_horizontal = 2131230858; - + // aapt resource value: 0x7F08008B public const int radio = 2131230859; - + // aapt resource value: 0x7F08008C public const int right = 2131230860; - + // aapt resource value: 0x7F08008D public const int right_icon = 2131230861; - + // aapt resource value: 0x7F08008E public const int right_side = 2131230862; - + // aapt resource value: 0x7F08008F public const int save_image_matrix = 2131230863; - + // aapt resource value: 0x7F080090 public const int save_non_transition_alpha = 2131230864; - + // aapt resource value: 0x7F080091 public const int save_scale_type = 2131230865; - + // aapt resource value: 0x7F080092 public const int screen = 2131230866; - + // aapt resource value: 0x7F080093 public const int scroll = 2131230867; - + // aapt resource value: 0x7F080097 public const int scrollable = 2131230871; - + // aapt resource value: 0x7F080094 public const int scrollIndicatorDown = 2131230868; - + // aapt resource value: 0x7F080095 public const int scrollIndicatorUp = 2131230869; - + // aapt resource value: 0x7F080096 public const int scrollView = 2131230870; - + // aapt resource value: 0x7F080098 public const int search_badge = 2131230872; - + // aapt resource value: 0x7F080099 public const int search_bar = 2131230873; - + // aapt resource value: 0x7F08009A public const int search_button = 2131230874; - + // aapt resource value: 0x7F08009B public const int search_close_btn = 2131230875; - + // aapt resource value: 0x7F08009C public const int search_edit_frame = 2131230876; - + // aapt resource value: 0x7F08009D public const int search_go_btn = 2131230877; - + // aapt resource value: 0x7F08009E public const int search_mag_icon = 2131230878; - + // aapt resource value: 0x7F08009F public const int search_plate = 2131230879; - + // aapt resource value: 0x7F0800A0 public const int search_src_text = 2131230880; - + // aapt resource value: 0x7F0800A1 public const int search_voice_btn = 2131230881; - + // aapt resource value: 0x7F0800A2 public const int select_dialog_listview = 2131230882; - + // aapt resource value: 0x7F0800A3 public const int shellcontent_appbar = 2131230883; - + // aapt resource value: 0x7F0800A4 public const int shellcontent_scrollview = 2131230884; - + // aapt resource value: 0x7F0800A5 public const int shellcontent_toolbar = 2131230885; - + // aapt resource value: 0x7F080004 public const int SHIFT = 2131230724; - + // aapt resource value: 0x7F0800A6 public const int shortcut = 2131230886; - + // aapt resource value: 0x7F0800A7 public const int showCustom = 2131230887; - + // aapt resource value: 0x7F0800A8 public const int showHome = 2131230888; - + // aapt resource value: 0x7F0800A9 public const int showTitle = 2131230889; - + // aapt resource value: 0x7F0800AA public const int smallLabel = 2131230890; - + // aapt resource value: 0x7F0800AB public const int snackbar_action = 2131230891; - + // aapt resource value: 0x7F0800AC public const int snackbar_text = 2131230892; - + // aapt resource value: 0x7F0800AD public const int snap = 2131230893; - + // aapt resource value: 0x7F0800AE public const int spacer = 2131230894; - + // aapt resource value: 0x7F0800AF public const int split_action_bar = 2131230895; - + // aapt resource value: 0x7F0800B0 public const int src_atop = 2131230896; - + // aapt resource value: 0x7F0800B1 public const int src_in = 2131230897; - + // aapt resource value: 0x7F0800B2 public const int src_over = 2131230898; - + // aapt resource value: 0x7F0800B3 public const int start = 2131230899; - + // aapt resource value: 0x7F0800B4 public const int status_bar_latest_event_content = 2131230900; - + // aapt resource value: 0x7F0800B5 public const int submenuarrow = 2131230901; - + // aapt resource value: 0x7F0800B6 public const int submit_area = 2131230902; - + // aapt resource value: 0x7F080005 public const int SYM = 2131230725; - + // aapt resource value: 0x7F0800B7 public const int tabMode = 2131230903; - + // aapt resource value: 0x7F0800B8 public const int tag_transition_group = 2131230904; - + // aapt resource value: 0x7F0800B9 public const int text = 2131230905; - + // aapt resource value: 0x7F0800BA public const int text2 = 2131230906; - + // aapt resource value: 0x7F0800BE public const int textinput_counter = 2131230910; - + // aapt resource value: 0x7F0800BF public const int textinput_error = 2131230911; - + // aapt resource value: 0x7F0800BB public const int textSpacerNoButtons = 2131230907; - + // aapt resource value: 0x7F0800BC public const int textSpacerNoTitle = 2131230908; - + // aapt resource value: 0x7F0800BD public const int text_input_password_toggle = 2131230909; - + // aapt resource value: 0x7F0800C0 public const int time = 2131230912; - + // aapt resource value: 0x7F0800C1 public const int title = 2131230913; - + // aapt resource value: 0x7F0800C2 public const int titleDividerNoCustom = 2131230914; - + // aapt resource value: 0x7F0800C3 public const int title_template = 2131230915; - + // aapt resource value: 0x7F0800C4 public const int top = 2131230916; - + // aapt resource value: 0x7F0800C5 public const int topPanel = 2131230917; - + // aapt resource value: 0x7F0800C6 public const int touch_outside = 2131230918; - + // aapt resource value: 0x7F0800C7 public const int transition_current_scene = 2131230919; - + // aapt resource value: 0x7F0800C8 public const int transition_layout_save = 2131230920; - + // aapt resource value: 0x7F0800C9 public const int transition_position = 2131230921; - + // aapt resource value: 0x7F0800CA public const int transition_scene_layoutid_cache = 2131230922; - + // aapt resource value: 0x7F0800CB public const int transition_transform = 2131230923; - + // aapt resource value: 0x7F0800CC public const int uniform = 2131230924; - + // aapt resource value: 0x7F0800CD public const int up = 2131230925; - + // aapt resource value: 0x7F0800CE public const int useLogo = 2131230926; - + // aapt resource value: 0x7F0800CF public const int view_offset_helper = 2131230927; - + // aapt resource value: 0x7F0800D0 public const int visible = 2131230928; - + // aapt resource value: 0x7F0800D1 public const int volume_item_container = 2131230929; - + // aapt resource value: 0x7F0800D2 public const int withText = 2131230930; - + // aapt resource value: 0x7F0800D3 public const int wrap_content = 2131230931; - + static Id() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Id() { } } - + public partial class Integer { - + // aapt resource value: 0x7F090000 public const int abc_config_activityDefaultDur = 2131296256; - + // aapt resource value: 0x7F090001 public const int abc_config_activityShortDur = 2131296257; - + // aapt resource value: 0x7F090002 public const int app_bar_elevation_anim_duration = 2131296258; - + // aapt resource value: 0x7F090003 public const int bottom_sheet_slide_duration = 2131296259; - + // aapt resource value: 0x7F090004 public const int cancel_button_image_alpha = 2131296260; - + // aapt resource value: 0x7F090005 public const int config_tooltipAnimTime = 2131296261; - + // aapt resource value: 0x7F090006 public const int design_snackbar_text_max_lines = 2131296262; - + // aapt resource value: 0x7F090007 public const int hide_password_duration = 2131296263; - + // aapt resource value: 0x7F090008 public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - + // aapt resource value: 0x7F090009 public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - + // aapt resource value: 0x7F09000A public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - + // aapt resource value: 0x7F09000B public const int show_password_duration = 2131296267; - + // aapt resource value: 0x7F09000C public const int status_bar_notification_info_maxnum = 2131296268; - + static Integer() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Integer() { } } - + public partial class Interpolator { - + // aapt resource value: 0x7F0A0000 public const int mr_fast_out_slow_in = 2131361792; - + // aapt resource value: 0x7F0A0001 public const int mr_linear_out_slow_in = 2131361793; - + static Interpolator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Interpolator() { } } - + public partial class Layout { - + // aapt resource value: 0x7F0B0000 public const int abc_action_bar_title_item = 2131427328; - + // aapt resource value: 0x7F0B0001 public const int abc_action_bar_up_container = 2131427329; - + // aapt resource value: 0x7F0B0002 public const int abc_action_menu_item_layout = 2131427330; - + // aapt resource value: 0x7F0B0003 public const int abc_action_menu_layout = 2131427331; - + // aapt resource value: 0x7F0B0004 public const int abc_action_mode_bar = 2131427332; - + // aapt resource value: 0x7F0B0005 public const int abc_action_mode_close_item_material = 2131427333; - + // aapt resource value: 0x7F0B0006 public const int abc_activity_chooser_view = 2131427334; - + // aapt resource value: 0x7F0B0007 public const int abc_activity_chooser_view_list_item = 2131427335; - + // aapt resource value: 0x7F0B0008 public const int abc_alert_dialog_button_bar_material = 2131427336; - + // aapt resource value: 0x7F0B0009 public const int abc_alert_dialog_material = 2131427337; - + // aapt resource value: 0x7F0B000A public const int abc_alert_dialog_title_material = 2131427338; - + // aapt resource value: 0x7F0B000B public const int abc_dialog_title_material = 2131427339; - + // aapt resource value: 0x7F0B000C public const int abc_expanded_menu_layout = 2131427340; - + // aapt resource value: 0x7F0B000D public const int abc_list_menu_item_checkbox = 2131427341; - + // aapt resource value: 0x7F0B000E public const int abc_list_menu_item_icon = 2131427342; - + // aapt resource value: 0x7F0B000F public const int abc_list_menu_item_layout = 2131427343; - + // aapt resource value: 0x7F0B0010 public const int abc_list_menu_item_radio = 2131427344; - + // aapt resource value: 0x7F0B0011 public const int abc_popup_menu_header_item_layout = 2131427345; - + // aapt resource value: 0x7F0B0012 public const int abc_popup_menu_item_layout = 2131427346; - + // aapt resource value: 0x7F0B0013 public const int abc_screen_content_include = 2131427347; - + // aapt resource value: 0x7F0B0014 public const int abc_screen_simple = 2131427348; - + // aapt resource value: 0x7F0B0015 public const int abc_screen_simple_overlay_action_mode = 2131427349; - + // aapt resource value: 0x7F0B0016 public const int abc_screen_toolbar = 2131427350; - + // aapt resource value: 0x7F0B0017 public const int abc_search_dropdown_item_icons_2line = 2131427351; - + // aapt resource value: 0x7F0B0018 public const int abc_search_view = 2131427352; - + // aapt resource value: 0x7F0B0019 public const int abc_select_dialog_material = 2131427353; - + // aapt resource value: 0x7F0B001A public const int activity_main = 2131427354; - + // aapt resource value: 0x7F0B001B public const int BottomTabLayout = 2131427355; - + // aapt resource value: 0x7F0B001C public const int design_bottom_navigation_item = 2131427356; - + // aapt resource value: 0x7F0B001D public const int design_bottom_sheet_dialog = 2131427357; - + // aapt resource value: 0x7F0B001E public const int design_layout_snackbar = 2131427358; - + // aapt resource value: 0x7F0B001F public const int design_layout_snackbar_include = 2131427359; - + // aapt resource value: 0x7F0B0020 public const int design_layout_tab_icon = 2131427360; - + // aapt resource value: 0x7F0B0021 public const int design_layout_tab_text = 2131427361; - + // aapt resource value: 0x7F0B0022 public const int design_menu_item_action_area = 2131427362; - + // aapt resource value: 0x7F0B0023 public const int design_navigation_item = 2131427363; - + // aapt resource value: 0x7F0B0024 public const int design_navigation_item_header = 2131427364; - + // aapt resource value: 0x7F0B0025 public const int design_navigation_item_separator = 2131427365; - + // aapt resource value: 0x7F0B0026 public const int design_navigation_item_subheader = 2131427366; - + // aapt resource value: 0x7F0B0027 public const int design_navigation_menu = 2131427367; - + // aapt resource value: 0x7F0B0028 public const int design_navigation_menu_item = 2131427368; - + // aapt resource value: 0x7F0B0029 public const int design_text_input_password_icon = 2131427369; - + // aapt resource value: 0x7F0B002A public const int FlyoutContent = 2131427370; - + // aapt resource value: 0x7F0B002B public const int mr_chooser_dialog = 2131427371; - + // aapt resource value: 0x7F0B002C public const int mr_chooser_list_item = 2131427372; - + // aapt resource value: 0x7F0B002D public const int mr_controller_material_dialog_b = 2131427373; - + // aapt resource value: 0x7F0B002E public const int mr_controller_volume_item = 2131427374; - + // aapt resource value: 0x7F0B002F public const int mr_playback_control = 2131427375; - + // aapt resource value: 0x7F0B0030 public const int mr_volume_control = 2131427376; - + // aapt resource value: 0x7F0B0031 public const int notification_action = 2131427377; - + // aapt resource value: 0x7F0B0032 public const int notification_action_tombstone = 2131427378; - + // aapt resource value: 0x7F0B0033 public const int notification_media_action = 2131427379; - + // aapt resource value: 0x7F0B0034 public const int notification_media_cancel_action = 2131427380; - + // aapt resource value: 0x7F0B0035 public const int notification_template_big_media = 2131427381; - + // aapt resource value: 0x7F0B0036 public const int notification_template_big_media_custom = 2131427382; - + // aapt resource value: 0x7F0B0037 public const int notification_template_big_media_narrow = 2131427383; - + // aapt resource value: 0x7F0B0038 public const int notification_template_big_media_narrow_custom = 2131427384; - + // aapt resource value: 0x7F0B0039 public const int notification_template_custom_big = 2131427385; - + // aapt resource value: 0x7F0B003A public const int notification_template_icon_group = 2131427386; - + // aapt resource value: 0x7F0B003B public const int notification_template_lines_media = 2131427387; - + // aapt resource value: 0x7F0B003C public const int notification_template_media = 2131427388; - + // aapt resource value: 0x7F0B003D public const int notification_template_media_custom = 2131427389; - + // aapt resource value: 0x7F0B003E public const int notification_template_part_chronometer = 2131427390; - + // aapt resource value: 0x7F0B003F public const int notification_template_part_time = 2131427391; - + // aapt resource value: 0x7F0B0040 public const int RootLayout = 2131427392; - + // aapt resource value: 0x7F0B0041 public const int select_dialog_item_material = 2131427393; - + // aapt resource value: 0x7F0B0042 public const int select_dialog_multichoice_material = 2131427394; - + // aapt resource value: 0x7F0B0043 public const int select_dialog_singlechoice_material = 2131427395; - + // aapt resource value: 0x7F0B0044 public const int ShellContent = 2131427396; - + // aapt resource value: 0x7F0B0045 public const int support_simple_spinner_dropdown_item = 2131427397; - + // aapt resource value: 0x7F0B0046 public const int tooltip = 2131427398; - + static Layout() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Layout() { } } - + public partial class Mipmap { - + // aapt resource value: 0x7F0C0000 public const int ic_launcher = 2131492864; - + // aapt resource value: 0x7F0C0001 public const int ic_launcher_foreground = 2131492865; - + // aapt resource value: 0x7F0C0002 public const int ic_launcher_round = 2131492866; - + static Mipmap() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Mipmap() { } } - + public partial class String { - + // aapt resource value: 0x7F0D0000 public const int abc_action_bar_home_description = 2131558400; - + // aapt resource value: 0x7F0D0001 public const int abc_action_bar_up_description = 2131558401; - + // aapt resource value: 0x7F0D0002 public const int abc_action_menu_overflow_description = 2131558402; - + // aapt resource value: 0x7F0D0003 public const int abc_action_mode_done = 2131558403; - + // aapt resource value: 0x7F0D0005 public const int abc_activitychooserview_choose_application = 2131558405; - + // aapt resource value: 0x7F0D0004 public const int abc_activity_chooser_view_see_all = 2131558404; - + // aapt resource value: 0x7F0D0006 public const int abc_capital_off = 2131558406; - + // aapt resource value: 0x7F0D0007 public const int abc_capital_on = 2131558407; - + // aapt resource value: 0x7F0D0008 public const int abc_font_family_body_1_material = 2131558408; - + // aapt resource value: 0x7F0D0009 public const int abc_font_family_body_2_material = 2131558409; - + // aapt resource value: 0x7F0D000A public const int abc_font_family_button_material = 2131558410; - + // aapt resource value: 0x7F0D000B public const int abc_font_family_caption_material = 2131558411; - + // aapt resource value: 0x7F0D000C public const int abc_font_family_display_1_material = 2131558412; - + // aapt resource value: 0x7F0D000D public const int abc_font_family_display_2_material = 2131558413; - + // aapt resource value: 0x7F0D000E public const int abc_font_family_display_3_material = 2131558414; - + // aapt resource value: 0x7F0D000F public const int abc_font_family_display_4_material = 2131558415; - + // aapt resource value: 0x7F0D0010 public const int abc_font_family_headline_material = 2131558416; - + // aapt resource value: 0x7F0D0011 public const int abc_font_family_menu_material = 2131558417; - + // aapt resource value: 0x7F0D0012 public const int abc_font_family_subhead_material = 2131558418; - + // aapt resource value: 0x7F0D0013 public const int abc_font_family_title_material = 2131558419; - + // aapt resource value: 0x7F0D0015 public const int abc_searchview_description_clear = 2131558421; - + // aapt resource value: 0x7F0D0016 public const int abc_searchview_description_query = 2131558422; - + // aapt resource value: 0x7F0D0017 public const int abc_searchview_description_search = 2131558423; - + // aapt resource value: 0x7F0D0018 public const int abc_searchview_description_submit = 2131558424; - + // aapt resource value: 0x7F0D0019 public const int abc_searchview_description_voice = 2131558425; - + // aapt resource value: 0x7F0D0014 public const int abc_search_hint = 2131558420; - + // aapt resource value: 0x7F0D001A public const int abc_shareactionprovider_share_with = 2131558426; - + // aapt resource value: 0x7F0D001B public const int abc_shareactionprovider_share_with_application = 2131558427; - + // aapt resource value: 0x7F0D001C public const int abc_toolbar_collapse_description = 2131558428; - + // aapt resource value: 0x7F0D001D public const int action_settings = 2131558429; - + // aapt resource value: 0x7F0D001F public const int appbar_scrolling_view_behavior = 2131558431; - + // aapt resource value: 0x7F0D001E public const int app_name = 2131558430; - + // aapt resource value: 0x7F0D0020 public const int bottom_sheet_behavior = 2131558432; - + // aapt resource value: 0x7F0D0021 public const int character_counter_pattern = 2131558433; - + // aapt resource value: 0x7F0D0022 public const int mr_button_content_description = 2131558434; - + // aapt resource value: 0x7F0D0023 public const int mr_cast_button_connected = 2131558435; - + // aapt resource value: 0x7F0D0024 public const int mr_cast_button_connecting = 2131558436; - + // aapt resource value: 0x7F0D0025 public const int mr_cast_button_disconnected = 2131558437; - + // aapt resource value: 0x7F0D0026 public const int mr_chooser_searching = 2131558438; - + // aapt resource value: 0x7F0D0027 public const int mr_chooser_title = 2131558439; - + // aapt resource value: 0x7F0D0028 public const int mr_controller_album_art = 2131558440; - + // aapt resource value: 0x7F0D0029 public const int mr_controller_casting_screen = 2131558441; - + // aapt resource value: 0x7F0D002A public const int mr_controller_close_description = 2131558442; - + // aapt resource value: 0x7F0D002B public const int mr_controller_collapse_group = 2131558443; - + // aapt resource value: 0x7F0D002C public const int mr_controller_disconnect = 2131558444; - + // aapt resource value: 0x7F0D002D public const int mr_controller_expand_group = 2131558445; - + // aapt resource value: 0x7F0D002E public const int mr_controller_no_info_available = 2131558446; - + // aapt resource value: 0x7F0D002F public const int mr_controller_no_media_selected = 2131558447; - + // aapt resource value: 0x7F0D0030 public const int mr_controller_pause = 2131558448; - + // aapt resource value: 0x7F0D0031 public const int mr_controller_play = 2131558449; - + // aapt resource value: 0x7F0D0032 public const int mr_controller_stop = 2131558450; - + // aapt resource value: 0x7F0D0033 public const int mr_controller_stop_casting = 2131558451; - + // aapt resource value: 0x7F0D0034 public const int mr_controller_volume_slider = 2131558452; - + // aapt resource value: 0x7F0D0035 public const int mr_system_route_name = 2131558453; - + // aapt resource value: 0x7F0D0036 public const int mr_user_route_category_name = 2131558454; - + // aapt resource value: 0x7F0D0037 public const int password_toggle_content_description = 2131558455; - + // aapt resource value: 0x7F0D0038 public const int path_password_eye = 2131558456; - + // aapt resource value: 0x7F0D0039 public const int path_password_eye_mask_strike_through = 2131558457; - + // aapt resource value: 0x7F0D003A public const int path_password_eye_mask_visible = 2131558458; - + // aapt resource value: 0x7F0D003B public const int path_password_strike_through = 2131558459; - + // aapt resource value: 0x7F0D003C public const int search_menu_title = 2131558460; - + // aapt resource value: 0x7F0D003D public const int status_bar_notification_info_overflow = 2131558461; - + static String() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private String() { } } - + public partial class Style { - + // aapt resource value: 0x7F0E0000 public const int AlertDialog_AppCompat = 2131623936; - + // aapt resource value: 0x7F0E0001 public const int AlertDialog_AppCompat_Light = 2131623937; - + // aapt resource value: 0x7F0E0002 public const int Animation_AppCompat_Dialog = 2131623938; - + // aapt resource value: 0x7F0E0003 public const int Animation_AppCompat_DropDownUp = 2131623939; - + // aapt resource value: 0x7F0E0004 public const int Animation_AppCompat_Tooltip = 2131623940; - + // aapt resource value: 0x7F0E0005 public const int Animation_Design_BottomSheetDialog = 2131623941; - + // aapt resource value: 0x7F0E0006 public const int Base_AlertDialog_AppCompat = 2131623942; - + // aapt resource value: 0x7F0E0007 public const int Base_AlertDialog_AppCompat_Light = 2131623943; - + // aapt resource value: 0x7F0E0008 public const int Base_Animation_AppCompat_Dialog = 2131623944; - + // aapt resource value: 0x7F0E0009 public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - + // aapt resource value: 0x7F0E000A public const int Base_Animation_AppCompat_Tooltip = 2131623946; - + // aapt resource value: 0x7F0E000B public const int Base_CardView = 2131623947; - + // aapt resource value: 0x7F0E000D public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - + // aapt resource value: 0x7F0E000C public const int Base_DialogWindowTitle_AppCompat = 2131623948; - + // aapt resource value: 0x7F0E000E public const int Base_TextAppearance_AppCompat = 2131623950; - + // aapt resource value: 0x7F0E000F public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - + // aapt resource value: 0x7F0E0010 public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - + // aapt resource value: 0x7F0E0011 public const int Base_TextAppearance_AppCompat_Button = 2131623953; - + // aapt resource value: 0x7F0E0012 public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - + // aapt resource value: 0x7F0E0013 public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - + // aapt resource value: 0x7F0E0014 public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - + // aapt resource value: 0x7F0E0015 public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - + // aapt resource value: 0x7F0E0016 public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - + // aapt resource value: 0x7F0E0017 public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - + // aapt resource value: 0x7F0E0018 public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - + // aapt resource value: 0x7F0E0019 public const int Base_TextAppearance_AppCompat_Large = 2131623961; - + // aapt resource value: 0x7F0E001A public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - + // aapt resource value: 0x7F0E001B public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - + // aapt resource value: 0x7F0E001C public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - + // aapt resource value: 0x7F0E001D public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - + // aapt resource value: 0x7F0E001E public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - + // aapt resource value: 0x7F0E001F public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - + // aapt resource value: 0x7F0E0020 public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - + // aapt resource value: 0x7F0E0021 public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - + // aapt resource value: 0x7F0E0022 public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - + // aapt resource value: 0x7F0E0023 public const int Base_TextAppearance_AppCompat_Small = 2131623971; - + // aapt resource value: 0x7F0E0024 public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - + // aapt resource value: 0x7F0E0025 public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - + // aapt resource value: 0x7F0E0026 public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - + // aapt resource value: 0x7F0E0027 public const int Base_TextAppearance_AppCompat_Title = 2131623975; - + // aapt resource value: 0x7F0E0028 public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - + // aapt resource value: 0x7F0E0029 public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - + // aapt resource value: 0x7F0E002A public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - + // aapt resource value: 0x7F0E002B public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - + // aapt resource value: 0x7F0E002C public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - + // aapt resource value: 0x7F0E002D public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - + // aapt resource value: 0x7F0E002E public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - + // aapt resource value: 0x7F0E002F public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - + // aapt resource value: 0x7F0E0030 public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - + // aapt resource value: 0x7F0E0031 public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - + // aapt resource value: 0x7F0E0032 public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - + // aapt resource value: 0x7F0E0033 public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - + // aapt resource value: 0x7F0E0034 public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - + // aapt resource value: 0x7F0E0035 public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - + // aapt resource value: 0x7F0E0036 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - + // aapt resource value: 0x7F0E0037 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - + // aapt resource value: 0x7F0E0038 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - + // aapt resource value: 0x7F0E0039 public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - + // aapt resource value: 0x7F0E003A public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - + // aapt resource value: 0x7F0E003B public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - + // aapt resource value: 0x7F0E003C public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - + // aapt resource value: 0x7F0E003D public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - + // aapt resource value: 0x7F0E004C public const int Base_ThemeOverlay_AppCompat = 2131624012; - + // aapt resource value: 0x7F0E004D public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - + // aapt resource value: 0x7F0E004E public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - + // aapt resource value: 0x7F0E004F public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - + // aapt resource value: 0x7F0E0050 public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - + // aapt resource value: 0x7F0E0051 public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - + // aapt resource value: 0x7F0E0052 public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - + // aapt resource value: 0x7F0E003E public const int Base_Theme_AppCompat = 2131623998; - + // aapt resource value: 0x7F0E003F public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - + // aapt resource value: 0x7F0E0040 public const int Base_Theme_AppCompat_Dialog = 2131624000; - + // aapt resource value: 0x7F0E0044 public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - + // aapt resource value: 0x7F0E0041 public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - + // aapt resource value: 0x7F0E0042 public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - + // aapt resource value: 0x7F0E0043 public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - + // aapt resource value: 0x7F0E0045 public const int Base_Theme_AppCompat_Light = 2131624005; - + // aapt resource value: 0x7F0E0046 public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - + // aapt resource value: 0x7F0E0047 public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - + // aapt resource value: 0x7F0E004B public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - + // aapt resource value: 0x7F0E0048 public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - + // aapt resource value: 0x7F0E0049 public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - + // aapt resource value: 0x7F0E004A public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - + // aapt resource value: 0x7F0E0055 public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - + // aapt resource value: 0x7F0E0053 public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - + // aapt resource value: 0x7F0E0054 public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - + // aapt resource value: 0x7F0E0056 public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - + // aapt resource value: 0x7F0E0057 public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - + // aapt resource value: 0x7F0E0058 public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - + // aapt resource value: 0x7F0E005D public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - + // aapt resource value: 0x7F0E0059 public const int Base_V21_Theme_AppCompat = 2131624025; - + // aapt resource value: 0x7F0E005A public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - + // aapt resource value: 0x7F0E005B public const int Base_V21_Theme_AppCompat_Light = 2131624027; - + // aapt resource value: 0x7F0E005C public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - + // aapt resource value: 0x7F0E005E public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - + // aapt resource value: 0x7F0E005F public const int Base_V22_Theme_AppCompat = 2131624031; - + // aapt resource value: 0x7F0E0060 public const int Base_V22_Theme_AppCompat_Light = 2131624032; - + // aapt resource value: 0x7F0E0061 public const int Base_V23_Theme_AppCompat = 2131624033; - + // aapt resource value: 0x7F0E0062 public const int Base_V23_Theme_AppCompat_Light = 2131624034; - + // aapt resource value: 0x7F0E0063 public const int Base_V26_Theme_AppCompat = 2131624035; - + // aapt resource value: 0x7F0E0064 public const int Base_V26_Theme_AppCompat_Light = 2131624036; - + // aapt resource value: 0x7F0E0065 public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - + // aapt resource value: 0x7F0E0066 public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - + // aapt resource value: 0x7F0E006B public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - + // aapt resource value: 0x7F0E0067 public const int Base_V7_Theme_AppCompat = 2131624039; - + // aapt resource value: 0x7F0E0068 public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - + // aapt resource value: 0x7F0E0069 public const int Base_V7_Theme_AppCompat_Light = 2131624041; - + // aapt resource value: 0x7F0E006A public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - + // aapt resource value: 0x7F0E006C public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - + // aapt resource value: 0x7F0E006D public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - + // aapt resource value: 0x7F0E006E public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - + // aapt resource value: 0x7F0E006F public const int Base_Widget_AppCompat_ActionBar = 2131624047; - + // aapt resource value: 0x7F0E0070 public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - + // aapt resource value: 0x7F0E0071 public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - + // aapt resource value: 0x7F0E0072 public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - + // aapt resource value: 0x7F0E0073 public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - + // aapt resource value: 0x7F0E0074 public const int Base_Widget_AppCompat_ActionButton = 2131624052; - + // aapt resource value: 0x7F0E0075 public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - + // aapt resource value: 0x7F0E0076 public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - + // aapt resource value: 0x7F0E0077 public const int Base_Widget_AppCompat_ActionMode = 2131624055; - + // aapt resource value: 0x7F0E0078 public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - + // aapt resource value: 0x7F0E0079 public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - + // aapt resource value: 0x7F0E007A public const int Base_Widget_AppCompat_Button = 2131624058; - + // aapt resource value: 0x7F0E0080 public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - + // aapt resource value: 0x7F0E0081 public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - + // aapt resource value: 0x7F0E007B public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - + // aapt resource value: 0x7F0E007C public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - + // aapt resource value: 0x7F0E007D public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - + // aapt resource value: 0x7F0E007E public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - + // aapt resource value: 0x7F0E007F public const int Base_Widget_AppCompat_Button_Small = 2131624063; - + // aapt resource value: 0x7F0E0082 public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - + // aapt resource value: 0x7F0E0083 public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - + // aapt resource value: 0x7F0E0084 public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - + // aapt resource value: 0x7F0E0085 public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - + // aapt resource value: 0x7F0E0086 public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - + // aapt resource value: 0x7F0E0087 public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - + // aapt resource value: 0x7F0E0088 public const int Base_Widget_AppCompat_EditText = 2131624072; - + // aapt resource value: 0x7F0E0089 public const int Base_Widget_AppCompat_ImageButton = 2131624073; - + // aapt resource value: 0x7F0E008A public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - + // aapt resource value: 0x7F0E008B public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - + // aapt resource value: 0x7F0E008C public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - + // aapt resource value: 0x7F0E008D public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - + // aapt resource value: 0x7F0E008E public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - + // aapt resource value: 0x7F0E008F public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - + // aapt resource value: 0x7F0E0090 public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - + // aapt resource value: 0x7F0E0091 public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - + // aapt resource value: 0x7F0E0092 public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - + // aapt resource value: 0x7F0E0093 public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - + // aapt resource value: 0x7F0E0094 public const int Base_Widget_AppCompat_ListView = 2131624084; - + // aapt resource value: 0x7F0E0095 public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - + // aapt resource value: 0x7F0E0096 public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - + // aapt resource value: 0x7F0E0097 public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - + // aapt resource value: 0x7F0E0098 public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - + // aapt resource value: 0x7F0E0099 public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - + // aapt resource value: 0x7F0E009A public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - + // aapt resource value: 0x7F0E009B public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - + // aapt resource value: 0x7F0E009C public const int Base_Widget_AppCompat_RatingBar = 2131624092; - + // aapt resource value: 0x7F0E009D public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - + // aapt resource value: 0x7F0E009E public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - + // aapt resource value: 0x7F0E009F public const int Base_Widget_AppCompat_SearchView = 2131624095; - + // aapt resource value: 0x7F0E00A0 public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - + // aapt resource value: 0x7F0E00A1 public const int Base_Widget_AppCompat_SeekBar = 2131624097; - + // aapt resource value: 0x7F0E00A2 public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - + // aapt resource value: 0x7F0E00A3 public const int Base_Widget_AppCompat_Spinner = 2131624099; - + // aapt resource value: 0x7F0E00A4 public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - + // aapt resource value: 0x7F0E00A5 public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - + // aapt resource value: 0x7F0E00A6 public const int Base_Widget_AppCompat_Toolbar = 2131624102; - + // aapt resource value: 0x7F0E00A7 public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - + // aapt resource value: 0x7F0E00A8 public const int Base_Widget_Design_AppBarLayout = 2131624104; - + // aapt resource value: 0x7F0E00A9 public const int Base_Widget_Design_TabLayout = 2131624105; - + // aapt resource value: 0x7F0E00AA public const int CardView = 2131624106; - + // aapt resource value: 0x7F0E00AB public const int CardView_Dark = 2131624107; - + // aapt resource value: 0x7F0E00AC public const int CardView_Light = 2131624108; - + // aapt resource value: 0x7F0E00AD public const int Platform_AppCompat = 2131624109; - + // aapt resource value: 0x7F0E00AE public const int Platform_AppCompat_Light = 2131624110; - + // aapt resource value: 0x7F0E00AF public const int Platform_ThemeOverlay_AppCompat = 2131624111; - + // aapt resource value: 0x7F0E00B0 public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - + // aapt resource value: 0x7F0E00B1 public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - + // aapt resource value: 0x7F0E00B2 public const int Platform_V11_AppCompat = 2131624114; - + // aapt resource value: 0x7F0E00B3 public const int Platform_V11_AppCompat_Light = 2131624115; - + // aapt resource value: 0x7F0E00B4 public const int Platform_V14_AppCompat = 2131624116; - + // aapt resource value: 0x7F0E00B5 public const int Platform_V14_AppCompat_Light = 2131624117; - + // aapt resource value: 0x7F0E00B6 public const int Platform_V21_AppCompat = 2131624118; - + // aapt resource value: 0x7F0E00B7 public const int Platform_V21_AppCompat_Light = 2131624119; - + // aapt resource value: 0x7F0E00B8 public const int Platform_V25_AppCompat = 2131624120; - + // aapt resource value: 0x7F0E00B9 public const int Platform_V25_AppCompat_Light = 2131624121; - + // aapt resource value: 0x7F0E00BA public const int Platform_Widget_AppCompat_Spinner = 2131624122; - + // aapt resource value: 0x7F0E00BB public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - + // aapt resource value: 0x7F0E00BC public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - + // aapt resource value: 0x7F0E00BD public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - + // aapt resource value: 0x7F0E00BE public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - + // aapt resource value: 0x7F0E00BF public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - + // aapt resource value: 0x7F0E00C0 public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - + // aapt resource value: 0x7F0E00C6 public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - + // aapt resource value: 0x7F0E00C1 public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - + // aapt resource value: 0x7F0E00C2 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - + // aapt resource value: 0x7F0E00C3 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - + // aapt resource value: 0x7F0E00C4 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - + // aapt resource value: 0x7F0E00C5 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - + // aapt resource value: 0x7F0E00C7 public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - + // aapt resource value: 0x7F0E00C8 public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - + // aapt resource value: 0x7F0E00C9 public const int TextAppearance_AppCompat = 2131624137; - + // aapt resource value: 0x7F0E00CA public const int TextAppearance_AppCompat_Body1 = 2131624138; - + // aapt resource value: 0x7F0E00CB public const int TextAppearance_AppCompat_Body2 = 2131624139; - + // aapt resource value: 0x7F0E00CC public const int TextAppearance_AppCompat_Button = 2131624140; - + // aapt resource value: 0x7F0E00CD public const int TextAppearance_AppCompat_Caption = 2131624141; - + // aapt resource value: 0x7F0E00CE public const int TextAppearance_AppCompat_Display1 = 2131624142; - + // aapt resource value: 0x7F0E00CF public const int TextAppearance_AppCompat_Display2 = 2131624143; - + // aapt resource value: 0x7F0E00D0 public const int TextAppearance_AppCompat_Display3 = 2131624144; - + // aapt resource value: 0x7F0E00D1 public const int TextAppearance_AppCompat_Display4 = 2131624145; - + // aapt resource value: 0x7F0E00D2 public const int TextAppearance_AppCompat_Headline = 2131624146; - + // aapt resource value: 0x7F0E00D3 public const int TextAppearance_AppCompat_Inverse = 2131624147; - + // aapt resource value: 0x7F0E00D4 public const int TextAppearance_AppCompat_Large = 2131624148; - + // aapt resource value: 0x7F0E00D5 public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - + // aapt resource value: 0x7F0E00D6 public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - + // aapt resource value: 0x7F0E00D7 public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - + // aapt resource value: 0x7F0E00D8 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - + // aapt resource value: 0x7F0E00D9 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - + // aapt resource value: 0x7F0E00DA public const int TextAppearance_AppCompat_Medium = 2131624154; - + // aapt resource value: 0x7F0E00DB public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - + // aapt resource value: 0x7F0E00DC public const int TextAppearance_AppCompat_Menu = 2131624156; - + // aapt resource value: 0x7F0E00DD public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - + // aapt resource value: 0x7F0E00DE public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - + // aapt resource value: 0x7F0E00DF public const int TextAppearance_AppCompat_Small = 2131624159; - + // aapt resource value: 0x7F0E00E0 public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - + // aapt resource value: 0x7F0E00E1 public const int TextAppearance_AppCompat_Subhead = 2131624161; - + // aapt resource value: 0x7F0E00E2 public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - + // aapt resource value: 0x7F0E00E3 public const int TextAppearance_AppCompat_Title = 2131624163; - + // aapt resource value: 0x7F0E00E4 public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - + // aapt resource value: 0x7F0E00E5 public const int TextAppearance_AppCompat_Tooltip = 2131624165; - + // aapt resource value: 0x7F0E00E6 public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - + // aapt resource value: 0x7F0E00E7 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - + // aapt resource value: 0x7F0E00E8 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - + // aapt resource value: 0x7F0E00E9 public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - + // aapt resource value: 0x7F0E00EA public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - + // aapt resource value: 0x7F0E00EB public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - + // aapt resource value: 0x7F0E00EC public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - + // aapt resource value: 0x7F0E00ED public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - + // aapt resource value: 0x7F0E00EE public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - + // aapt resource value: 0x7F0E00EF public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - + // aapt resource value: 0x7F0E00F0 public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - + // aapt resource value: 0x7F0E00F1 public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - + // aapt resource value: 0x7F0E00F2 public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - + // aapt resource value: 0x7F0E00F3 public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - + // aapt resource value: 0x7F0E00F4 public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - + // aapt resource value: 0x7F0E00F5 public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - + // aapt resource value: 0x7F0E00F6 public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - + // aapt resource value: 0x7F0E00F7 public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - + // aapt resource value: 0x7F0E00F8 public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - + // aapt resource value: 0x7F0E00F9 public const int TextAppearance_Compat_Notification = 2131624185; - + // aapt resource value: 0x7F0E00FA public const int TextAppearance_Compat_Notification_Info = 2131624186; - + // aapt resource value: 0x7F0E00FB public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - + // aapt resource value: 0x7F0E00FC public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - + // aapt resource value: 0x7F0E00FD public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - + // aapt resource value: 0x7F0E00FE public const int TextAppearance_Compat_Notification_Media = 2131624190; - + // aapt resource value: 0x7F0E00FF public const int TextAppearance_Compat_Notification_Time = 2131624191; - + // aapt resource value: 0x7F0E0100 public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - + // aapt resource value: 0x7F0E0101 public const int TextAppearance_Compat_Notification_Title = 2131624193; - + // aapt resource value: 0x7F0E0102 public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - + // aapt resource value: 0x7F0E0103 public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - + // aapt resource value: 0x7F0E0104 public const int TextAppearance_Design_Counter = 2131624196; - + // aapt resource value: 0x7F0E0105 public const int TextAppearance_Design_Counter_Overflow = 2131624197; - + // aapt resource value: 0x7F0E0106 public const int TextAppearance_Design_Error = 2131624198; - + // aapt resource value: 0x7F0E0107 public const int TextAppearance_Design_Hint = 2131624199; - + // aapt resource value: 0x7F0E0108 public const int TextAppearance_Design_Snackbar_Message = 2131624200; - + // aapt resource value: 0x7F0E0109 public const int TextAppearance_Design_Tab = 2131624201; - + // aapt resource value: 0x7F0E010A public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - + // aapt resource value: 0x7F0E010B public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - + // aapt resource value: 0x7F0E010C public const int TextAppearance_MediaRouter_Title = 2131624204; - + // aapt resource value: 0x7F0E010D public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - + // aapt resource value: 0x7F0E010E public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - + // aapt resource value: 0x7F0E010F public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - + // aapt resource value: 0x7F0E012F public const int ThemeOverlay_AppCompat = 2131624239; - + // aapt resource value: 0x7F0E0130 public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - + // aapt resource value: 0x7F0E0131 public const int ThemeOverlay_AppCompat_Dark = 2131624241; - + // aapt resource value: 0x7F0E0132 public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - + // aapt resource value: 0x7F0E0133 public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - + // aapt resource value: 0x7F0E0134 public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - + // aapt resource value: 0x7F0E0135 public const int ThemeOverlay_AppCompat_Light = 2131624245; - + // aapt resource value: 0x7F0E0136 public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - + // aapt resource value: 0x7F0E0137 public const int ThemeOverlay_MediaRouter_Light = 2131624247; - + // aapt resource value: 0x7F0E0110 public const int Theme_AppCompat = 2131624208; - + // aapt resource value: 0x7F0E0111 public const int Theme_AppCompat_CompactMenu = 2131624209; - + // aapt resource value: 0x7F0E0112 public const int Theme_AppCompat_DayNight = 2131624210; - + // aapt resource value: 0x7F0E0113 public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - + // aapt resource value: 0x7F0E0114 public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - + // aapt resource value: 0x7F0E0117 public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - + // aapt resource value: 0x7F0E0115 public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - + // aapt resource value: 0x7F0E0116 public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - + // aapt resource value: 0x7F0E0118 public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - + // aapt resource value: 0x7F0E0119 public const int Theme_AppCompat_Dialog = 2131624217; - + // aapt resource value: 0x7F0E011C public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - + // aapt resource value: 0x7F0E011A public const int Theme_AppCompat_Dialog_Alert = 2131624218; - + // aapt resource value: 0x7F0E011B public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - + // aapt resource value: 0x7F0E011D public const int Theme_AppCompat_Light = 2131624221; - + // aapt resource value: 0x7F0E011E public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - + // aapt resource value: 0x7F0E011F public const int Theme_AppCompat_Light_Dialog = 2131624223; - + // aapt resource value: 0x7F0E0122 public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - + // aapt resource value: 0x7F0E0120 public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - + // aapt resource value: 0x7F0E0121 public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - + // aapt resource value: 0x7F0E0123 public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - + // aapt resource value: 0x7F0E0124 public const int Theme_AppCompat_NoActionBar = 2131624228; - + // aapt resource value: 0x7F0E0125 public const int Theme_Design = 2131624229; - + // aapt resource value: 0x7F0E0126 public const int Theme_Design_BottomSheetDialog = 2131624230; - + // aapt resource value: 0x7F0E0127 public const int Theme_Design_Light = 2131624231; - + // aapt resource value: 0x7F0E0128 public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - + // aapt resource value: 0x7F0E0129 public const int Theme_Design_Light_NoActionBar = 2131624233; - + // aapt resource value: 0x7F0E012A public const int Theme_Design_NoActionBar = 2131624234; - + // aapt resource value: 0x7F0E012B public const int Theme_MediaRouter = 2131624235; - + // aapt resource value: 0x7F0E012C public const int Theme_MediaRouter_Light = 2131624236; - + // aapt resource value: 0x7F0E012E public const int Theme_MediaRouter_LightControlPanel = 2131624238; - + // aapt resource value: 0x7F0E012D public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - + // aapt resource value: 0x7F0E0138 public const int Widget_AppCompat_ActionBar = 2131624248; - + // aapt resource value: 0x7F0E0139 public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - + // aapt resource value: 0x7F0E013A public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - + // aapt resource value: 0x7F0E013B public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - + // aapt resource value: 0x7F0E013C public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - + // aapt resource value: 0x7F0E013D public const int Widget_AppCompat_ActionButton = 2131624253; - + // aapt resource value: 0x7F0E013E public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - + // aapt resource value: 0x7F0E013F public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - + // aapt resource value: 0x7F0E0140 public const int Widget_AppCompat_ActionMode = 2131624256; - + // aapt resource value: 0x7F0E0141 public const int Widget_AppCompat_ActivityChooserView = 2131624257; - + // aapt resource value: 0x7F0E0142 public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - + // aapt resource value: 0x7F0E0143 public const int Widget_AppCompat_Button = 2131624259; - + // aapt resource value: 0x7F0E0149 public const int Widget_AppCompat_ButtonBar = 2131624265; - + // aapt resource value: 0x7F0E014A public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - + // aapt resource value: 0x7F0E0144 public const int Widget_AppCompat_Button_Borderless = 2131624260; - + // aapt resource value: 0x7F0E0145 public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - + // aapt resource value: 0x7F0E0146 public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - + // aapt resource value: 0x7F0E0147 public const int Widget_AppCompat_Button_Colored = 2131624263; - + // aapt resource value: 0x7F0E0148 public const int Widget_AppCompat_Button_Small = 2131624264; - + // aapt resource value: 0x7F0E014B public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - + // aapt resource value: 0x7F0E014C public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - + // aapt resource value: 0x7F0E014D public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - + // aapt resource value: 0x7F0E014E public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - + // aapt resource value: 0x7F0E014F public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - + // aapt resource value: 0x7F0E0150 public const int Widget_AppCompat_EditText = 2131624272; - + // aapt resource value: 0x7F0E0151 public const int Widget_AppCompat_ImageButton = 2131624273; - + // aapt resource value: 0x7F0E0152 public const int Widget_AppCompat_Light_ActionBar = 2131624274; - + // aapt resource value: 0x7F0E0153 public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - + // aapt resource value: 0x7F0E0154 public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - + // aapt resource value: 0x7F0E0155 public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - + // aapt resource value: 0x7F0E0156 public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - + // aapt resource value: 0x7F0E0157 public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - + // aapt resource value: 0x7F0E0158 public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - + // aapt resource value: 0x7F0E0159 public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - + // aapt resource value: 0x7F0E015A public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - + // aapt resource value: 0x7F0E015B public const int Widget_AppCompat_Light_ActionButton = 2131624283; - + // aapt resource value: 0x7F0E015C public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - + // aapt resource value: 0x7F0E015D public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - + // aapt resource value: 0x7F0E015E public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - + // aapt resource value: 0x7F0E015F public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - + // aapt resource value: 0x7F0E0160 public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - + // aapt resource value: 0x7F0E0161 public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - + // aapt resource value: 0x7F0E0162 public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - + // aapt resource value: 0x7F0E0163 public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - + // aapt resource value: 0x7F0E0164 public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - + // aapt resource value: 0x7F0E0165 public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - + // aapt resource value: 0x7F0E0166 public const int Widget_AppCompat_Light_SearchView = 2131624294; - + // aapt resource value: 0x7F0E0167 public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - + // aapt resource value: 0x7F0E0168 public const int Widget_AppCompat_ListMenuView = 2131624296; - + // aapt resource value: 0x7F0E0169 public const int Widget_AppCompat_ListPopupWindow = 2131624297; - + // aapt resource value: 0x7F0E016A public const int Widget_AppCompat_ListView = 2131624298; - + // aapt resource value: 0x7F0E016B public const int Widget_AppCompat_ListView_DropDown = 2131624299; - + // aapt resource value: 0x7F0E016C public const int Widget_AppCompat_ListView_Menu = 2131624300; - + // aapt resource value: 0x7F0E016D public const int Widget_AppCompat_PopupMenu = 2131624301; - + // aapt resource value: 0x7F0E016E public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - + // aapt resource value: 0x7F0E016F public const int Widget_AppCompat_PopupWindow = 2131624303; - + // aapt resource value: 0x7F0E0170 public const int Widget_AppCompat_ProgressBar = 2131624304; - + // aapt resource value: 0x7F0E0171 public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - + // aapt resource value: 0x7F0E0172 public const int Widget_AppCompat_RatingBar = 2131624306; - + // aapt resource value: 0x7F0E0173 public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - + // aapt resource value: 0x7F0E0174 public const int Widget_AppCompat_RatingBar_Small = 2131624308; - + // aapt resource value: 0x7F0E0175 public const int Widget_AppCompat_SearchView = 2131624309; - + // aapt resource value: 0x7F0E0176 public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - + // aapt resource value: 0x7F0E0177 public const int Widget_AppCompat_SeekBar = 2131624311; - + // aapt resource value: 0x7F0E0178 public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - + // aapt resource value: 0x7F0E0179 public const int Widget_AppCompat_Spinner = 2131624313; - + // aapt resource value: 0x7F0E017A public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - + // aapt resource value: 0x7F0E017B public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - + // aapt resource value: 0x7F0E017C public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - + // aapt resource value: 0x7F0E017D public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - + // aapt resource value: 0x7F0E017E public const int Widget_AppCompat_Toolbar = 2131624318; - + // aapt resource value: 0x7F0E017F public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - + // aapt resource value: 0x7F0E0180 public const int Widget_Compat_NotificationActionContainer = 2131624320; - + // aapt resource value: 0x7F0E0181 public const int Widget_Compat_NotificationActionText = 2131624321; - + // aapt resource value: 0x7F0E0182 public const int Widget_Design_AppBarLayout = 2131624322; - + // aapt resource value: 0x7F0E0183 public const int Widget_Design_BottomNavigationView = 2131624323; - + // aapt resource value: 0x7F0E0184 public const int Widget_Design_BottomSheet_Modal = 2131624324; - + // aapt resource value: 0x7F0E0185 public const int Widget_Design_CollapsingToolbar = 2131624325; - + // aapt resource value: 0x7F0E0186 public const int Widget_Design_CoordinatorLayout = 2131624326; - + // aapt resource value: 0x7F0E0187 public const int Widget_Design_FloatingActionButton = 2131624327; - + // aapt resource value: 0x7F0E0188 public const int Widget_Design_NavigationView = 2131624328; - + // aapt resource value: 0x7F0E0189 public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - + // aapt resource value: 0x7F0E018A public const int Widget_Design_Snackbar = 2131624330; - + // aapt resource value: 0x7F0E018B public const int Widget_Design_TabLayout = 2131624331; - + // aapt resource value: 0x7F0E018C public const int Widget_Design_TextInputLayout = 2131624332; - + // aapt resource value: 0x7F0E018D public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - + // aapt resource value: 0x7F0E018E public const int Widget_MediaRouter_MediaRouteButton = 2131624334; - + static Style() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Style() { } } - + public partial class Styleable { - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } public static int[] ActionBar = new int[] { 2130903089, @@ -7540,112 +7417,112 @@ public partial class Styleable 2130903335, 2130903379, 2130903389}; - + // aapt resource value: { 0x10100B3 } public static int[] ActionBarLayout = new int[] { 16842931}; - + // aapt resource value: 0 public const int ActionBarLayout_android_layout_gravity = 0; - + // aapt resource value: 0 public const int ActionBar_background = 0; - + // aapt resource value: 1 public const int ActionBar_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionBar_backgroundStacked = 2; - + // aapt resource value: 3 public const int ActionBar_contentInsetEnd = 3; - + // aapt resource value: 4 public const int ActionBar_contentInsetEndWithActions = 4; - + // aapt resource value: 5 public const int ActionBar_contentInsetLeft = 5; - + // aapt resource value: 6 public const int ActionBar_contentInsetRight = 6; - + // aapt resource value: 7 public const int ActionBar_contentInsetStart = 7; - + // aapt resource value: 8 public const int ActionBar_contentInsetStartWithNavigation = 8; - + // aapt resource value: 9 public const int ActionBar_customNavigationLayout = 9; - + // aapt resource value: 10 public const int ActionBar_displayOptions = 10; - + // aapt resource value: 11 public const int ActionBar_divider = 11; - + // aapt resource value: 12 public const int ActionBar_elevation = 12; - + // aapt resource value: 13 public const int ActionBar_height = 13; - + // aapt resource value: 14 public const int ActionBar_hideOnContentScroll = 14; - + // aapt resource value: 15 public const int ActionBar_homeAsUpIndicator = 15; - + // aapt resource value: 16 public const int ActionBar_homeLayout = 16; - + // aapt resource value: 17 public const int ActionBar_icon = 17; - + // aapt resource value: 18 public const int ActionBar_indeterminateProgressStyle = 18; - + // aapt resource value: 19 public const int ActionBar_itemPadding = 19; - + // aapt resource value: 20 public const int ActionBar_logo = 20; - + // aapt resource value: 21 public const int ActionBar_navigationMode = 21; - + // aapt resource value: 22 public const int ActionBar_popupTheme = 22; - + // aapt resource value: 23 public const int ActionBar_progressBarPadding = 23; - + // aapt resource value: 24 public const int ActionBar_progressBarStyle = 24; - + // aapt resource value: 25 public const int ActionBar_subtitle = 25; - + // aapt resource value: 26 public const int ActionBar_subtitleTextStyle = 26; - + // aapt resource value: 27 public const int ActionBar_title = 27; - + // aapt resource value: 28 public const int ActionBar_titleTextStyle = 28; - + // aapt resource value: { 0x101013F } public static int[] ActionMenuItemView = new int[] { 16843071}; - + // aapt resource value: 0 public const int ActionMenuItemView_android_minWidth = 0; - + // aapt resource value: { 0xFFFFFFFF } public static int[] ActionMenuView = new int[] { -1}; - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } public static int[] ActionMode = new int[] { 2130903089, @@ -7654,36 +7531,36 @@ public partial class Styleable 2130903208, 2130903335, 2130903389}; - + // aapt resource value: 0 public const int ActionMode_background = 0; - + // aapt resource value: 1 public const int ActionMode_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionMode_closeItemLayout = 2; - + // aapt resource value: 3 public const int ActionMode_height = 3; - + // aapt resource value: 4 public const int ActionMode_subtitleTextStyle = 4; - + // aapt resource value: 5 public const int ActionMode_titleTextStyle = 5; - + // aapt resource value: { 0x7F03008A,0x7F0300B5 } public static int[] ActivityChooserView = new int[] { 2130903178, 2130903221}; - + // aapt resource value: 0 public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; - + // aapt resource value: 1 public const int ActivityChooserView_initialActivityCount = 1; - + // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } public static int[] AlertDialog = new int[] { 16842994, @@ -7693,28 +7570,28 @@ public partial class Styleable 2130903272, 2130903316, 2130903317}; - + // aapt resource value: 0 public const int AlertDialog_android_layout = 0; - + // aapt resource value: 1 public const int AlertDialog_buttonPanelSideLayout = 1; - + // aapt resource value: 2 public const int AlertDialog_listItemLayout = 2; - + // aapt resource value: 3 public const int AlertDialog_listLayout = 3; - + // aapt resource value: 4 public const int AlertDialog_multiChoiceItemLayout = 4; - + // aapt resource value: 5 public const int AlertDialog_showTitle = 5; - + // aapt resource value: 6 public const int AlertDialog_singleChoiceItemLayout = 6; - + // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } public static int[] AppBarLayout = new int[] { 16842964, @@ -7722,82 +7599,82 @@ public partial class Styleable 16844096, 2130903175, 2130903179}; - + // aapt resource value: { 0x7F03011E,0x7F03011F } public static int[] AppBarLayoutStates = new int[] { 2130903326, 2130903327}; - + // aapt resource value: 0 public const int AppBarLayoutStates_state_collapsed = 0; - + // aapt resource value: 1 public const int AppBarLayoutStates_state_collapsible = 1; - + // aapt resource value: 0 public const int AppBarLayout_android_background = 0; - + // aapt resource value: 2 public const int AppBarLayout_android_keyboardNavigationCluster = 2; - + // aapt resource value: 1 public const int AppBarLayout_android_touchscreenBlocksFocus = 1; - + // aapt resource value: 3 public const int AppBarLayout_elevation = 3; - + // aapt resource value: 4 public const int AppBarLayout_expanded = 4; - + // aapt resource value: { 0x7F0300C8,0x7F0300C9 } public static int[] AppBarLayout_Layout = new int[] { 2130903240, 2130903241}; - + // aapt resource value: 0 public const int AppBarLayout_Layout_layout_scrollFlags = 0; - + // aapt resource value: 1 public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; - + // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } public static int[] AppCompatImageView = new int[] { 16843033, 2130903323, 2130903377, 2130903378}; - + // aapt resource value: 0 public const int AppCompatImageView_android_src = 0; - + // aapt resource value: 1 public const int AppCompatImageView_srcCompat = 1; - + // aapt resource value: 2 public const int AppCompatImageView_tint = 2; - + // aapt resource value: 3 public const int AppCompatImageView_tintMode = 3; - + // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } public static int[] AppCompatSeekBar = new int[] { 16843074, 2130903374, 2130903375, 2130903376}; - + // aapt resource value: 0 public const int AppCompatSeekBar_android_thumb = 0; - + // aapt resource value: 1 public const int AppCompatSeekBar_tickMark = 1; - + // aapt resource value: 2 public const int AppCompatSeekBar_tickMarkTint = 2; - + // aapt resource value: 3 public const int AppCompatSeekBar_tickMarkTintMode = 3; - + // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } public static int[] AppCompatTextHelper = new int[] { 16842804, @@ -7807,28 +7684,28 @@ public partial class Styleable 16843120, 16843666, 16843667}; - + // aapt resource value: 2 public const int AppCompatTextHelper_android_drawableBottom = 2; - + // aapt resource value: 6 public const int AppCompatTextHelper_android_drawableEnd = 6; - + // aapt resource value: 3 public const int AppCompatTextHelper_android_drawableLeft = 3; - + // aapt resource value: 4 public const int AppCompatTextHelper_android_drawableRight = 4; - + // aapt resource value: 5 public const int AppCompatTextHelper_android_drawableStart = 5; - + // aapt resource value: 1 public const int AppCompatTextHelper_android_drawableTop = 1; - + // aapt resource value: 0 public const int AppCompatTextHelper_android_textAppearance = 0; - + // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } public static int[] AppCompatTextView = new int[] { 16842804, @@ -7839,31 +7716,31 @@ public partial class Styleable 2130903088, 2130903195, 2130903357}; - + // aapt resource value: 0 public const int AppCompatTextView_android_textAppearance = 0; - + // aapt resource value: 1 public const int AppCompatTextView_autoSizeMaxTextSize = 1; - + // aapt resource value: 2 public const int AppCompatTextView_autoSizeMinTextSize = 2; - + // aapt resource value: 3 public const int AppCompatTextView_autoSizePresetSizes = 3; - + // aapt resource value: 4 public const int AppCompatTextView_autoSizeStepGranularity = 4; - + // aapt resource value: 5 public const int AppCompatTextView_autoSizeTextType = 5; - + // aapt resource value: 6 public const int AppCompatTextView_fontFamily = 6; - + // aapt resource value: 7 public const int AppCompatTextView_textAllCaps = 7; - + // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } public static int[] AppCompatTheme = new int[] { 16842839, @@ -7985,364 +7862,364 @@ public partial class Styleable 2130903408, 2130903409, 2130903410}; - + // aapt resource value: 2 public const int AppCompatTheme_actionBarDivider = 2; - + // aapt resource value: 3 public const int AppCompatTheme_actionBarItemBackground = 3; - + // aapt resource value: 4 public const int AppCompatTheme_actionBarPopupTheme = 4; - + // aapt resource value: 5 public const int AppCompatTheme_actionBarSize = 5; - + // aapt resource value: 6 public const int AppCompatTheme_actionBarSplitStyle = 6; - + // aapt resource value: 7 public const int AppCompatTheme_actionBarStyle = 7; - + // aapt resource value: 8 public const int AppCompatTheme_actionBarTabBarStyle = 8; - + // aapt resource value: 9 public const int AppCompatTheme_actionBarTabStyle = 9; - + // aapt resource value: 10 public const int AppCompatTheme_actionBarTabTextStyle = 10; - + // aapt resource value: 11 public const int AppCompatTheme_actionBarTheme = 11; - + // aapt resource value: 12 public const int AppCompatTheme_actionBarWidgetTheme = 12; - + // aapt resource value: 13 public const int AppCompatTheme_actionButtonStyle = 13; - + // aapt resource value: 14 public const int AppCompatTheme_actionDropDownStyle = 14; - + // aapt resource value: 15 public const int AppCompatTheme_actionMenuTextAppearance = 15; - + // aapt resource value: 16 public const int AppCompatTheme_actionMenuTextColor = 16; - + // aapt resource value: 17 public const int AppCompatTheme_actionModeBackground = 17; - + // aapt resource value: 18 public const int AppCompatTheme_actionModeCloseButtonStyle = 18; - + // aapt resource value: 19 public const int AppCompatTheme_actionModeCloseDrawable = 19; - + // aapt resource value: 20 public const int AppCompatTheme_actionModeCopyDrawable = 20; - + // aapt resource value: 21 public const int AppCompatTheme_actionModeCutDrawable = 21; - + // aapt resource value: 22 public const int AppCompatTheme_actionModeFindDrawable = 22; - + // aapt resource value: 23 public const int AppCompatTheme_actionModePasteDrawable = 23; - + // aapt resource value: 24 public const int AppCompatTheme_actionModePopupWindowStyle = 24; - + // aapt resource value: 25 public const int AppCompatTheme_actionModeSelectAllDrawable = 25; - + // aapt resource value: 26 public const int AppCompatTheme_actionModeShareDrawable = 26; - + // aapt resource value: 27 public const int AppCompatTheme_actionModeSplitBackground = 27; - + // aapt resource value: 28 public const int AppCompatTheme_actionModeStyle = 28; - + // aapt resource value: 29 public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - + // aapt resource value: 30 public const int AppCompatTheme_actionOverflowButtonStyle = 30; - + // aapt resource value: 31 public const int AppCompatTheme_actionOverflowMenuStyle = 31; - + // aapt resource value: 32 public const int AppCompatTheme_activityChooserViewStyle = 32; - + // aapt resource value: 33 public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; - + // aapt resource value: 34 public const int AppCompatTheme_alertDialogCenterButtons = 34; - + // aapt resource value: 35 public const int AppCompatTheme_alertDialogStyle = 35; - + // aapt resource value: 36 public const int AppCompatTheme_alertDialogTheme = 36; - + // aapt resource value: 1 public const int AppCompatTheme_android_windowAnimationStyle = 1; - + // aapt resource value: 0 public const int AppCompatTheme_android_windowIsFloating = 0; - + // aapt resource value: 37 public const int AppCompatTheme_autoCompleteTextViewStyle = 37; - + // aapt resource value: 38 public const int AppCompatTheme_borderlessButtonStyle = 38; - + // aapt resource value: 39 public const int AppCompatTheme_buttonBarButtonStyle = 39; - + // aapt resource value: 40 public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - + // aapt resource value: 41 public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - + // aapt resource value: 42 public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - + // aapt resource value: 43 public const int AppCompatTheme_buttonBarStyle = 43; - + // aapt resource value: 44 public const int AppCompatTheme_buttonStyle = 44; - + // aapt resource value: 45 public const int AppCompatTheme_buttonStyleSmall = 45; - + // aapt resource value: 46 public const int AppCompatTheme_checkboxStyle = 46; - + // aapt resource value: 47 public const int AppCompatTheme_checkedTextViewStyle = 47; - + // aapt resource value: 48 public const int AppCompatTheme_colorAccent = 48; - + // aapt resource value: 49 public const int AppCompatTheme_colorBackgroundFloating = 49; - + // aapt resource value: 50 public const int AppCompatTheme_colorButtonNormal = 50; - + // aapt resource value: 51 public const int AppCompatTheme_colorControlActivated = 51; - + // aapt resource value: 52 public const int AppCompatTheme_colorControlHighlight = 52; - + // aapt resource value: 53 public const int AppCompatTheme_colorControlNormal = 53; - + // aapt resource value: 54 public const int AppCompatTheme_colorError = 54; - + // aapt resource value: 55 public const int AppCompatTheme_colorPrimary = 55; - + // aapt resource value: 56 public const int AppCompatTheme_colorPrimaryDark = 56; - + // aapt resource value: 57 public const int AppCompatTheme_colorSwitchThumbNormal = 57; - + // aapt resource value: 58 public const int AppCompatTheme_controlBackground = 58; - + // aapt resource value: 59 public const int AppCompatTheme_dialogPreferredPadding = 59; - + // aapt resource value: 60 public const int AppCompatTheme_dialogTheme = 60; - + // aapt resource value: 61 public const int AppCompatTheme_dividerHorizontal = 61; - + // aapt resource value: 62 public const int AppCompatTheme_dividerVertical = 62; - + // aapt resource value: 64 public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - + // aapt resource value: 63 public const int AppCompatTheme_dropDownListViewStyle = 63; - + // aapt resource value: 65 public const int AppCompatTheme_editTextBackground = 65; - + // aapt resource value: 66 public const int AppCompatTheme_editTextColor = 66; - + // aapt resource value: 67 public const int AppCompatTheme_editTextStyle = 67; - + // aapt resource value: 68 public const int AppCompatTheme_homeAsUpIndicator = 68; - + // aapt resource value: 69 public const int AppCompatTheme_imageButtonStyle = 69; - + // aapt resource value: 70 public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - + // aapt resource value: 71 public const int AppCompatTheme_listDividerAlertDialog = 71; - + // aapt resource value: 72 public const int AppCompatTheme_listMenuViewStyle = 72; - + // aapt resource value: 73 public const int AppCompatTheme_listPopupWindowStyle = 73; - + // aapt resource value: 74 public const int AppCompatTheme_listPreferredItemHeight = 74; - + // aapt resource value: 75 public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - + // aapt resource value: 76 public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - + // aapt resource value: 77 public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - + // aapt resource value: 78 public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - + // aapt resource value: 79 public const int AppCompatTheme_panelBackground = 79; - + // aapt resource value: 80 public const int AppCompatTheme_panelMenuListTheme = 80; - + // aapt resource value: 81 public const int AppCompatTheme_panelMenuListWidth = 81; - + // aapt resource value: 82 public const int AppCompatTheme_popupMenuStyle = 82; - + // aapt resource value: 83 public const int AppCompatTheme_popupWindowStyle = 83; - + // aapt resource value: 84 public const int AppCompatTheme_radioButtonStyle = 84; - + // aapt resource value: 85 public const int AppCompatTheme_ratingBarStyle = 85; - + // aapt resource value: 86 public const int AppCompatTheme_ratingBarStyleIndicator = 86; - + // aapt resource value: 87 public const int AppCompatTheme_ratingBarStyleSmall = 87; - + // aapt resource value: 88 public const int AppCompatTheme_searchViewStyle = 88; - + // aapt resource value: 89 public const int AppCompatTheme_seekBarStyle = 89; - + // aapt resource value: 90 public const int AppCompatTheme_selectableItemBackground = 90; - + // aapt resource value: 91 public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - + // aapt resource value: 92 public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - + // aapt resource value: 93 public const int AppCompatTheme_spinnerStyle = 93; - + // aapt resource value: 94 public const int AppCompatTheme_switchStyle = 94; - + // aapt resource value: 95 public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - + // aapt resource value: 96 public const int AppCompatTheme_textAppearanceListItem = 96; - + // aapt resource value: 97 public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - + // aapt resource value: 98 public const int AppCompatTheme_textAppearanceListItemSmall = 98; - + // aapt resource value: 99 public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - + // aapt resource value: 100 public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - + // aapt resource value: 101 public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - + // aapt resource value: 102 public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - + // aapt resource value: 103 public const int AppCompatTheme_textColorAlertDialogListItem = 103; - + // aapt resource value: 104 public const int AppCompatTheme_textColorSearchUrl = 104; - + // aapt resource value: 105 public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - + // aapt resource value: 106 public const int AppCompatTheme_toolbarStyle = 106; - + // aapt resource value: 107 public const int AppCompatTheme_tooltipForegroundColor = 107; - + // aapt resource value: 108 public const int AppCompatTheme_tooltipFrameBackground = 108; - + // aapt resource value: 109 public const int AppCompatTheme_windowActionBar = 109; - + // aapt resource value: 110 public const int AppCompatTheme_windowActionBarOverlay = 110; - + // aapt resource value: 111 public const int AppCompatTheme_windowActionModeOverlay = 111; - + // aapt resource value: 112 public const int AppCompatTheme_windowFixedHeightMajor = 112; - + // aapt resource value: 113 public const int AppCompatTheme_windowFixedHeightMinor = 113; - + // aapt resource value: 114 public const int AppCompatTheme_windowFixedWidthMajor = 114; - + // aapt resource value: 115 public const int AppCompatTheme_windowFixedWidthMinor = 115; - + // aapt resource value: 116 public const int AppCompatTheme_windowMinWidthMajor = 116; - + // aapt resource value: 117 public const int AppCompatTheme_windowMinWidthMinor = 117; - + // aapt resource value: 118 public const int AppCompatTheme_windowNoTitle = 118; - + // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } public static int[] BottomNavigationView = new int[] { 2130903175, @@ -8350,44 +8227,44 @@ public partial class Styleable 2130903225, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int BottomNavigationView_elevation = 0; - + // aapt resource value: 1 public const int BottomNavigationView_itemBackground = 1; - + // aapt resource value: 2 public const int BottomNavigationView_itemIconTint = 2; - + // aapt resource value: 3 public const int BottomNavigationView_itemTextColor = 3; - + // aapt resource value: 4 public const int BottomNavigationView_menu = 4; - + // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } public static int[] BottomSheetBehavior_Layout = new int[] { 2130903096, 2130903098, 2130903099}; - + // aapt resource value: 0 public const int BottomSheetBehavior_Layout_behavior_hideable = 0; - + // aapt resource value: 1 public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; - + // aapt resource value: 2 public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; - + // aapt resource value: { 0x7F030026 } public static int[] ButtonBarLayout = new int[] { 2130903078}; - + // aapt resource value: 0 public const int ButtonBarLayout_allowStacking = 0; - + // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } public static int[] CardView = new int[] { 16843071, @@ -8403,46 +8280,46 @@ public partial class Styleable 2130903150, 2130903151, 2130903152}; - + // aapt resource value: 1 public const int CardView_android_minHeight = 1; - + // aapt resource value: 0 public const int CardView_android_minWidth = 0; - + // aapt resource value: 2 public const int CardView_cardBackgroundColor = 2; - + // aapt resource value: 3 public const int CardView_cardCornerRadius = 3; - + // aapt resource value: 4 public const int CardView_cardElevation = 4; - + // aapt resource value: 5 public const int CardView_cardMaxElevation = 5; - + // aapt resource value: 6 public const int CardView_cardPreventCornerOverlap = 6; - + // aapt resource value: 7 public const int CardView_cardUseCompatPadding = 7; - + // aapt resource value: 8 public const int CardView_contentPadding = 8; - + // aapt resource value: 9 public const int CardView_contentPaddingBottom = 9; - + // aapt resource value: 10 public const int CardView_contentPaddingLeft = 10; - + // aapt resource value: 11 public const int CardView_contentPaddingRight = 11; - + // aapt resource value: 12 public const int CardView_contentPaddingTop = 12; - + // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } public static int[] CollapsingToolbarLayout = new int[] { 2130903127, @@ -8461,104 +8338,104 @@ public partial class Styleable 2130903379, 2130903380, 2130903390}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - + // aapt resource value: 2 public const int CollapsingToolbarLayout_contentScrim = 2; - + // aapt resource value: 3 public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - + // aapt resource value: 4 public const int CollapsingToolbarLayout_expandedTitleMargin = 4; - + // aapt resource value: 5 public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - + // aapt resource value: 6 public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - + // aapt resource value: 7 public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - + // aapt resource value: 8 public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; - + // aapt resource value: 9 public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; - + // aapt resource value: { 0x7F0300C3,0x7F0300C4 } public static int[] CollapsingToolbarLayout_Layout = new int[] { 2130903235, 2130903236}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; - + // aapt resource value: 10 public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; - + // aapt resource value: 11 public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; - + // aapt resource value: 12 public const int CollapsingToolbarLayout_statusBarScrim = 12; - + // aapt resource value: 13 public const int CollapsingToolbarLayout_title = 13; - + // aapt resource value: 14 public const int CollapsingToolbarLayout_titleEnabled = 14; - + // aapt resource value: 15 public const int CollapsingToolbarLayout_toolbarId = 15; - + // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } public static int[] ColorStateListItem = new int[] { 16843173, 16843551, 2130903079}; - + // aapt resource value: 2 public const int ColorStateListItem_alpha = 2; - + // aapt resource value: 1 public const int ColorStateListItem_android_alpha = 1; - + // aapt resource value: 0 public const int ColorStateListItem_android_color = 0; - + // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } public static int[] CompoundButton = new int[] { 16843015, 2130903113, 2130903114}; - + // aapt resource value: 0 public const int CompoundButton_android_button = 0; - + // aapt resource value: 1 public const int CompoundButton_buttonTint = 1; - + // aapt resource value: 2 public const int CompoundButton_buttonTintMode = 2; - + // aapt resource value: { 0x7F0300BD,0x7F030120 } public static int[] CoordinatorLayout = new int[] { 2130903229, 2130903328}; - + // aapt resource value: 0 public const int CoordinatorLayout_keylines = 0; - + // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } public static int[] CoordinatorLayout_Layout = new int[] { 16842931, @@ -8568,46 +8445,46 @@ public partial class Styleable 2130903237, 2130903238, 2130903239}; - + // aapt resource value: 0 public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - + // aapt resource value: 1 public const int CoordinatorLayout_Layout_layout_anchor = 1; - + // aapt resource value: 2 public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - + // aapt resource value: 3 public const int CoordinatorLayout_Layout_layout_behavior = 3; - + // aapt resource value: 4 public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; - + // aapt resource value: 5 public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - + // aapt resource value: 6 public const int CoordinatorLayout_Layout_layout_keyline = 6; - + // aapt resource value: 1 public const int CoordinatorLayout_statusBarBackground = 1; - + // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } public static int[] DesignTheme = new int[] { 2130903102, 2130903103, 2130903367}; - + // aapt resource value: 0 public const int DesignTheme_bottomSheetDialogTheme = 0; - + // aapt resource value: 1 public const int DesignTheme_bottomSheetStyle = 1; - + // aapt resource value: 2 public const int DesignTheme_textColorError = 2; - + // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } public static int[] DrawerArrowToggle = new int[] { 2130903081, @@ -8618,31 +8495,31 @@ public partial class Styleable 2130903205, 2130903319, 2130903370}; - + // aapt resource value: 0 public const int DrawerArrowToggle_arrowHeadLength = 0; - + // aapt resource value: 1 public const int DrawerArrowToggle_arrowShaftLength = 1; - + // aapt resource value: 2 public const int DrawerArrowToggle_barLength = 2; - + // aapt resource value: 3 public const int DrawerArrowToggle_color = 3; - + // aapt resource value: 4 public const int DrawerArrowToggle_drawableSize = 4; - + // aapt resource value: 5 public const int DrawerArrowToggle_gapBetweenBars = 5; - + // aapt resource value: 6 public const int DrawerArrowToggle_spinBars = 6; - + // aapt resource value: 7 public const int DrawerArrowToggle_thickness = 7; - + // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } public static int[] FloatingActionButton = new int[] { 2130903092, @@ -8653,38 +8530,38 @@ public partial class Styleable 2130903294, 2130903304, 2130903399}; - + // aapt resource value: 0 public const int FloatingActionButton_backgroundTint = 0; - + // aapt resource value: 1 public const int FloatingActionButton_backgroundTintMode = 1; - + // aapt resource value: { 0x7F030037 } public static int[] FloatingActionButton_Behavior_Layout = new int[] { 2130903095}; - + // aapt resource value: 0 public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - + // aapt resource value: 2 public const int FloatingActionButton_borderWidth = 2; - + // aapt resource value: 3 public const int FloatingActionButton_elevation = 3; - + // aapt resource value: 4 public const int FloatingActionButton_fabSize = 4; - + // aapt resource value: 5 public const int FloatingActionButton_pressedTranslationZ = 5; - + // aapt resource value: 6 public const int FloatingActionButton_rippleColor = 6; - + // aapt resource value: 7 public const int FloatingActionButton_useCompatPadding = 7; - + // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } public static int[] FontFamily = new int[] { 2130903196, @@ -8693,7 +8570,7 @@ public partial class Styleable 2130903199, 2130903200, 2130903201}; - + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } public static int[] FontFamilyFont = new int[] { 16844082, @@ -8702,58 +8579,58 @@ public partial class Styleable 2130903194, 2130903202, 2130903203}; - + // aapt resource value: 0 public const int FontFamilyFont_android_font = 0; - + // aapt resource value: 2 public const int FontFamilyFont_android_fontStyle = 2; - + // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; - + // aapt resource value: 3 public const int FontFamilyFont_font = 3; - + // aapt resource value: 4 public const int FontFamilyFont_fontStyle = 4; - + // aapt resource value: 5 public const int FontFamilyFont_fontWeight = 5; - + // aapt resource value: 0 public const int FontFamily_fontProviderAuthority = 0; - + // aapt resource value: 1 public const int FontFamily_fontProviderCerts = 1; - + // aapt resource value: 2 public const int FontFamily_fontProviderFetchStrategy = 2; - + // aapt resource value: 3 public const int FontFamily_fontProviderFetchTimeout = 3; - + // aapt resource value: 4 public const int FontFamily_fontProviderPackage = 4; - + // aapt resource value: 5 public const int FontFamily_fontProviderQuery = 5; - + // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } public static int[] ForegroundLinearLayout = new int[] { 16843017, 16843264, 2130903204}; - + // aapt resource value: 0 public const int ForegroundLinearLayout_android_foreground = 0; - + // aapt resource value: 1 public const int ForegroundLinearLayout_android_foregroundGravity = 1; - + // aapt resource value: 2 public const int ForegroundLinearLayout_foregroundInsidePadding = 2; - + // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } public static int[] LinearLayoutCompat = new int[] { 16842927, @@ -8765,83 +8642,83 @@ public partial class Styleable 2130903166, 2130903257, 2130903314}; - + // aapt resource value: 2 public const int LinearLayoutCompat_android_baselineAligned = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; - + // aapt resource value: 0 public const int LinearLayoutCompat_android_gravity = 0; - + // aapt resource value: 1 public const int LinearLayoutCompat_android_orientation = 1; - + // aapt resource value: 4 public const int LinearLayoutCompat_android_weightSum = 4; - + // aapt resource value: 5 public const int LinearLayoutCompat_divider = 5; - + // aapt resource value: 6 public const int LinearLayoutCompat_dividerPadding = 6; - + // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } public static int[] LinearLayoutCompat_Layout = new int[] { 16842931, 16842996, 16842997, 16843137}; - + // aapt resource value: 0 public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; - + // aapt resource value: 2 public const int LinearLayoutCompat_Layout_android_layout_height = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_Layout_android_layout_weight = 3; - + // aapt resource value: 1 public const int LinearLayoutCompat_Layout_android_layout_width = 1; - + // aapt resource value: 7 public const int LinearLayoutCompat_measureWithLargestChild = 7; - + // aapt resource value: 8 public const int LinearLayoutCompat_showDividers = 8; - + // aapt resource value: { 0x10102AC,0x10102AD } public static int[] ListPopupWindow = new int[] { 16843436, 16843437}; - + // aapt resource value: 0 public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; - + // aapt resource value: 1 public const int ListPopupWindow_android_dropDownVerticalOffset = 1; - + // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } public static int[] MediaRouteButton = new int[] { 16843071, 16843072, 2130903187, 2130903260}; - + // aapt resource value: 1 public const int MediaRouteButton_android_minHeight = 1; - + // aapt resource value: 0 public const int MediaRouteButton_android_minWidth = 0; - + // aapt resource value: 2 public const int MediaRouteButton_externalRouteEnabledDrawable = 2; - + // aapt resource value: 3 public const int MediaRouteButton_mediaRouteButtonTint = 3; - + // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } public static int[] MenuGroup = new int[] { 16842766, @@ -8850,25 +8727,25 @@ public partial class Styleable 16843230, 16843231, 16843232}; - + // aapt resource value: 5 public const int MenuGroup_android_checkableBehavior = 5; - + // aapt resource value: 0 public const int MenuGroup_android_enabled = 0; - + // aapt resource value: 1 public const int MenuGroup_android_id = 1; - + // aapt resource value: 3 public const int MenuGroup_android_menuCategory = 3; - + // aapt resource value: 4 public const int MenuGroup_android_orderInCategory = 4; - + // aapt resource value: 2 public const int MenuGroup_android_visible = 2; - + // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } public static int[] MenuItem = new int[] { 16842754, @@ -8894,76 +8771,76 @@ public partial class Styleable 2130903276, 2130903313, 2130903395}; - + // aapt resource value: 13 public const int MenuItem_actionLayout = 13; - + // aapt resource value: 14 public const int MenuItem_actionProviderClass = 14; - + // aapt resource value: 15 public const int MenuItem_actionViewClass = 15; - + // aapt resource value: 16 public const int MenuItem_alphabeticModifiers = 16; - + // aapt resource value: 9 public const int MenuItem_android_alphabeticShortcut = 9; - + // aapt resource value: 11 public const int MenuItem_android_checkable = 11; - + // aapt resource value: 3 public const int MenuItem_android_checked = 3; - + // aapt resource value: 1 public const int MenuItem_android_enabled = 1; - + // aapt resource value: 0 public const int MenuItem_android_icon = 0; - + // aapt resource value: 2 public const int MenuItem_android_id = 2; - + // aapt resource value: 5 public const int MenuItem_android_menuCategory = 5; - + // aapt resource value: 10 public const int MenuItem_android_numericShortcut = 10; - + // aapt resource value: 12 public const int MenuItem_android_onClick = 12; - + // aapt resource value: 6 public const int MenuItem_android_orderInCategory = 6; - + // aapt resource value: 7 public const int MenuItem_android_title = 7; - + // aapt resource value: 8 public const int MenuItem_android_titleCondensed = 8; - + // aapt resource value: 4 public const int MenuItem_android_visible = 4; - + // aapt resource value: 17 public const int MenuItem_contentDescription = 17; - + // aapt resource value: 18 public const int MenuItem_iconTint = 18; - + // aapt resource value: 19 public const int MenuItem_iconTintMode = 19; - + // aapt resource value: 20 public const int MenuItem_numericModifiers = 20; - + // aapt resource value: 21 public const int MenuItem_showAsAction = 21; - + // aapt resource value: 22 public const int MenuItem_tooltipText = 22; - + // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } public static int[] MenuView = new int[] { 16842926, @@ -8975,34 +8852,34 @@ public partial class Styleable 16843057, 2130903293, 2130903330}; - + // aapt resource value: 4 public const int MenuView_android_headerBackground = 4; - + // aapt resource value: 2 public const int MenuView_android_horizontalDivider = 2; - + // aapt resource value: 5 public const int MenuView_android_itemBackground = 5; - + // aapt resource value: 6 public const int MenuView_android_itemIconDisabledAlpha = 6; - + // aapt resource value: 1 public const int MenuView_android_itemTextAppearance = 1; - + // aapt resource value: 3 public const int MenuView_android_verticalDivider = 3; - + // aapt resource value: 0 public const int MenuView_android_windowAnimationStyle = 0; - + // aapt resource value: 7 public const int MenuView_preserveIconSpacing = 7; - + // aapt resource value: 8 public const int MenuView_subMenuArrow = 8; - + // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } public static int[] NavigationView = new int[] { 16842964, @@ -9015,70 +8892,70 @@ public partial class Styleable 2130903227, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int NavigationView_android_background = 0; - + // aapt resource value: 1 public const int NavigationView_android_fitsSystemWindows = 1; - + // aapt resource value: 2 public const int NavigationView_android_maxWidth = 2; - + // aapt resource value: 3 public const int NavigationView_elevation = 3; - + // aapt resource value: 4 public const int NavigationView_headerLayout = 4; - + // aapt resource value: 5 public const int NavigationView_itemBackground = 5; - + // aapt resource value: 6 public const int NavigationView_itemIconTint = 6; - + // aapt resource value: 7 public const int NavigationView_itemTextAppearance = 7; - + // aapt resource value: 8 public const int NavigationView_itemTextColor = 8; - + // aapt resource value: 9 public const int NavigationView_menu = 9; - + // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } public static int[] PopupWindow = new int[] { 16843126, 16843465, 2130903277}; - + // aapt resource value: { 0x7F03011D } public static int[] PopupWindowBackgroundState = new int[] { 2130903325}; - + // aapt resource value: 0 public const int PopupWindowBackgroundState_state_above_anchor = 0; - + // aapt resource value: 1 public const int PopupWindow_android_popupAnimationStyle = 1; - + // aapt resource value: 0 public const int PopupWindow_android_popupBackground = 0; - + // aapt resource value: 2 public const int PopupWindow_overlapAnchor = 2; - + // aapt resource value: { 0x7F0300EE,0x7F0300F1 } public static int[] RecycleListView = new int[] { 2130903278, 2130903281}; - + // aapt resource value: 0 public const int RecycleListView_paddingBottomNoButtons = 0; - + // aapt resource value: 1 public const int RecycleListView_paddingTopNoTitle = 1; - + // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } public static int[] RecyclerView = new int[] { 16842948, @@ -9092,54 +8969,54 @@ public partial class Styleable 2130903303, 2130903318, 2130903324}; - + // aapt resource value: 1 public const int RecyclerView_android_descendantFocusability = 1; - + // aapt resource value: 0 public const int RecyclerView_android_orientation = 0; - + // aapt resource value: 2 public const int RecyclerView_fastScrollEnabled = 2; - + // aapt resource value: 3 public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - + // aapt resource value: 4 public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - + // aapt resource value: 5 public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - + // aapt resource value: 6 public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - + // aapt resource value: 7 public const int RecyclerView_layoutManager = 7; - + // aapt resource value: 8 public const int RecyclerView_reverseLayout = 8; - + // aapt resource value: 9 public const int RecyclerView_spanCount = 9; - + // aapt resource value: 10 public const int RecyclerView_stackFromEnd = 10; - + // aapt resource value: { 0x7F0300B6 } public static int[] ScrimInsetsFrameLayout = new int[] { 2130903222}; - + // aapt resource value: 0 public const int ScrimInsetsFrameLayout_insetForeground = 0; - + // aapt resource value: { 0x7F030039 } public static int[] ScrollingViewBehavior_Layout = new int[] { 2130903097}; - + // aapt resource value: 0 public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; - + // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } public static int[] SearchView = new int[] { 16842970, @@ -9159,73 +9036,73 @@ public partial class Styleable 2130903331, 2130903336, 2130903400}; - + // aapt resource value: 0 public const int SearchView_android_focusable = 0; - + // aapt resource value: 3 public const int SearchView_android_imeOptions = 3; - + // aapt resource value: 2 public const int SearchView_android_inputType = 2; - + // aapt resource value: 1 public const int SearchView_android_maxWidth = 1; - + // aapt resource value: 4 public const int SearchView_closeIcon = 4; - + // aapt resource value: 5 public const int SearchView_commitIcon = 5; - + // aapt resource value: 6 public const int SearchView_defaultQueryHint = 6; - + // aapt resource value: 7 public const int SearchView_goIcon = 7; - + // aapt resource value: 8 public const int SearchView_iconifiedByDefault = 8; - + // aapt resource value: 9 public const int SearchView_layout = 9; - + // aapt resource value: 10 public const int SearchView_queryBackground = 10; - + // aapt resource value: 11 public const int SearchView_queryHint = 11; - + // aapt resource value: 12 public const int SearchView_searchHintIcon = 12; - + // aapt resource value: 13 public const int SearchView_searchIcon = 13; - + // aapt resource value: 14 public const int SearchView_submitBackground = 14; - + // aapt resource value: 15 public const int SearchView_suggestionRowLayout = 15; - + // aapt resource value: 16 public const int SearchView_voiceIcon = 16; - + // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } public static int[] SnackbarLayout = new int[] { 16843039, 2130903175, 2130903255}; - + // aapt resource value: 0 public const int SnackbarLayout_android_maxWidth = 0; - + // aapt resource value: 1 public const int SnackbarLayout_elevation = 1; - + // aapt resource value: 2 public const int SnackbarLayout_maxActionInlineWidth = 2; - + // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } public static int[] Spinner = new int[] { 16842930, @@ -9233,22 +9110,22 @@ public partial class Styleable 16843131, 16843362, 2130903291}; - + // aapt resource value: 3 public const int Spinner_android_dropDownWidth = 3; - + // aapt resource value: 0 public const int Spinner_android_entries = 0; - + // aapt resource value: 1 public const int Spinner_android_popupBackground = 1; - + // aapt resource value: 2 public const int Spinner_android_prompt = 2; - + // aapt resource value: 4 public const int Spinner_popupTheme = 4; - + // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } public static int[] SwitchCompat = new int[] { 16843044, @@ -9265,64 +9142,64 @@ public partial class Styleable 2130903396, 2130903397, 2130903398}; - + // aapt resource value: 1 public const int SwitchCompat_android_textOff = 1; - + // aapt resource value: 0 public const int SwitchCompat_android_textOn = 0; - + // aapt resource value: 2 public const int SwitchCompat_android_thumb = 2; - + // aapt resource value: 3 public const int SwitchCompat_showText = 3; - + // aapt resource value: 4 public const int SwitchCompat_splitTrack = 4; - + // aapt resource value: 5 public const int SwitchCompat_switchMinWidth = 5; - + // aapt resource value: 6 public const int SwitchCompat_switchPadding = 6; - + // aapt resource value: 7 public const int SwitchCompat_switchTextAppearance = 7; - + // aapt resource value: 8 public const int SwitchCompat_thumbTextPadding = 8; - + // aapt resource value: 9 public const int SwitchCompat_thumbTint = 9; - + // aapt resource value: 10 public const int SwitchCompat_thumbTintMode = 10; - + // aapt resource value: 11 public const int SwitchCompat_track = 11; - + // aapt resource value: 12 public const int SwitchCompat_trackTint = 12; - + // aapt resource value: 13 public const int SwitchCompat_trackTintMode = 13; - + // aapt resource value: { 0x1010002,0x10100F2,0x101014F } public static int[] TabItem = new int[] { 16842754, 16842994, 16843087}; - + // aapt resource value: 0 public const int TabItem_android_icon = 0; - + // aapt resource value: 1 public const int TabItem_android_layout = 1; - + // aapt resource value: 2 public const int TabItem_android_text = 2; - + // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } public static int[] TabLayout = new int[] { 2130903341, @@ -9341,55 +9218,55 @@ public partial class Styleable 2130903354, 2130903355, 2130903356}; - + // aapt resource value: 0 public const int TabLayout_tabBackground = 0; - + // aapt resource value: 1 public const int TabLayout_tabContentStart = 1; - + // aapt resource value: 2 public const int TabLayout_tabGravity = 2; - + // aapt resource value: 3 public const int TabLayout_tabIndicatorColor = 3; - + // aapt resource value: 4 public const int TabLayout_tabIndicatorHeight = 4; - + // aapt resource value: 5 public const int TabLayout_tabMaxWidth = 5; - + // aapt resource value: 6 public const int TabLayout_tabMinWidth = 6; - + // aapt resource value: 7 public const int TabLayout_tabMode = 7; - + // aapt resource value: 8 public const int TabLayout_tabPadding = 8; - + // aapt resource value: 9 public const int TabLayout_tabPaddingBottom = 9; - + // aapt resource value: 10 public const int TabLayout_tabPaddingEnd = 10; - + // aapt resource value: 11 public const int TabLayout_tabPaddingStart = 11; - + // aapt resource value: 12 public const int TabLayout_tabPaddingTop = 12; - + // aapt resource value: 13 public const int TabLayout_tabSelectedTextColor = 13; - + // aapt resource value: 14 public const int TabLayout_tabTextAppearance = 14; - + // aapt resource value: 15 public const int TabLayout_tabTextColor = 15; - + // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } public static int[] TextAppearance = new int[] { 16842901, @@ -9405,46 +9282,46 @@ public partial class Styleable 16843692, 2130903195, 2130903357}; - + // aapt resource value: 10 public const int TextAppearance_android_fontFamily = 10; - + // aapt resource value: 6 public const int TextAppearance_android_shadowColor = 6; - + // aapt resource value: 7 public const int TextAppearance_android_shadowDx = 7; - + // aapt resource value: 8 public const int TextAppearance_android_shadowDy = 8; - + // aapt resource value: 9 public const int TextAppearance_android_shadowRadius = 9; - + // aapt resource value: 3 public const int TextAppearance_android_textColor = 3; - + // aapt resource value: 4 public const int TextAppearance_android_textColorHint = 4; - + // aapt resource value: 5 public const int TextAppearance_android_textColorLink = 5; - + // aapt resource value: 0 public const int TextAppearance_android_textSize = 0; - + // aapt resource value: 2 public const int TextAppearance_android_textStyle = 2; - + // aapt resource value: 1 public const int TextAppearance_android_typeface = 1; - + // aapt resource value: 11 public const int TextAppearance_fontFamily = 11; - + // aapt resource value: 12 public const int TextAppearance_textAllCaps = 12; - + // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } public static int[] TextInputLayout = new int[] { 16842906, @@ -9463,55 +9340,55 @@ public partial class Styleable 2130903287, 2130903288, 2130903289}; - + // aapt resource value: 1 public const int TextInputLayout_android_hint = 1; - + // aapt resource value: 0 public const int TextInputLayout_android_textColorHint = 0; - + // aapt resource value: 2 public const int TextInputLayout_counterEnabled = 2; - + // aapt resource value: 3 public const int TextInputLayout_counterMaxLength = 3; - + // aapt resource value: 4 public const int TextInputLayout_counterOverflowTextAppearance = 4; - + // aapt resource value: 5 public const int TextInputLayout_counterTextAppearance = 5; - + // aapt resource value: 6 public const int TextInputLayout_errorEnabled = 6; - + // aapt resource value: 7 public const int TextInputLayout_errorTextAppearance = 7; - + // aapt resource value: 8 public const int TextInputLayout_hintAnimationEnabled = 8; - + // aapt resource value: 9 public const int TextInputLayout_hintEnabled = 9; - + // aapt resource value: 10 public const int TextInputLayout_hintTextAppearance = 10; - + // aapt resource value: 11 public const int TextInputLayout_passwordToggleContentDescription = 11; - + // aapt resource value: 12 public const int TextInputLayout_passwordToggleDrawable = 12; - + // aapt resource value: 13 public const int TextInputLayout_passwordToggleEnabled = 13; - + // aapt resource value: 14 public const int TextInputLayout_passwordToggleTint = 14; - + // aapt resource value: 15 public const int TextInputLayout_passwordToggleTintMode = 15; - + // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } public static int[] Toolbar = new int[] { 16842927, @@ -9543,94 +9420,94 @@ public partial class Styleable 2130903386, 2130903387, 2130903388}; - + // aapt resource value: 0 public const int Toolbar_android_gravity = 0; - + // aapt resource value: 1 public const int Toolbar_android_minHeight = 1; - + // aapt resource value: 2 public const int Toolbar_buttonGravity = 2; - + // aapt resource value: 3 public const int Toolbar_collapseContentDescription = 3; - + // aapt resource value: 4 public const int Toolbar_collapseIcon = 4; - + // aapt resource value: 5 public const int Toolbar_contentInsetEnd = 5; - + // aapt resource value: 6 public const int Toolbar_contentInsetEndWithActions = 6; - + // aapt resource value: 7 public const int Toolbar_contentInsetLeft = 7; - + // aapt resource value: 8 public const int Toolbar_contentInsetRight = 8; - + // aapt resource value: 9 public const int Toolbar_contentInsetStart = 9; - + // aapt resource value: 10 public const int Toolbar_contentInsetStartWithNavigation = 10; - + // aapt resource value: 11 public const int Toolbar_logo = 11; - + // aapt resource value: 12 public const int Toolbar_logoDescription = 12; - + // aapt resource value: 13 public const int Toolbar_maxButtonHeight = 13; - + // aapt resource value: 14 public const int Toolbar_navigationContentDescription = 14; - + // aapt resource value: 15 public const int Toolbar_navigationIcon = 15; - + // aapt resource value: 16 public const int Toolbar_popupTheme = 16; - + // aapt resource value: 17 public const int Toolbar_subtitle = 17; - + // aapt resource value: 18 public const int Toolbar_subtitleTextAppearance = 18; - + // aapt resource value: 19 public const int Toolbar_subtitleTextColor = 19; - + // aapt resource value: 20 public const int Toolbar_title = 20; - + // aapt resource value: 21 public const int Toolbar_titleMargin = 21; - + // aapt resource value: 22 public const int Toolbar_titleMarginBottom = 22; - + // aapt resource value: 23 public const int Toolbar_titleMarginEnd = 23; - + // aapt resource value: 26 public const int Toolbar_titleMargins = 26; - + // aapt resource value: 24 public const int Toolbar_titleMarginStart = 24; - + // aapt resource value: 25 public const int Toolbar_titleMarginTop = 25; - + // aapt resource value: 27 public const int Toolbar_titleTextAppearance = 27; - + // aapt resource value: 28 public const int Toolbar_titleTextColor = 28; - + // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } public static int[] View = new int[] { 16842752, @@ -9638,57 +9515,57 @@ public partial class Styleable 2130903279, 2130903280, 2130903369}; - + // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } public static int[] ViewBackgroundHelper = new int[] { 16842964, 2130903092, 2130903093}; - + // aapt resource value: 0 public const int ViewBackgroundHelper_android_background = 0; - + // aapt resource value: 1 public const int ViewBackgroundHelper_backgroundTint = 1; - + // aapt resource value: 2 public const int ViewBackgroundHelper_backgroundTintMode = 2; - + // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } public static int[] ViewStubCompat = new int[] { 16842960, 16842994, 16842995}; - + // aapt resource value: 0 public const int ViewStubCompat_android_id = 0; - + // aapt resource value: 2 public const int ViewStubCompat_android_inflatedId = 2; - + // aapt resource value: 1 public const int ViewStubCompat_android_layout = 1; - + // aapt resource value: 1 public const int View_android_focusable = 1; - + // aapt resource value: 0 public const int View_android_theme = 0; - + // aapt resource value: 2 public const int View_paddingEnd = 2; - + // aapt resource value: 3 public const int View_paddingStart = 3; - + // aapt resource value: 4 public const int View_theme = 4; - + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Styleable() { } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 00e61223..87153ee8 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -410,6 +410,51 @@ public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() } } + [Fact] + public async void IdentifyWithAutoEnvAttributesEnabledAddsContexts() + { + var stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); + var config = BasicConfig() + .AutoEnvironmentAttributes(ConfigurationBuilder.AutoEnvAttributes.Enabled) + .DataSource(dataSourceConfig) + .GenerateAnonymousKeys(true) + .Build(); + + using (var client = await LdClient.InitAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + } + } + + [Fact] + public async void IdentifyWithAutoEnvAttributesDisabledNoAddedContexts() + { + var stub = new MockPollingProcessor(DataSetBuilder.Empty); + var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); + var config = BasicConfig() + .AutoEnvironmentAttributes(ConfigurationBuilder.AutoEnvAttributes.Disabled) + .DataSource(dataSourceConfig) + .GenerateAnonymousKeys(true) + .Build(); + + using (var client = await LdClient.InitAsync(config, BasicUser)) + { + await client.IdentifyAsync(AnonUser); + + var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; + Assert.NotEqual(AnonUser, receivedContext); + Assert.Equal(client.Context, receivedContext); + AssertHelpers.ContextsEqual( + Context.BuilderFromContext(AnonUser).Key(receivedContext.Key).Build(), + receivedContext); + } + } + [Fact] public void SharedClientIsTheOnlyClientAvailable() { From ae46f6c502adf4b9458f210f3ecc370b82355d80 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 08:47:41 -0700 Subject: [PATCH 471/499] remove XamarinEssentialsUtils.cs --- .../Internal/XamarinEsssentialsUtils.cs | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs diff --git a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs b/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs deleted file mode 100644 index f858a44a..00000000 --- a/src/LaunchDarkly.ClientSdk/Internal/XamarinEsssentialsUtils.cs +++ /dev/null @@ -1,69 +0,0 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LaunchDarkly.Sdk.Client.Internal -{ - internal static class Utils - { - internal static Version ParseVersion(string version) - { - if (Version.TryParse(version, out var number)) - return number; - - if (int.TryParse(version, out var major)) - return new Version(major, 0); - - return new Version(0, 0); - } - - internal static CancellationToken TimeoutToken(CancellationToken cancellationToken, TimeSpan timeout) - { - // create a new linked cancellation token source - var cancelTokenSrc = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - // if a timeout was given, make the token source cancel after it expires - if (timeout > TimeSpan.Zero) - cancelTokenSrc.CancelAfter(timeout); - - // our Cancel method will handle the actual cancellation logic - return cancelTokenSrc.Token; - } - - internal static async Task WithTimeout(Task task, TimeSpan timeSpan) - { - var retTask = await Task.WhenAny(task, Task.Delay(timeSpan)) - .ConfigureAwait(false); - - return retTask is Task ? task.Result : default(T); - } - } -} From b0c793fd832874a065f7d9e02b94647af25a63f7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 08:55:52 -0700 Subject: [PATCH 472/499] remove irrelevant code --- .../PlatformSpecific/AppInfo.android.cs | 28 - .../PlatformSpecific/AppInfo.ios.cs | 155 +- .../PlatformSpecific/AppInfo.netstandard.cs | 2 - .../PlatformSpecific/AppInfo.shared.cs | 4 - .../PlatformSpecific/DeviceInfo.android.cs | 153 +- .../PlatformSpecific/DeviceInfo.ios.cs | 16 +- .../PlatformSpecific/DeviceInfo.shared.cs | 3 - .../Resources/Resource.designer.cs | 4799 +++++++++-------- 8 files changed, 2472 insertions(+), 2688 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs index d9f51f13..a4f71f11 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs @@ -57,8 +57,6 @@ internal static partial class AppInfo // End LaunchDarkly additions. - static string PlatformGetPackageName() => Platform.AppContext.PackageName; - static string PlatformGetName() { var applicationInfo = Platform.AppContext.ApplicationInfo; @@ -95,31 +93,5 @@ static string PlatformGetBuild() #endif } } - - // static void PlatformShowSettingsUI() - // { - // var context = Platform.GetCurrentActivity(false) ?? Platform.AppContext; - // - // var settingsIntent = new Intent(); - // settingsIntent.SetAction(global::Android.Provider.Settings.ActionApplicationDetailsSettings); - // settingsIntent.AddCategory(Intent.CategoryDefault); - // settingsIntent.SetData(global::Android.Net.Uri.Parse("package:" + PlatformGetPackageName())); - // - // var flags = ActivityFlags.NewTask | ActivityFlags.NoHistory | ActivityFlags.ExcludeFromRecents; - // - // settingsIntent.SetFlags(flags); - // - // context.StartActivity(settingsIntent); - // } - - // static AppTheme PlatformRequestedTheme() - // { - // return (Platform.AppContext.Resources.Configuration.UiMode & UiMode.NightMask) switch - // { - // UiMode.NightYes => AppTheme.Dark, - // UiMode.NightNo => AppTheme.Light, - // _ => AppTheme.Unspecified - // }; - // } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs index 44bc877e..1557e6dd 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs @@ -1,36 +1,4 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -using System; -using System.Collections.Generic; -using System.Linq; using Foundation; -using LaunchDarkly.Sdk.EnvReporting; #if __IOS__ || __TVOS__ using UIKit; @@ -43,127 +11,12 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific internal static partial class AppInfo { static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( - PlatformGetAppId(), - PlatformGetAppName(), - PlatformGetAppVersion(), - PlatformGetAppVersionName()); - - // The following methods are added by LaunchDarkly to align with the Application Info - // required by the SDK. - static string PlatformGetAppId() => GetBundleValue("CFBundleIdentifier"); - static string PlatformGetAppName() => GetBundleValue("CFBundleName"); - static string PlatformGetAppVersion() => GetBundleValue("CFBundleVersion"); - static string PlatformGetAppVersionName() => GetBundleValue("CFBundleShortString"); - - // End LaunchDarkly additions. + GetBundleValue("CFBundleIdentifier"), + GetBundleValue("CFBundleName"), + GetBundleValue("CFBundleVersion"), + GetBundleValue("CFBundleShortString")); static string GetBundleValue(string key) => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); - -// #if __IOS__ || __TVOS__ -// static async void PlatformShowSettingsUI() -// => await Launcher.OpenAsync(UIApplication.OpenSettingsUrlString); -// #elif __MACOS__ -// static void PlatformShowSettingsUI() -// { -// MainThread.BeginInvokeOnMainThread(() => -// { -// // Ignore obsolete for the time being. Will be updated in a future release. -// #pragma warning disable CS0618 // Type or member is obsolete -// var prefsApp = ScriptingBridge.SBApplication.FromBundleIdentifier("com.apple.systempreferences"); -// #pragma warning restore CS0618 // Type or member is obsolete -// prefsApp.SendMode = ScriptingBridge.AESendMode.NoReply; -// prefsApp.Activate(); -// }); -// } -// #else -// static void PlatformShowSettingsUI() => -// throw new FeatureNotSupportedException(); -// #endif - -// #if __IOS__ || __TVOS__ -// static AppTheme PlatformRequestedTheme() -// { -// if (!Platform.HasOSVersion(13, 0)) -// return AppTheme.Unspecified; -// -// var uiStyle = Platform.GetCurrentUIViewController()?.TraitCollection?.UserInterfaceStyle ?? -// UITraitCollection.CurrentTraitCollection.UserInterfaceStyle; -// -// return uiStyle switch -// { -// UIUserInterfaceStyle.Light => AppTheme.Light, -// UIUserInterfaceStyle.Dark => AppTheme.Dark, -// _ => AppTheme.Unspecified -// }; -// } -// #elif __MACOS__ -// static AppTheme PlatformRequestedTheme() -// { -// if (DeviceInfo.Version >= new Version(10, 14)) -// { -// var app = NSAppearance.CurrentAppearance?.FindBestMatch(new string[] -// { -// NSAppearance.NameAqua, -// NSAppearance.NameDarkAqua -// }); -// -// if (string.IsNullOrEmpty(app)) -// return AppTheme.Unspecified; -// -// if (app == NSAppearance.NameDarkAqua) -// return AppTheme.Dark; -// } -// return AppTheme.Light; -// } -// #else -// static AppTheme PlatformRequestedTheme() => -// AppTheme.Unspecified; -// #endif - - internal static bool VerifyHasUrlScheme(string scheme) - { - var cleansed = scheme.Replace("://", string.Empty); - var schemes = GetCFBundleURLSchemes().ToList(); - return schemes.Any(x => x != null && x.Equals(cleansed, StringComparison.InvariantCultureIgnoreCase)); - } - - internal static IEnumerable GetCFBundleURLSchemes() - { - var schemes = new List(); - - NSObject nsobj = null; - if (!NSBundle.MainBundle.InfoDictionary.TryGetValue((NSString)"CFBundleURLTypes", out nsobj)) - return schemes; - - var array = nsobj as NSArray; - - if (array == null) - return schemes; - - for (nuint i = 0; i < array.Count; i++) - { - var d = array.GetItem(i); - if (d == null || !d.Any()) - continue; - - if (!d.TryGetValue((NSString)"CFBundleURLSchemes", out nsobj)) - continue; - - var a = nsobj as NSArray; - var urls = ConvertToIEnumerable(a).Select(x => x.ToString()).ToArray(); - foreach (var url in urls) - schemes.Add(url); - } - - return schemes; - } - - static IEnumerable ConvertToIEnumerable(NSArray array) - where T : class, ObjCRuntime.INativeObject - { - for (nuint i = 0; i < array.Count; i++) - yield return array.GetItem(i); - } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index 6d1ecdcc..c7e072b7 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -1,5 +1,3 @@ -using LaunchDarkly.Sdk.EnvReporting; - namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs index 19689b96..1573db1b 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs @@ -1,7 +1,3 @@ -using System; -using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.EnvReporting; - namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs index 24df9aa8..3212c1a4 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs @@ -1,33 +1,4 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - using Android.OS; -using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.EnvReporting.LayerModels; namespace LaunchDarkly.Sdk.Client.PlatformSpecific @@ -36,129 +7,15 @@ internal static partial class DeviceInfo { private static OsInfo? PlatformGetOsInfo() => new OsInfo( - GetPlatform().ToString(), - GetPlatform().ToString()+Build.VERSION.SdkInt, - GetVersionString() + DevicePlatform.Android.ToString(), + DevicePlatform.Android.ToString()+Build.VERSION.SdkInt, + Build.VERSION.Release ); private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => new EnvReporting.LayerModels.DeviceInfo( - GetManufacturer(), - GetModel() + Build.Manufacturer, + Build.Model ); - - - //const int tabletCrossover = 600; - - static string GetModel() => Build.Model; - - static string GetManufacturer() => Build.Manufacturer; - - // private static string GetDeviceName() - // { - // // DEVICE_NAME added in System.Global in API level 25 - // // https://developer.android.com/reference/android/provider/Settings.Global#DEVICE_NAME - // var name = GetSystemSetting("device_name", true); - // if (string.IsNullOrWhiteSpace(name)) - // name = ColorSpace.Model; - // return name; - // } - - private static string GetVersionString() => Build.VERSION.Release; - - private static DevicePlatform GetPlatform() => DevicePlatform.Android; - - // static DeviceIdiom GetIdiom() - // { - // var currentIdiom = DeviceIdiom.Unknown; - // - // // first try UIModeManager - // using var uiModeManager = UiModeManager.FromContext(Essentials.Platform.AppContext); - // - // try - // { - // var uiMode = uiModeManager?.CurrentModeType ?? UiMode.TypeUndefined; - // currentIdiom = DetectIdiom(uiMode); - // } - // catch (Exception ex) - // { - // System.Diagnostics.Debug.WriteLine($"Unable to detect using UiModeManager: {ex.Message}"); - // } - // - // // then try Configuration - // if (currentIdiom == DeviceIdiom.Unknown) - // { - // var configuration = Essentials.Platform.AppContext.Resources?.Configuration; - // if (configuration != null) - // { - // var minWidth = configuration.SmallestScreenWidthDp; - // var isWide = minWidth >= tabletCrossover; - // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; - // } - // else - // { - // // start clutching at straws - // using var metrics = Essentials.Platform.AppContext.Resources?.DisplayMetrics; - // if (metrics != null) - // { - // var minSize = Math.Min(metrics.WidthPixels, metrics.HeightPixels); - // var isWide = minSize * metrics.Density >= tabletCrossover; - // currentIdiom = isWide ? DeviceIdiom.Tablet : DeviceIdiom.Phone; - // } - // } - // } - // - // // hope we got it somewhere - // return currentIdiom; - // } - - // static DeviceIdiom DetectIdiom(UiMode uiMode) - // { - // if (uiMode == UiMode.TypeNormal) - // return DeviceIdiom.Unknown; - // else if (uiMode == UiMode.TypeTelevision) - // return DeviceIdiom.TV; - // else if (uiMode == UiMode.TypeDesk) - // return DeviceIdiom.Desktop; - // else if (Essentials.Platform.HasApiLevel(BuildVersionCodes.KitkatWatch) && uiMode == UiMode.TypeWatch) - // return DeviceIdiom.Watch; - // - // return DeviceIdiom.Unknown; - // } - - // static DeviceType GetDeviceType() - // { - // var isEmulator = - // (Build.Brand.StartsWith("generic", StringComparison.InvariantCulture) && Build.Device.StartsWith("generic", StringComparison.InvariantCulture)) || - // Build.Fingerprint.StartsWith("generic", StringComparison.InvariantCulture) || - // Build.Fingerprint.StartsWith("unknown", StringComparison.InvariantCulture) || - // Build.Hardware.Contains("goldfish") || - // Build.Hardware.Contains("ranchu") || - // Build.Model.Contains("google_sdk") || - // Build.Model.Contains("Emulator") || - // Build.Model.Contains("Android SDK built for x86") || - // Build.Manufacturer.Contains("Genymotion") || - // Build.Manufacturer.Contains("VS Emulator") || - // Build.Product.Contains("emulator") || - // Build.Product.Contains("google_sdk") || - // Build.Product.Contains("sdk") || - // Build.Product.Contains("sdk_google") || - // Build.Product.Contains("sdk_x86") || - // Build.Product.Contains("simulator") || - // Build.Product.Contains("vbox86p"); - // - // if (isEmulator) - // return DeviceType.Virtual; - // - // return DeviceType.Physical; - // } - - // private static string GetSystemSetting(string name, bool isGlobal = false) - // { - // if (isGlobal && Platform.HasApiLevel(BuildVersionCodes.NMr1)) - // return Settings.Global.GetString(Platform.AppContext.ContentResolver, name); - // else - // return Settings.System.GetString(Platform.AppContext.ContentResolver, name); - // } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs index e36d520c..0e5158e9 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs @@ -1,6 +1,3 @@ -using System; -using System.Diagnostics; -using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.EnvReporting.LayerModels; #if __WATCHOS__ using WatchKit; @@ -9,26 +6,17 @@ using UIKit; #endif -using ObjCRuntime; - namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { private static OsInfo? PlatformGetOsInfo() => - new OsInfo(GetManufacturer(), GetPlatform().ToString(), GetVersionString()); + new OsInfo("Apple", GetPlatform().ToString(), UIDevice.CurrentDevice.SystemVersion); private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => new EnvReporting.LayerModels.DeviceInfo( - GetManufacturer(), GetModel()); - - static string GetModel() => UIDevice.CurrentDevice.Model; - - static string GetManufacturer() => "Apple"; - - static string GetVersionString() => UIDevice.CurrentDevice.SystemVersion; - + "Apple", UIDevice.CurrentDevice.Model); static DevicePlatform GetPlatform() => #if __IOS__ DevicePlatform.iOS; diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs index f4e5b78a..4f4d267b 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs @@ -1,6 +1,3 @@ -using System; -using LaunchDarkly.Sdk.Client.Internal; -using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.EnvReporting.LayerModels; namespace LaunchDarkly.Sdk.Client.PlatformSpecific diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index 046c4004..c57802dc 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,7 +2,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -13,19 +12,143 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] + + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.123")] public partial class Resource { - + static Resource() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + public static void UpdateIdValues() { + global::LaunchDarkly.Sdk.Client.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; + global::LaunchDarkly.Sdk.Client.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; + global::LaunchDarkly.Sdk.Client.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; + global::LaunchDarkly.Sdk.Client.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; + global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; + global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; + global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; + global::LaunchDarkly.Sdk.Client.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; + global::LaunchDarkly.Sdk.Client.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; + global::LaunchDarkly.Sdk.Client.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; + global::LaunchDarkly.Sdk.Client.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; + global::LaunchDarkly.Sdk.Client.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; + global::LaunchDarkly.Sdk.Client.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; + global::LaunchDarkly.Sdk.Client.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; + global::LaunchDarkly.Sdk.Client.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; + global::LaunchDarkly.Sdk.Client.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; + global::LaunchDarkly.Sdk.Client.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; + global::LaunchDarkly.Sdk.Client.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; + global::LaunchDarkly.Sdk.Client.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; + global::LaunchDarkly.Sdk.Client.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; + global::LaunchDarkly.Sdk.Client.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; + global::LaunchDarkly.Sdk.Client.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; + global::LaunchDarkly.Sdk.Client.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; + global::LaunchDarkly.Sdk.Client.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; + global::LaunchDarkly.Sdk.Client.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; + global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; + global::LaunchDarkly.Sdk.Client.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; + global::LaunchDarkly.Sdk.Client.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; + global::LaunchDarkly.Sdk.Client.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; + global::LaunchDarkly.Sdk.Client.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; + global::LaunchDarkly.Sdk.Client.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; + global::LaunchDarkly.Sdk.Client.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; + global::LaunchDarkly.Sdk.Client.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; + global::LaunchDarkly.Sdk.Client.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; + global::LaunchDarkly.Sdk.Client.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; + global::LaunchDarkly.Sdk.Client.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; + global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; + global::LaunchDarkly.Sdk.Client.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; + global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; + global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; + global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; + global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; @@ -2013,5379 +2136,5379 @@ public static void UpdateIdValues() global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; } - + public partial class Animation { - + // aapt resource value: 0x7F010000 public const int abc_fade_in = 2130771968; - + // aapt resource value: 0x7F010001 public const int abc_fade_out = 2130771969; - + // aapt resource value: 0x7F010002 public const int abc_grow_fade_in_from_bottom = 2130771970; - + // aapt resource value: 0x7F010003 public const int abc_popup_enter = 2130771971; - + // aapt resource value: 0x7F010004 public const int abc_popup_exit = 2130771972; - + // aapt resource value: 0x7F010005 public const int abc_shrink_fade_out_from_bottom = 2130771973; - + // aapt resource value: 0x7F010006 public const int abc_slide_in_bottom = 2130771974; - + // aapt resource value: 0x7F010007 public const int abc_slide_in_top = 2130771975; - + // aapt resource value: 0x7F010008 public const int abc_slide_out_bottom = 2130771976; - + // aapt resource value: 0x7F010009 public const int abc_slide_out_top = 2130771977; - + // aapt resource value: 0x7F01000A public const int design_bottom_sheet_slide_in = 2130771978; - + // aapt resource value: 0x7F01000B public const int design_bottom_sheet_slide_out = 2130771979; - + // aapt resource value: 0x7F01000C public const int design_snackbar_in = 2130771980; - + // aapt resource value: 0x7F01000D public const int design_snackbar_out = 2130771981; - + // aapt resource value: 0x7F01000E public const int EnterFromLeft = 2130771982; - + // aapt resource value: 0x7F01000F public const int EnterFromRight = 2130771983; - + // aapt resource value: 0x7F010010 public const int ExitToLeft = 2130771984; - + // aapt resource value: 0x7F010011 public const int ExitToRight = 2130771985; - + // aapt resource value: 0x7F010012 public const int tooltip_enter = 2130771986; - + // aapt resource value: 0x7F010013 public const int tooltip_exit = 2130771987; - + static Animation() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animation() { } } - + public partial class Animator { - + // aapt resource value: 0x7F020000 public const int design_appbar_state_list_animator = 2130837504; - + static Animator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animator() { } } - + public partial class Attribute { - + // aapt resource value: 0x7F030000 public const int actionBarDivider = 2130903040; - + // aapt resource value: 0x7F030001 public const int actionBarItemBackground = 2130903041; - + // aapt resource value: 0x7F030002 public const int actionBarPopupTheme = 2130903042; - + // aapt resource value: 0x7F030003 public const int actionBarSize = 2130903043; - + // aapt resource value: 0x7F030004 public const int actionBarSplitStyle = 2130903044; - + // aapt resource value: 0x7F030005 public const int actionBarStyle = 2130903045; - + // aapt resource value: 0x7F030006 public const int actionBarTabBarStyle = 2130903046; - + // aapt resource value: 0x7F030007 public const int actionBarTabStyle = 2130903047; - + // aapt resource value: 0x7F030008 public const int actionBarTabTextStyle = 2130903048; - + // aapt resource value: 0x7F030009 public const int actionBarTheme = 2130903049; - + // aapt resource value: 0x7F03000A public const int actionBarWidgetTheme = 2130903050; - + // aapt resource value: 0x7F03000B public const int actionButtonStyle = 2130903051; - + // aapt resource value: 0x7F03000C public const int actionDropDownStyle = 2130903052; - + // aapt resource value: 0x7F03000D public const int actionLayout = 2130903053; - + // aapt resource value: 0x7F03000E public const int actionMenuTextAppearance = 2130903054; - + // aapt resource value: 0x7F03000F public const int actionMenuTextColor = 2130903055; - + // aapt resource value: 0x7F030010 public const int actionModeBackground = 2130903056; - + // aapt resource value: 0x7F030011 public const int actionModeCloseButtonStyle = 2130903057; - + // aapt resource value: 0x7F030012 public const int actionModeCloseDrawable = 2130903058; - + // aapt resource value: 0x7F030013 public const int actionModeCopyDrawable = 2130903059; - + // aapt resource value: 0x7F030014 public const int actionModeCutDrawable = 2130903060; - + // aapt resource value: 0x7F030015 public const int actionModeFindDrawable = 2130903061; - + // aapt resource value: 0x7F030016 public const int actionModePasteDrawable = 2130903062; - + // aapt resource value: 0x7F030017 public const int actionModePopupWindowStyle = 2130903063; - + // aapt resource value: 0x7F030018 public const int actionModeSelectAllDrawable = 2130903064; - + // aapt resource value: 0x7F030019 public const int actionModeShareDrawable = 2130903065; - + // aapt resource value: 0x7F03001A public const int actionModeSplitBackground = 2130903066; - + // aapt resource value: 0x7F03001B public const int actionModeStyle = 2130903067; - + // aapt resource value: 0x7F03001C public const int actionModeWebSearchDrawable = 2130903068; - + // aapt resource value: 0x7F03001D public const int actionOverflowButtonStyle = 2130903069; - + // aapt resource value: 0x7F03001E public const int actionOverflowMenuStyle = 2130903070; - + // aapt resource value: 0x7F03001F public const int actionProviderClass = 2130903071; - + // aapt resource value: 0x7F030020 public const int actionViewClass = 2130903072; - + // aapt resource value: 0x7F030021 public const int activityChooserViewStyle = 2130903073; - + // aapt resource value: 0x7F030022 public const int alertDialogButtonGroupStyle = 2130903074; - + // aapt resource value: 0x7F030023 public const int alertDialogCenterButtons = 2130903075; - + // aapt resource value: 0x7F030024 public const int alertDialogStyle = 2130903076; - + // aapt resource value: 0x7F030025 public const int alertDialogTheme = 2130903077; - + // aapt resource value: 0x7F030026 public const int allowStacking = 2130903078; - + // aapt resource value: 0x7F030027 public const int alpha = 2130903079; - + // aapt resource value: 0x7F030028 public const int alphabeticModifiers = 2130903080; - + // aapt resource value: 0x7F030029 public const int arrowHeadLength = 2130903081; - + // aapt resource value: 0x7F03002A public const int arrowShaftLength = 2130903082; - + // aapt resource value: 0x7F03002B public const int autoCompleteTextViewStyle = 2130903083; - + // aapt resource value: 0x7F03002C public const int autoSizeMaxTextSize = 2130903084; - + // aapt resource value: 0x7F03002D public const int autoSizeMinTextSize = 2130903085; - + // aapt resource value: 0x7F03002E public const int autoSizePresetSizes = 2130903086; - + // aapt resource value: 0x7F03002F public const int autoSizeStepGranularity = 2130903087; - + // aapt resource value: 0x7F030030 public const int autoSizeTextType = 2130903088; - + // aapt resource value: 0x7F030031 public const int background = 2130903089; - + // aapt resource value: 0x7F030032 public const int backgroundSplit = 2130903090; - + // aapt resource value: 0x7F030033 public const int backgroundStacked = 2130903091; - + // aapt resource value: 0x7F030034 public const int backgroundTint = 2130903092; - + // aapt resource value: 0x7F030035 public const int backgroundTintMode = 2130903093; - + // aapt resource value: 0x7F030036 public const int barLength = 2130903094; - + // aapt resource value: 0x7F030037 public const int behavior_autoHide = 2130903095; - + // aapt resource value: 0x7F030038 public const int behavior_hideable = 2130903096; - + // aapt resource value: 0x7F030039 public const int behavior_overlapTop = 2130903097; - + // aapt resource value: 0x7F03003A public const int behavior_peekHeight = 2130903098; - + // aapt resource value: 0x7F03003B public const int behavior_skipCollapsed = 2130903099; - + // aapt resource value: 0x7F03003D public const int borderlessButtonStyle = 2130903101; - + // aapt resource value: 0x7F03003C public const int borderWidth = 2130903100; - + // aapt resource value: 0x7F03003E public const int bottomSheetDialogTheme = 2130903102; - + // aapt resource value: 0x7F03003F public const int bottomSheetStyle = 2130903103; - + // aapt resource value: 0x7F030040 public const int buttonBarButtonStyle = 2130903104; - + // aapt resource value: 0x7F030041 public const int buttonBarNegativeButtonStyle = 2130903105; - + // aapt resource value: 0x7F030042 public const int buttonBarNeutralButtonStyle = 2130903106; - + // aapt resource value: 0x7F030043 public const int buttonBarPositiveButtonStyle = 2130903107; - + // aapt resource value: 0x7F030044 public const int buttonBarStyle = 2130903108; - + // aapt resource value: 0x7F030045 public const int buttonGravity = 2130903109; - + // aapt resource value: 0x7F030046 public const int buttonPanelSideLayout = 2130903110; - + // aapt resource value: 0x7F030047 public const int buttonStyle = 2130903111; - + // aapt resource value: 0x7F030048 public const int buttonStyleSmall = 2130903112; - + // aapt resource value: 0x7F030049 public const int buttonTint = 2130903113; - + // aapt resource value: 0x7F03004A public const int buttonTintMode = 2130903114; - + // aapt resource value: 0x7F03004B public const int cardBackgroundColor = 2130903115; - + // aapt resource value: 0x7F03004C public const int cardCornerRadius = 2130903116; - + // aapt resource value: 0x7F03004D public const int cardElevation = 2130903117; - + // aapt resource value: 0x7F03004E public const int cardMaxElevation = 2130903118; - + // aapt resource value: 0x7F03004F public const int cardPreventCornerOverlap = 2130903119; - + // aapt resource value: 0x7F030050 public const int cardUseCompatPadding = 2130903120; - + // aapt resource value: 0x7F030051 public const int checkboxStyle = 2130903121; - + // aapt resource value: 0x7F030052 public const int checkedTextViewStyle = 2130903122; - + // aapt resource value: 0x7F030053 public const int closeIcon = 2130903123; - + // aapt resource value: 0x7F030054 public const int closeItemLayout = 2130903124; - + // aapt resource value: 0x7F030055 public const int collapseContentDescription = 2130903125; - + // aapt resource value: 0x7F030057 public const int collapsedTitleGravity = 2130903127; - + // aapt resource value: 0x7F030058 public const int collapsedTitleTextAppearance = 2130903128; - + // aapt resource value: 0x7F030056 public const int collapseIcon = 2130903126; - + // aapt resource value: 0x7F030059 public const int color = 2130903129; - + // aapt resource value: 0x7F03005A public const int colorAccent = 2130903130; - + // aapt resource value: 0x7F03005B public const int colorBackgroundFloating = 2130903131; - + // aapt resource value: 0x7F03005C public const int colorButtonNormal = 2130903132; - + // aapt resource value: 0x7F03005D public const int colorControlActivated = 2130903133; - + // aapt resource value: 0x7F03005E public const int colorControlHighlight = 2130903134; - + // aapt resource value: 0x7F03005F public const int colorControlNormal = 2130903135; - + // aapt resource value: 0x7F030060 public const int colorError = 2130903136; - + // aapt resource value: 0x7F030061 public const int colorPrimary = 2130903137; - + // aapt resource value: 0x7F030062 public const int colorPrimaryDark = 2130903138; - + // aapt resource value: 0x7F030063 public const int colorSwitchThumbNormal = 2130903139; - + // aapt resource value: 0x7F030064 public const int commitIcon = 2130903140; - + // aapt resource value: 0x7F030065 public const int contentDescription = 2130903141; - + // aapt resource value: 0x7F030066 public const int contentInsetEnd = 2130903142; - + // aapt resource value: 0x7F030067 public const int contentInsetEndWithActions = 2130903143; - + // aapt resource value: 0x7F030068 public const int contentInsetLeft = 2130903144; - + // aapt resource value: 0x7F030069 public const int contentInsetRight = 2130903145; - + // aapt resource value: 0x7F03006A public const int contentInsetStart = 2130903146; - + // aapt resource value: 0x7F03006B public const int contentInsetStartWithNavigation = 2130903147; - + // aapt resource value: 0x7F03006C public const int contentPadding = 2130903148; - + // aapt resource value: 0x7F03006D public const int contentPaddingBottom = 2130903149; - + // aapt resource value: 0x7F03006E public const int contentPaddingLeft = 2130903150; - + // aapt resource value: 0x7F03006F public const int contentPaddingRight = 2130903151; - + // aapt resource value: 0x7F030070 public const int contentPaddingTop = 2130903152; - + // aapt resource value: 0x7F030071 public const int contentScrim = 2130903153; - + // aapt resource value: 0x7F030072 public const int controlBackground = 2130903154; - + // aapt resource value: 0x7F030073 public const int counterEnabled = 2130903155; - + // aapt resource value: 0x7F030074 public const int counterMaxLength = 2130903156; - + // aapt resource value: 0x7F030075 public const int counterOverflowTextAppearance = 2130903157; - + // aapt resource value: 0x7F030076 public const int counterTextAppearance = 2130903158; - + // aapt resource value: 0x7F030077 public const int customNavigationLayout = 2130903159; - + // aapt resource value: 0x7F030078 public const int defaultQueryHint = 2130903160; - + // aapt resource value: 0x7F030079 public const int dialogPreferredPadding = 2130903161; - + // aapt resource value: 0x7F03007A public const int dialogTheme = 2130903162; - + // aapt resource value: 0x7F03007B public const int displayOptions = 2130903163; - + // aapt resource value: 0x7F03007C public const int divider = 2130903164; - + // aapt resource value: 0x7F03007D public const int dividerHorizontal = 2130903165; - + // aapt resource value: 0x7F03007E public const int dividerPadding = 2130903166; - + // aapt resource value: 0x7F03007F public const int dividerVertical = 2130903167; - + // aapt resource value: 0x7F030080 public const int drawableSize = 2130903168; - + // aapt resource value: 0x7F030081 public const int drawerArrowStyle = 2130903169; - + // aapt resource value: 0x7F030083 public const int dropdownListPreferredItemHeight = 2130903171; - + // aapt resource value: 0x7F030082 public const int dropDownListViewStyle = 2130903170; - + // aapt resource value: 0x7F030084 public const int editTextBackground = 2130903172; - + // aapt resource value: 0x7F030085 public const int editTextColor = 2130903173; - + // aapt resource value: 0x7F030086 public const int editTextStyle = 2130903174; - + // aapt resource value: 0x7F030087 public const int elevation = 2130903175; - + // aapt resource value: 0x7F030088 public const int errorEnabled = 2130903176; - + // aapt resource value: 0x7F030089 public const int errorTextAppearance = 2130903177; - + // aapt resource value: 0x7F03008A public const int expandActivityOverflowButtonDrawable = 2130903178; - + // aapt resource value: 0x7F03008B public const int expanded = 2130903179; - + // aapt resource value: 0x7F03008C public const int expandedTitleGravity = 2130903180; - + // aapt resource value: 0x7F03008D public const int expandedTitleMargin = 2130903181; - + // aapt resource value: 0x7F03008E public const int expandedTitleMarginBottom = 2130903182; - + // aapt resource value: 0x7F03008F public const int expandedTitleMarginEnd = 2130903183; - + // aapt resource value: 0x7F030090 public const int expandedTitleMarginStart = 2130903184; - + // aapt resource value: 0x7F030091 public const int expandedTitleMarginTop = 2130903185; - + // aapt resource value: 0x7F030092 public const int expandedTitleTextAppearance = 2130903186; - + // aapt resource value: 0x7F030093 public const int externalRouteEnabledDrawable = 2130903187; - + // aapt resource value: 0x7F030094 public const int fabSize = 2130903188; - + // aapt resource value: 0x7F030095 public const int fastScrollEnabled = 2130903189; - + // aapt resource value: 0x7F030096 public const int fastScrollHorizontalThumbDrawable = 2130903190; - + // aapt resource value: 0x7F030097 public const int fastScrollHorizontalTrackDrawable = 2130903191; - + // aapt resource value: 0x7F030098 public const int fastScrollVerticalThumbDrawable = 2130903192; - + // aapt resource value: 0x7F030099 public const int fastScrollVerticalTrackDrawable = 2130903193; - + // aapt resource value: 0x7F03009A public const int font = 2130903194; - + // aapt resource value: 0x7F03009B public const int fontFamily = 2130903195; - + // aapt resource value: 0x7F03009C public const int fontProviderAuthority = 2130903196; - + // aapt resource value: 0x7F03009D public const int fontProviderCerts = 2130903197; - + // aapt resource value: 0x7F03009E public const int fontProviderFetchStrategy = 2130903198; - + // aapt resource value: 0x7F03009F public const int fontProviderFetchTimeout = 2130903199; - + // aapt resource value: 0x7F0300A0 public const int fontProviderPackage = 2130903200; - + // aapt resource value: 0x7F0300A1 public const int fontProviderQuery = 2130903201; - + // aapt resource value: 0x7F0300A2 public const int fontStyle = 2130903202; - + // aapt resource value: 0x7F0300A3 public const int fontWeight = 2130903203; - + // aapt resource value: 0x7F0300A4 public const int foregroundInsidePadding = 2130903204; - + // aapt resource value: 0x7F0300A5 public const int gapBetweenBars = 2130903205; - + // aapt resource value: 0x7F0300A6 public const int goIcon = 2130903206; - + // aapt resource value: 0x7F0300A7 public const int headerLayout = 2130903207; - + // aapt resource value: 0x7F0300A8 public const int height = 2130903208; - + // aapt resource value: 0x7F0300A9 public const int hideOnContentScroll = 2130903209; - + // aapt resource value: 0x7F0300AA public const int hintAnimationEnabled = 2130903210; - + // aapt resource value: 0x7F0300AB public const int hintEnabled = 2130903211; - + // aapt resource value: 0x7F0300AC public const int hintTextAppearance = 2130903212; - + // aapt resource value: 0x7F0300AD public const int homeAsUpIndicator = 2130903213; - + // aapt resource value: 0x7F0300AE public const int homeLayout = 2130903214; - + // aapt resource value: 0x7F0300AF public const int icon = 2130903215; - + // aapt resource value: 0x7F0300B2 public const int iconifiedByDefault = 2130903218; - + // aapt resource value: 0x7F0300B0 public const int iconTint = 2130903216; - + // aapt resource value: 0x7F0300B1 public const int iconTintMode = 2130903217; - + // aapt resource value: 0x7F0300B3 public const int imageButtonStyle = 2130903219; - + // aapt resource value: 0x7F0300B4 public const int indeterminateProgressStyle = 2130903220; - + // aapt resource value: 0x7F0300B5 public const int initialActivityCount = 2130903221; - + // aapt resource value: 0x7F0300B6 public const int insetForeground = 2130903222; - + // aapt resource value: 0x7F0300B7 public const int isLightTheme = 2130903223; - + // aapt resource value: 0x7F0300B8 public const int itemBackground = 2130903224; - + // aapt resource value: 0x7F0300B9 public const int itemIconTint = 2130903225; - + // aapt resource value: 0x7F0300BA public const int itemPadding = 2130903226; - + // aapt resource value: 0x7F0300BB public const int itemTextAppearance = 2130903227; - + // aapt resource value: 0x7F0300BC public const int itemTextColor = 2130903228; - + // aapt resource value: 0x7F0300BD public const int keylines = 2130903229; - + // aapt resource value: 0x7F0300BE public const int layout = 2130903230; - + // aapt resource value: 0x7F0300BF public const int layoutManager = 2130903231; - + // aapt resource value: 0x7F0300C0 public const int layout_anchor = 2130903232; - + // aapt resource value: 0x7F0300C1 public const int layout_anchorGravity = 2130903233; - + // aapt resource value: 0x7F0300C2 public const int layout_behavior = 2130903234; - + // aapt resource value: 0x7F0300C3 public const int layout_collapseMode = 2130903235; - + // aapt resource value: 0x7F0300C4 public const int layout_collapseParallaxMultiplier = 2130903236; - + // aapt resource value: 0x7F0300C5 public const int layout_dodgeInsetEdges = 2130903237; - + // aapt resource value: 0x7F0300C6 public const int layout_insetEdge = 2130903238; - + // aapt resource value: 0x7F0300C7 public const int layout_keyline = 2130903239; - + // aapt resource value: 0x7F0300C8 public const int layout_scrollFlags = 2130903240; - + // aapt resource value: 0x7F0300C9 public const int layout_scrollInterpolator = 2130903241; - + // aapt resource value: 0x7F0300CA public const int listChoiceBackgroundIndicator = 2130903242; - + // aapt resource value: 0x7F0300CB public const int listDividerAlertDialog = 2130903243; - + // aapt resource value: 0x7F0300CC public const int listItemLayout = 2130903244; - + // aapt resource value: 0x7F0300CD public const int listLayout = 2130903245; - + // aapt resource value: 0x7F0300CE public const int listMenuViewStyle = 2130903246; - + // aapt resource value: 0x7F0300CF public const int listPopupWindowStyle = 2130903247; - + // aapt resource value: 0x7F0300D0 public const int listPreferredItemHeight = 2130903248; - + // aapt resource value: 0x7F0300D1 public const int listPreferredItemHeightLarge = 2130903249; - + // aapt resource value: 0x7F0300D2 public const int listPreferredItemHeightSmall = 2130903250; - + // aapt resource value: 0x7F0300D3 public const int listPreferredItemPaddingLeft = 2130903251; - + // aapt resource value: 0x7F0300D4 public const int listPreferredItemPaddingRight = 2130903252; - + // aapt resource value: 0x7F0300D5 public const int logo = 2130903253; - + // aapt resource value: 0x7F0300D6 public const int logoDescription = 2130903254; - + // aapt resource value: 0x7F0300D7 public const int maxActionInlineWidth = 2130903255; - + // aapt resource value: 0x7F0300D8 public const int maxButtonHeight = 2130903256; - + // aapt resource value: 0x7F0300D9 public const int measureWithLargestChild = 2130903257; - + // aapt resource value: 0x7F0300DA public const int mediaRouteAudioTrackDrawable = 2130903258; - + // aapt resource value: 0x7F0300DB public const int mediaRouteButtonStyle = 2130903259; - + // aapt resource value: 0x7F0300DC public const int mediaRouteButtonTint = 2130903260; - + // aapt resource value: 0x7F0300DD public const int mediaRouteCloseDrawable = 2130903261; - + // aapt resource value: 0x7F0300DE public const int mediaRouteControlPanelThemeOverlay = 2130903262; - + // aapt resource value: 0x7F0300DF public const int mediaRouteDefaultIconDrawable = 2130903263; - + // aapt resource value: 0x7F0300E0 public const int mediaRoutePauseDrawable = 2130903264; - + // aapt resource value: 0x7F0300E1 public const int mediaRoutePlayDrawable = 2130903265; - + // aapt resource value: 0x7F0300E2 public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - + // aapt resource value: 0x7F0300E3 public const int mediaRouteSpeakerIconDrawable = 2130903267; - + // aapt resource value: 0x7F0300E4 public const int mediaRouteStopDrawable = 2130903268; - + // aapt resource value: 0x7F0300E5 public const int mediaRouteTheme = 2130903269; - + // aapt resource value: 0x7F0300E6 public const int mediaRouteTvIconDrawable = 2130903270; - + // aapt resource value: 0x7F0300E7 public const int menu = 2130903271; - + // aapt resource value: 0x7F0300E8 public const int multiChoiceItemLayout = 2130903272; - + // aapt resource value: 0x7F0300E9 public const int navigationContentDescription = 2130903273; - + // aapt resource value: 0x7F0300EA public const int navigationIcon = 2130903274; - + // aapt resource value: 0x7F0300EB public const int navigationMode = 2130903275; - + // aapt resource value: 0x7F0300EC public const int numericModifiers = 2130903276; - + // aapt resource value: 0x7F0300ED public const int overlapAnchor = 2130903277; - + // aapt resource value: 0x7F0300EE public const int paddingBottomNoButtons = 2130903278; - + // aapt resource value: 0x7F0300EF public const int paddingEnd = 2130903279; - + // aapt resource value: 0x7F0300F0 public const int paddingStart = 2130903280; - + // aapt resource value: 0x7F0300F1 public const int paddingTopNoTitle = 2130903281; - + // aapt resource value: 0x7F0300F2 public const int panelBackground = 2130903282; - + // aapt resource value: 0x7F0300F3 public const int panelMenuListTheme = 2130903283; - + // aapt resource value: 0x7F0300F4 public const int panelMenuListWidth = 2130903284; - + // aapt resource value: 0x7F0300F5 public const int passwordToggleContentDescription = 2130903285; - + // aapt resource value: 0x7F0300F6 public const int passwordToggleDrawable = 2130903286; - + // aapt resource value: 0x7F0300F7 public const int passwordToggleEnabled = 2130903287; - + // aapt resource value: 0x7F0300F8 public const int passwordToggleTint = 2130903288; - + // aapt resource value: 0x7F0300F9 public const int passwordToggleTintMode = 2130903289; - + // aapt resource value: 0x7F0300FA public const int popupMenuStyle = 2130903290; - + // aapt resource value: 0x7F0300FB public const int popupTheme = 2130903291; - + // aapt resource value: 0x7F0300FC public const int popupWindowStyle = 2130903292; - + // aapt resource value: 0x7F0300FD public const int preserveIconSpacing = 2130903293; - + // aapt resource value: 0x7F0300FE public const int pressedTranslationZ = 2130903294; - + // aapt resource value: 0x7F0300FF public const int progressBarPadding = 2130903295; - + // aapt resource value: 0x7F030100 public const int progressBarStyle = 2130903296; - + // aapt resource value: 0x7F030101 public const int queryBackground = 2130903297; - + // aapt resource value: 0x7F030102 public const int queryHint = 2130903298; - + // aapt resource value: 0x7F030103 public const int radioButtonStyle = 2130903299; - + // aapt resource value: 0x7F030104 public const int ratingBarStyle = 2130903300; - + // aapt resource value: 0x7F030105 public const int ratingBarStyleIndicator = 2130903301; - + // aapt resource value: 0x7F030106 public const int ratingBarStyleSmall = 2130903302; - + // aapt resource value: 0x7F030107 public const int reverseLayout = 2130903303; - + // aapt resource value: 0x7F030108 public const int rippleColor = 2130903304; - + // aapt resource value: 0x7F030109 public const int scrimAnimationDuration = 2130903305; - + // aapt resource value: 0x7F03010A public const int scrimVisibleHeightTrigger = 2130903306; - + // aapt resource value: 0x7F03010B public const int searchHintIcon = 2130903307; - + // aapt resource value: 0x7F03010C public const int searchIcon = 2130903308; - + // aapt resource value: 0x7F03010D public const int searchViewStyle = 2130903309; - + // aapt resource value: 0x7F03010E public const int seekBarStyle = 2130903310; - + // aapt resource value: 0x7F03010F public const int selectableItemBackground = 2130903311; - + // aapt resource value: 0x7F030110 public const int selectableItemBackgroundBorderless = 2130903312; - + // aapt resource value: 0x7F030111 public const int showAsAction = 2130903313; - + // aapt resource value: 0x7F030112 public const int showDividers = 2130903314; - + // aapt resource value: 0x7F030113 public const int showText = 2130903315; - + // aapt resource value: 0x7F030114 public const int showTitle = 2130903316; - + // aapt resource value: 0x7F030115 public const int singleChoiceItemLayout = 2130903317; - + // aapt resource value: 0x7F030116 public const int spanCount = 2130903318; - + // aapt resource value: 0x7F030117 public const int spinBars = 2130903319; - + // aapt resource value: 0x7F030118 public const int spinnerDropDownItemStyle = 2130903320; - + // aapt resource value: 0x7F030119 public const int spinnerStyle = 2130903321; - + // aapt resource value: 0x7F03011A public const int splitTrack = 2130903322; - + // aapt resource value: 0x7F03011B public const int srcCompat = 2130903323; - + // aapt resource value: 0x7F03011C public const int stackFromEnd = 2130903324; - + // aapt resource value: 0x7F03011D public const int state_above_anchor = 2130903325; - + // aapt resource value: 0x7F03011E public const int state_collapsed = 2130903326; - + // aapt resource value: 0x7F03011F public const int state_collapsible = 2130903327; - + // aapt resource value: 0x7F030120 public const int statusBarBackground = 2130903328; - + // aapt resource value: 0x7F030121 public const int statusBarScrim = 2130903329; - + // aapt resource value: 0x7F030122 public const int subMenuArrow = 2130903330; - + // aapt resource value: 0x7F030123 public const int submitBackground = 2130903331; - + // aapt resource value: 0x7F030124 public const int subtitle = 2130903332; - + // aapt resource value: 0x7F030125 public const int subtitleTextAppearance = 2130903333; - + // aapt resource value: 0x7F030126 public const int subtitleTextColor = 2130903334; - + // aapt resource value: 0x7F030127 public const int subtitleTextStyle = 2130903335; - + // aapt resource value: 0x7F030128 public const int suggestionRowLayout = 2130903336; - + // aapt resource value: 0x7F030129 public const int switchMinWidth = 2130903337; - + // aapt resource value: 0x7F03012A public const int switchPadding = 2130903338; - + // aapt resource value: 0x7F03012B public const int switchStyle = 2130903339; - + // aapt resource value: 0x7F03012C public const int switchTextAppearance = 2130903340; - + // aapt resource value: 0x7F03012D public const int tabBackground = 2130903341; - + // aapt resource value: 0x7F03012E public const int tabContentStart = 2130903342; - + // aapt resource value: 0x7F03012F public const int tabGravity = 2130903343; - + // aapt resource value: 0x7F030130 public const int tabIndicatorColor = 2130903344; - + // aapt resource value: 0x7F030131 public const int tabIndicatorHeight = 2130903345; - + // aapt resource value: 0x7F030132 public const int tabMaxWidth = 2130903346; - + // aapt resource value: 0x7F030133 public const int tabMinWidth = 2130903347; - + // aapt resource value: 0x7F030134 public const int tabMode = 2130903348; - + // aapt resource value: 0x7F030135 public const int tabPadding = 2130903349; - + // aapt resource value: 0x7F030136 public const int tabPaddingBottom = 2130903350; - + // aapt resource value: 0x7F030137 public const int tabPaddingEnd = 2130903351; - + // aapt resource value: 0x7F030138 public const int tabPaddingStart = 2130903352; - + // aapt resource value: 0x7F030139 public const int tabPaddingTop = 2130903353; - + // aapt resource value: 0x7F03013A public const int tabSelectedTextColor = 2130903354; - + // aapt resource value: 0x7F03013B public const int tabTextAppearance = 2130903355; - + // aapt resource value: 0x7F03013C public const int tabTextColor = 2130903356; - + // aapt resource value: 0x7F03013D public const int textAllCaps = 2130903357; - + // aapt resource value: 0x7F03013E public const int textAppearanceLargePopupMenu = 2130903358; - + // aapt resource value: 0x7F03013F public const int textAppearanceListItem = 2130903359; - + // aapt resource value: 0x7F030140 public const int textAppearanceListItemSecondary = 2130903360; - + // aapt resource value: 0x7F030141 public const int textAppearanceListItemSmall = 2130903361; - + // aapt resource value: 0x7F030142 public const int textAppearancePopupMenuHeader = 2130903362; - + // aapt resource value: 0x7F030143 public const int textAppearanceSearchResultSubtitle = 2130903363; - + // aapt resource value: 0x7F030144 public const int textAppearanceSearchResultTitle = 2130903364; - + // aapt resource value: 0x7F030145 public const int textAppearanceSmallPopupMenu = 2130903365; - + // aapt resource value: 0x7F030146 public const int textColorAlertDialogListItem = 2130903366; - + // aapt resource value: 0x7F030147 public const int textColorError = 2130903367; - + // aapt resource value: 0x7F030148 public const int textColorSearchUrl = 2130903368; - + // aapt resource value: 0x7F030149 public const int theme = 2130903369; - + // aapt resource value: 0x7F03014A public const int thickness = 2130903370; - + // aapt resource value: 0x7F03014B public const int thumbTextPadding = 2130903371; - + // aapt resource value: 0x7F03014C public const int thumbTint = 2130903372; - + // aapt resource value: 0x7F03014D public const int thumbTintMode = 2130903373; - + // aapt resource value: 0x7F03014E public const int tickMark = 2130903374; - + // aapt resource value: 0x7F03014F public const int tickMarkTint = 2130903375; - + // aapt resource value: 0x7F030150 public const int tickMarkTintMode = 2130903376; - + // aapt resource value: 0x7F030151 public const int tint = 2130903377; - + // aapt resource value: 0x7F030152 public const int tintMode = 2130903378; - + // aapt resource value: 0x7F030153 public const int title = 2130903379; - + // aapt resource value: 0x7F030154 public const int titleEnabled = 2130903380; - + // aapt resource value: 0x7F030155 public const int titleMargin = 2130903381; - + // aapt resource value: 0x7F030156 public const int titleMarginBottom = 2130903382; - + // aapt resource value: 0x7F030157 public const int titleMarginEnd = 2130903383; - + // aapt resource value: 0x7F03015A public const int titleMargins = 2130903386; - + // aapt resource value: 0x7F030158 public const int titleMarginStart = 2130903384; - + // aapt resource value: 0x7F030159 public const int titleMarginTop = 2130903385; - + // aapt resource value: 0x7F03015B public const int titleTextAppearance = 2130903387; - + // aapt resource value: 0x7F03015C public const int titleTextColor = 2130903388; - + // aapt resource value: 0x7F03015D public const int titleTextStyle = 2130903389; - + // aapt resource value: 0x7F03015E public const int toolbarId = 2130903390; - + // aapt resource value: 0x7F03015F public const int toolbarNavigationButtonStyle = 2130903391; - + // aapt resource value: 0x7F030160 public const int toolbarStyle = 2130903392; - + // aapt resource value: 0x7F030161 public const int tooltipForegroundColor = 2130903393; - + // aapt resource value: 0x7F030162 public const int tooltipFrameBackground = 2130903394; - + // aapt resource value: 0x7F030163 public const int tooltipText = 2130903395; - + // aapt resource value: 0x7F030164 public const int track = 2130903396; - + // aapt resource value: 0x7F030165 public const int trackTint = 2130903397; - + // aapt resource value: 0x7F030166 public const int trackTintMode = 2130903398; - + // aapt resource value: 0x7F030167 public const int useCompatPadding = 2130903399; - + // aapt resource value: 0x7F030168 public const int voiceIcon = 2130903400; - + // aapt resource value: 0x7F030169 public const int windowActionBar = 2130903401; - + // aapt resource value: 0x7F03016A public const int windowActionBarOverlay = 2130903402; - + // aapt resource value: 0x7F03016B public const int windowActionModeOverlay = 2130903403; - + // aapt resource value: 0x7F03016C public const int windowFixedHeightMajor = 2130903404; - + // aapt resource value: 0x7F03016D public const int windowFixedHeightMinor = 2130903405; - + // aapt resource value: 0x7F03016E public const int windowFixedWidthMajor = 2130903406; - + // aapt resource value: 0x7F03016F public const int windowFixedWidthMinor = 2130903407; - + // aapt resource value: 0x7F030170 public const int windowMinWidthMajor = 2130903408; - + // aapt resource value: 0x7F030171 public const int windowMinWidthMinor = 2130903409; - + // aapt resource value: 0x7F030172 public const int windowNoTitle = 2130903410; - + static Attribute() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Attribute() { } } - + public partial class Boolean { - + // aapt resource value: 0x7F040000 public const int abc_action_bar_embed_tabs = 2130968576; - + // aapt resource value: 0x7F040001 public const int abc_allow_stacked_button_bar = 2130968577; - + // aapt resource value: 0x7F040002 public const int abc_config_actionMenuItemAllCaps = 2130968578; - + // aapt resource value: 0x7F040003 public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - + // aapt resource value: 0x7F040004 public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; - + static Boolean() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Boolean() { } } - + public partial class Color { - + // aapt resource value: 0x7F050000 public const int abc_background_cache_hint_selector_material_dark = 2131034112; - + // aapt resource value: 0x7F050001 public const int abc_background_cache_hint_selector_material_light = 2131034113; - + // aapt resource value: 0x7F050002 public const int abc_btn_colored_borderless_text_material = 2131034114; - + // aapt resource value: 0x7F050003 public const int abc_btn_colored_text_material = 2131034115; - + // aapt resource value: 0x7F050004 public const int abc_color_highlight_material = 2131034116; - + // aapt resource value: 0x7F050005 public const int abc_hint_foreground_material_dark = 2131034117; - + // aapt resource value: 0x7F050006 public const int abc_hint_foreground_material_light = 2131034118; - + // aapt resource value: 0x7F050007 public const int abc_input_method_navigation_guard = 2131034119; - + // aapt resource value: 0x7F050008 public const int abc_primary_text_disable_only_material_dark = 2131034120; - + // aapt resource value: 0x7F050009 public const int abc_primary_text_disable_only_material_light = 2131034121; - + // aapt resource value: 0x7F05000A public const int abc_primary_text_material_dark = 2131034122; - + // aapt resource value: 0x7F05000B public const int abc_primary_text_material_light = 2131034123; - + // aapt resource value: 0x7F05000C public const int abc_search_url_text = 2131034124; - + // aapt resource value: 0x7F05000D public const int abc_search_url_text_normal = 2131034125; - + // aapt resource value: 0x7F05000E public const int abc_search_url_text_pressed = 2131034126; - + // aapt resource value: 0x7F05000F public const int abc_search_url_text_selected = 2131034127; - + // aapt resource value: 0x7F050010 public const int abc_secondary_text_material_dark = 2131034128; - + // aapt resource value: 0x7F050011 public const int abc_secondary_text_material_light = 2131034129; - + // aapt resource value: 0x7F050012 public const int abc_tint_btn_checkable = 2131034130; - + // aapt resource value: 0x7F050013 public const int abc_tint_default = 2131034131; - + // aapt resource value: 0x7F050014 public const int abc_tint_edittext = 2131034132; - + // aapt resource value: 0x7F050015 public const int abc_tint_seek_thumb = 2131034133; - + // aapt resource value: 0x7F050016 public const int abc_tint_spinner = 2131034134; - + // aapt resource value: 0x7F050017 public const int abc_tint_switch_track = 2131034135; - + // aapt resource value: 0x7F050018 public const int accent_material_dark = 2131034136; - + // aapt resource value: 0x7F050019 public const int accent_material_light = 2131034137; - + // aapt resource value: 0x7F05001A public const int background_floating_material_dark = 2131034138; - + // aapt resource value: 0x7F05001B public const int background_floating_material_light = 2131034139; - + // aapt resource value: 0x7F05001C public const int background_material_dark = 2131034140; - + // aapt resource value: 0x7F05001D public const int background_material_light = 2131034141; - + // aapt resource value: 0x7F05001E public const int bright_foreground_disabled_material_dark = 2131034142; - + // aapt resource value: 0x7F05001F public const int bright_foreground_disabled_material_light = 2131034143; - + // aapt resource value: 0x7F050020 public const int bright_foreground_inverse_material_dark = 2131034144; - + // aapt resource value: 0x7F050021 public const int bright_foreground_inverse_material_light = 2131034145; - + // aapt resource value: 0x7F050022 public const int bright_foreground_material_dark = 2131034146; - + // aapt resource value: 0x7F050023 public const int bright_foreground_material_light = 2131034147; - + // aapt resource value: 0x7F050024 public const int button_material_dark = 2131034148; - + // aapt resource value: 0x7F050025 public const int button_material_light = 2131034149; - + // aapt resource value: 0x7F050026 public const int cardview_dark_background = 2131034150; - + // aapt resource value: 0x7F050027 public const int cardview_light_background = 2131034151; - + // aapt resource value: 0x7F050028 public const int cardview_shadow_end_color = 2131034152; - + // aapt resource value: 0x7F050029 public const int cardview_shadow_start_color = 2131034153; - + // aapt resource value: 0x7F05002A public const int colorAccent = 2131034154; - + // aapt resource value: 0x7F05002B public const int colorPrimary = 2131034155; - + // aapt resource value: 0x7F05002C public const int colorPrimaryDark = 2131034156; - + // aapt resource value: 0x7F05002D public const int design_bottom_navigation_shadow_color = 2131034157; - + // aapt resource value: 0x7F05002E public const int design_error = 2131034158; - + // aapt resource value: 0x7F05002F public const int design_fab_shadow_end_color = 2131034159; - + // aapt resource value: 0x7F050030 public const int design_fab_shadow_mid_color = 2131034160; - + // aapt resource value: 0x7F050031 public const int design_fab_shadow_start_color = 2131034161; - + // aapt resource value: 0x7F050032 public const int design_fab_stroke_end_inner_color = 2131034162; - + // aapt resource value: 0x7F050033 public const int design_fab_stroke_end_outer_color = 2131034163; - + // aapt resource value: 0x7F050034 public const int design_fab_stroke_top_inner_color = 2131034164; - + // aapt resource value: 0x7F050035 public const int design_fab_stroke_top_outer_color = 2131034165; - + // aapt resource value: 0x7F050036 public const int design_snackbar_background_color = 2131034166; - + // aapt resource value: 0x7F050037 public const int design_tint_password_toggle = 2131034167; - + // aapt resource value: 0x7F050038 public const int dim_foreground_disabled_material_dark = 2131034168; - + // aapt resource value: 0x7F050039 public const int dim_foreground_disabled_material_light = 2131034169; - + // aapt resource value: 0x7F05003A public const int dim_foreground_material_dark = 2131034170; - + // aapt resource value: 0x7F05003B public const int dim_foreground_material_light = 2131034171; - + // aapt resource value: 0x7F05003C public const int error_color_material = 2131034172; - + // aapt resource value: 0x7F05003D public const int foreground_material_dark = 2131034173; - + // aapt resource value: 0x7F05003E public const int foreground_material_light = 2131034174; - + // aapt resource value: 0x7F05003F public const int highlighted_text_material_dark = 2131034175; - + // aapt resource value: 0x7F050040 public const int highlighted_text_material_light = 2131034176; - + // aapt resource value: 0x7F050041 public const int ic_launcher_background = 2131034177; - + // aapt resource value: 0x7F050042 public const int material_blue_grey_800 = 2131034178; - + // aapt resource value: 0x7F050043 public const int material_blue_grey_900 = 2131034179; - + // aapt resource value: 0x7F050044 public const int material_blue_grey_950 = 2131034180; - + // aapt resource value: 0x7F050045 public const int material_deep_teal_200 = 2131034181; - + // aapt resource value: 0x7F050046 public const int material_deep_teal_500 = 2131034182; - + // aapt resource value: 0x7F050047 public const int material_grey_100 = 2131034183; - + // aapt resource value: 0x7F050048 public const int material_grey_300 = 2131034184; - + // aapt resource value: 0x7F050049 public const int material_grey_50 = 2131034185; - + // aapt resource value: 0x7F05004A public const int material_grey_600 = 2131034186; - + // aapt resource value: 0x7F05004B public const int material_grey_800 = 2131034187; - + // aapt resource value: 0x7F05004C public const int material_grey_850 = 2131034188; - + // aapt resource value: 0x7F05004D public const int material_grey_900 = 2131034189; - + // aapt resource value: 0x7F05004E public const int notification_action_color_filter = 2131034190; - + // aapt resource value: 0x7F05004F public const int notification_icon_bg_color = 2131034191; - + // aapt resource value: 0x7F050050 public const int notification_material_background_media_default_color = 2131034192; - + // aapt resource value: 0x7F050051 public const int primary_dark_material_dark = 2131034193; - + // aapt resource value: 0x7F050052 public const int primary_dark_material_light = 2131034194; - + // aapt resource value: 0x7F050053 public const int primary_material_dark = 2131034195; - + // aapt resource value: 0x7F050054 public const int primary_material_light = 2131034196; - + // aapt resource value: 0x7F050055 public const int primary_text_default_material_dark = 2131034197; - + // aapt resource value: 0x7F050056 public const int primary_text_default_material_light = 2131034198; - + // aapt resource value: 0x7F050057 public const int primary_text_disabled_material_dark = 2131034199; - + // aapt resource value: 0x7F050058 public const int primary_text_disabled_material_light = 2131034200; - + // aapt resource value: 0x7F050059 public const int ripple_material_dark = 2131034201; - + // aapt resource value: 0x7F05005A public const int ripple_material_light = 2131034202; - + // aapt resource value: 0x7F05005B public const int secondary_text_default_material_dark = 2131034203; - + // aapt resource value: 0x7F05005C public const int secondary_text_default_material_light = 2131034204; - + // aapt resource value: 0x7F05005D public const int secondary_text_disabled_material_dark = 2131034205; - + // aapt resource value: 0x7F05005E public const int secondary_text_disabled_material_light = 2131034206; - + // aapt resource value: 0x7F05005F public const int switch_thumb_disabled_material_dark = 2131034207; - + // aapt resource value: 0x7F050060 public const int switch_thumb_disabled_material_light = 2131034208; - + // aapt resource value: 0x7F050061 public const int switch_thumb_material_dark = 2131034209; - + // aapt resource value: 0x7F050062 public const int switch_thumb_material_light = 2131034210; - + // aapt resource value: 0x7F050063 public const int switch_thumb_normal_material_dark = 2131034211; - + // aapt resource value: 0x7F050064 public const int switch_thumb_normal_material_light = 2131034212; - + // aapt resource value: 0x7F050065 public const int tooltip_background_dark = 2131034213; - + // aapt resource value: 0x7F050066 public const int tooltip_background_light = 2131034214; - + static Color() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Color() { } } - + public partial class Dimension { - + // aapt resource value: 0x7F060000 public const int abc_action_bar_content_inset_material = 2131099648; - + // aapt resource value: 0x7F060001 public const int abc_action_bar_content_inset_with_nav = 2131099649; - + // aapt resource value: 0x7F060002 public const int abc_action_bar_default_height_material = 2131099650; - + // aapt resource value: 0x7F060003 public const int abc_action_bar_default_padding_end_material = 2131099651; - + // aapt resource value: 0x7F060004 public const int abc_action_bar_default_padding_start_material = 2131099652; - + // aapt resource value: 0x7F060005 public const int abc_action_bar_elevation_material = 2131099653; - + // aapt resource value: 0x7F060006 public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - + // aapt resource value: 0x7F060007 public const int abc_action_bar_overflow_padding_end_material = 2131099655; - + // aapt resource value: 0x7F060008 public const int abc_action_bar_overflow_padding_start_material = 2131099656; - + // aapt resource value: 0x7F060009 public const int abc_action_bar_progress_bar_size = 2131099657; - + // aapt resource value: 0x7F06000A public const int abc_action_bar_stacked_max_height = 2131099658; - + // aapt resource value: 0x7F06000B public const int abc_action_bar_stacked_tab_max_width = 2131099659; - + // aapt resource value: 0x7F06000C public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - + // aapt resource value: 0x7F06000D public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - + // aapt resource value: 0x7F06000E public const int abc_action_button_min_height_material = 2131099662; - + // aapt resource value: 0x7F06000F public const int abc_action_button_min_width_material = 2131099663; - + // aapt resource value: 0x7F060010 public const int abc_action_button_min_width_overflow_material = 2131099664; - + // aapt resource value: 0x7F060011 public const int abc_alert_dialog_button_bar_height = 2131099665; - + // aapt resource value: 0x7F060012 public const int abc_button_inset_horizontal_material = 2131099666; - + // aapt resource value: 0x7F060013 public const int abc_button_inset_vertical_material = 2131099667; - + // aapt resource value: 0x7F060014 public const int abc_button_padding_horizontal_material = 2131099668; - + // aapt resource value: 0x7F060015 public const int abc_button_padding_vertical_material = 2131099669; - + // aapt resource value: 0x7F060016 public const int abc_cascading_menus_min_smallest_width = 2131099670; - + // aapt resource value: 0x7F060017 public const int abc_config_prefDialogWidth = 2131099671; - + // aapt resource value: 0x7F060018 public const int abc_control_corner_material = 2131099672; - + // aapt resource value: 0x7F060019 public const int abc_control_inset_material = 2131099673; - + // aapt resource value: 0x7F06001A public const int abc_control_padding_material = 2131099674; - + // aapt resource value: 0x7F06001B public const int abc_dialog_fixed_height_major = 2131099675; - + // aapt resource value: 0x7F06001C public const int abc_dialog_fixed_height_minor = 2131099676; - + // aapt resource value: 0x7F06001D public const int abc_dialog_fixed_width_major = 2131099677; - + // aapt resource value: 0x7F06001E public const int abc_dialog_fixed_width_minor = 2131099678; - + // aapt resource value: 0x7F06001F public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - + // aapt resource value: 0x7F060020 public const int abc_dialog_list_padding_top_no_title = 2131099680; - + // aapt resource value: 0x7F060021 public const int abc_dialog_min_width_major = 2131099681; - + // aapt resource value: 0x7F060022 public const int abc_dialog_min_width_minor = 2131099682; - + // aapt resource value: 0x7F060023 public const int abc_dialog_padding_material = 2131099683; - + // aapt resource value: 0x7F060024 public const int abc_dialog_padding_top_material = 2131099684; - + // aapt resource value: 0x7F060025 public const int abc_dialog_title_divider_material = 2131099685; - + // aapt resource value: 0x7F060026 public const int abc_disabled_alpha_material_dark = 2131099686; - + // aapt resource value: 0x7F060027 public const int abc_disabled_alpha_material_light = 2131099687; - + // aapt resource value: 0x7F060028 public const int abc_dropdownitem_icon_width = 2131099688; - + // aapt resource value: 0x7F060029 public const int abc_dropdownitem_text_padding_left = 2131099689; - + // aapt resource value: 0x7F06002A public const int abc_dropdownitem_text_padding_right = 2131099690; - + // aapt resource value: 0x7F06002B public const int abc_edit_text_inset_bottom_material = 2131099691; - + // aapt resource value: 0x7F06002C public const int abc_edit_text_inset_horizontal_material = 2131099692; - + // aapt resource value: 0x7F06002D public const int abc_edit_text_inset_top_material = 2131099693; - + // aapt resource value: 0x7F06002E public const int abc_floating_window_z = 2131099694; - + // aapt resource value: 0x7F06002F public const int abc_list_item_padding_horizontal_material = 2131099695; - + // aapt resource value: 0x7F060030 public const int abc_panel_menu_list_width = 2131099696; - + // aapt resource value: 0x7F060031 public const int abc_progress_bar_height_material = 2131099697; - + // aapt resource value: 0x7F060032 public const int abc_search_view_preferred_height = 2131099698; - + // aapt resource value: 0x7F060033 public const int abc_search_view_preferred_width = 2131099699; - + // aapt resource value: 0x7F060034 public const int abc_seekbar_track_background_height_material = 2131099700; - + // aapt resource value: 0x7F060035 public const int abc_seekbar_track_progress_height_material = 2131099701; - + // aapt resource value: 0x7F060036 public const int abc_select_dialog_padding_start_material = 2131099702; - + // aapt resource value: 0x7F060037 public const int abc_switch_padding = 2131099703; - + // aapt resource value: 0x7F060038 public const int abc_text_size_body_1_material = 2131099704; - + // aapt resource value: 0x7F060039 public const int abc_text_size_body_2_material = 2131099705; - + // aapt resource value: 0x7F06003A public const int abc_text_size_button_material = 2131099706; - + // aapt resource value: 0x7F06003B public const int abc_text_size_caption_material = 2131099707; - + // aapt resource value: 0x7F06003C public const int abc_text_size_display_1_material = 2131099708; - + // aapt resource value: 0x7F06003D public const int abc_text_size_display_2_material = 2131099709; - + // aapt resource value: 0x7F06003E public const int abc_text_size_display_3_material = 2131099710; - + // aapt resource value: 0x7F06003F public const int abc_text_size_display_4_material = 2131099711; - + // aapt resource value: 0x7F060040 public const int abc_text_size_headline_material = 2131099712; - + // aapt resource value: 0x7F060041 public const int abc_text_size_large_material = 2131099713; - + // aapt resource value: 0x7F060042 public const int abc_text_size_medium_material = 2131099714; - + // aapt resource value: 0x7F060043 public const int abc_text_size_menu_header_material = 2131099715; - + // aapt resource value: 0x7F060044 public const int abc_text_size_menu_material = 2131099716; - + // aapt resource value: 0x7F060045 public const int abc_text_size_small_material = 2131099717; - + // aapt resource value: 0x7F060046 public const int abc_text_size_subhead_material = 2131099718; - + // aapt resource value: 0x7F060047 public const int abc_text_size_subtitle_material_toolbar = 2131099719; - + // aapt resource value: 0x7F060048 public const int abc_text_size_title_material = 2131099720; - + // aapt resource value: 0x7F060049 public const int abc_text_size_title_material_toolbar = 2131099721; - + // aapt resource value: 0x7F06004A public const int cardview_compat_inset_shadow = 2131099722; - + // aapt resource value: 0x7F06004B public const int cardview_default_elevation = 2131099723; - + // aapt resource value: 0x7F06004C public const int cardview_default_radius = 2131099724; - + // aapt resource value: 0x7F06004D public const int compat_button_inset_horizontal_material = 2131099725; - + // aapt resource value: 0x7F06004E public const int compat_button_inset_vertical_material = 2131099726; - + // aapt resource value: 0x7F06004F public const int compat_button_padding_horizontal_material = 2131099727; - + // aapt resource value: 0x7F060050 public const int compat_button_padding_vertical_material = 2131099728; - + // aapt resource value: 0x7F060051 public const int compat_control_corner_material = 2131099729; - + // aapt resource value: 0x7F060052 public const int design_appbar_elevation = 2131099730; - + // aapt resource value: 0x7F060053 public const int design_bottom_navigation_active_item_max_width = 2131099731; - + // aapt resource value: 0x7F060054 public const int design_bottom_navigation_active_text_size = 2131099732; - + // aapt resource value: 0x7F060055 public const int design_bottom_navigation_elevation = 2131099733; - + // aapt resource value: 0x7F060056 public const int design_bottom_navigation_height = 2131099734; - + // aapt resource value: 0x7F060057 public const int design_bottom_navigation_item_max_width = 2131099735; - + // aapt resource value: 0x7F060058 public const int design_bottom_navigation_item_min_width = 2131099736; - + // aapt resource value: 0x7F060059 public const int design_bottom_navigation_margin = 2131099737; - + // aapt resource value: 0x7F06005A public const int design_bottom_navigation_shadow_height = 2131099738; - + // aapt resource value: 0x7F06005B public const int design_bottom_navigation_text_size = 2131099739; - + // aapt resource value: 0x7F06005C public const int design_bottom_sheet_modal_elevation = 2131099740; - + // aapt resource value: 0x7F06005D public const int design_bottom_sheet_peek_height_min = 2131099741; - + // aapt resource value: 0x7F06005E public const int design_fab_border_width = 2131099742; - + // aapt resource value: 0x7F06005F public const int design_fab_elevation = 2131099743; - + // aapt resource value: 0x7F060060 public const int design_fab_image_size = 2131099744; - + // aapt resource value: 0x7F060061 public const int design_fab_size_mini = 2131099745; - + // aapt resource value: 0x7F060062 public const int design_fab_size_normal = 2131099746; - + // aapt resource value: 0x7F060063 public const int design_fab_translation_z_pressed = 2131099747; - + // aapt resource value: 0x7F060064 public const int design_navigation_elevation = 2131099748; - + // aapt resource value: 0x7F060065 public const int design_navigation_icon_padding = 2131099749; - + // aapt resource value: 0x7F060066 public const int design_navigation_icon_size = 2131099750; - + // aapt resource value: 0x7F060067 public const int design_navigation_max_width = 2131099751; - + // aapt resource value: 0x7F060068 public const int design_navigation_padding_bottom = 2131099752; - + // aapt resource value: 0x7F060069 public const int design_navigation_separator_vertical_padding = 2131099753; - + // aapt resource value: 0x7F06006A public const int design_snackbar_action_inline_max_width = 2131099754; - + // aapt resource value: 0x7F06006B public const int design_snackbar_background_corner_radius = 2131099755; - + // aapt resource value: 0x7F06006C public const int design_snackbar_elevation = 2131099756; - + // aapt resource value: 0x7F06006D public const int design_snackbar_extra_spacing_horizontal = 2131099757; - + // aapt resource value: 0x7F06006E public const int design_snackbar_max_width = 2131099758; - + // aapt resource value: 0x7F06006F public const int design_snackbar_min_width = 2131099759; - + // aapt resource value: 0x7F060070 public const int design_snackbar_padding_horizontal = 2131099760; - + // aapt resource value: 0x7F060071 public const int design_snackbar_padding_vertical = 2131099761; - + // aapt resource value: 0x7F060072 public const int design_snackbar_padding_vertical_2lines = 2131099762; - + // aapt resource value: 0x7F060073 public const int design_snackbar_text_size = 2131099763; - + // aapt resource value: 0x7F060074 public const int design_tab_max_width = 2131099764; - + // aapt resource value: 0x7F060075 public const int design_tab_scrollable_min_width = 2131099765; - + // aapt resource value: 0x7F060076 public const int design_tab_text_size = 2131099766; - + // aapt resource value: 0x7F060077 public const int design_tab_text_size_2line = 2131099767; - + // aapt resource value: 0x7F060078 public const int disabled_alpha_material_dark = 2131099768; - + // aapt resource value: 0x7F060079 public const int disabled_alpha_material_light = 2131099769; - + // aapt resource value: 0x7F06007A public const int fastscroll_default_thickness = 2131099770; - + // aapt resource value: 0x7F06007B public const int fastscroll_margin = 2131099771; - + // aapt resource value: 0x7F06007C public const int fastscroll_minimum_range = 2131099772; - + // aapt resource value: 0x7F06007D public const int highlight_alpha_material_colored = 2131099773; - + // aapt resource value: 0x7F06007E public const int highlight_alpha_material_dark = 2131099774; - + // aapt resource value: 0x7F06007F public const int highlight_alpha_material_light = 2131099775; - + // aapt resource value: 0x7F060080 public const int hint_alpha_material_dark = 2131099776; - + // aapt resource value: 0x7F060081 public const int hint_alpha_material_light = 2131099777; - + // aapt resource value: 0x7F060082 public const int hint_pressed_alpha_material_dark = 2131099778; - + // aapt resource value: 0x7F060083 public const int hint_pressed_alpha_material_light = 2131099779; - + // aapt resource value: 0x7F060084 public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - + // aapt resource value: 0x7F060085 public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - + // aapt resource value: 0x7F060086 public const int item_touch_helper_swipe_escape_velocity = 2131099782; - + // aapt resource value: 0x7F060087 public const int mr_controller_volume_group_list_item_height = 2131099783; - + // aapt resource value: 0x7F060088 public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - + // aapt resource value: 0x7F060089 public const int mr_controller_volume_group_list_max_height = 2131099785; - + // aapt resource value: 0x7F06008A public const int mr_controller_volume_group_list_padding_top = 2131099786; - + // aapt resource value: 0x7F06008B public const int mr_dialog_fixed_width_major = 2131099787; - + // aapt resource value: 0x7F06008C public const int mr_dialog_fixed_width_minor = 2131099788; - + // aapt resource value: 0x7F06008D public const int notification_action_icon_size = 2131099789; - + // aapt resource value: 0x7F06008E public const int notification_action_text_size = 2131099790; - + // aapt resource value: 0x7F06008F public const int notification_big_circle_margin = 2131099791; - + // aapt resource value: 0x7F060090 public const int notification_content_margin_start = 2131099792; - + // aapt resource value: 0x7F060091 public const int notification_large_icon_height = 2131099793; - + // aapt resource value: 0x7F060092 public const int notification_large_icon_width = 2131099794; - + // aapt resource value: 0x7F060093 public const int notification_main_column_padding_top = 2131099795; - + // aapt resource value: 0x7F060094 public const int notification_media_narrow_margin = 2131099796; - + // aapt resource value: 0x7F060095 public const int notification_right_icon_size = 2131099797; - + // aapt resource value: 0x7F060096 public const int notification_right_side_padding_top = 2131099798; - + // aapt resource value: 0x7F060097 public const int notification_small_icon_background_padding = 2131099799; - + // aapt resource value: 0x7F060098 public const int notification_small_icon_size_as_large = 2131099800; - + // aapt resource value: 0x7F060099 public const int notification_subtext_size = 2131099801; - + // aapt resource value: 0x7F06009A public const int notification_top_pad = 2131099802; - + // aapt resource value: 0x7F06009B public const int notification_top_pad_large_text = 2131099803; - + // aapt resource value: 0x7F06009C public const int tooltip_corner_radius = 2131099804; - + // aapt resource value: 0x7F06009D public const int tooltip_horizontal_padding = 2131099805; - + // aapt resource value: 0x7F06009E public const int tooltip_margin = 2131099806; - + // aapt resource value: 0x7F06009F public const int tooltip_precise_anchor_extra_offset = 2131099807; - + // aapt resource value: 0x7F0600A0 public const int tooltip_precise_anchor_threshold = 2131099808; - + // aapt resource value: 0x7F0600A1 public const int tooltip_vertical_padding = 2131099809; - + // aapt resource value: 0x7F0600A2 public const int tooltip_y_offset_non_touch = 2131099810; - + // aapt resource value: 0x7F0600A3 public const int tooltip_y_offset_touch = 2131099811; - + static Dimension() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Dimension() { } } - + public partial class Drawable { - + // aapt resource value: 0x7F070006 public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - + // aapt resource value: 0x7F070007 public const int abc_action_bar_item_background_material = 2131165191; - + // aapt resource value: 0x7F070008 public const int abc_btn_borderless_material = 2131165192; - + // aapt resource value: 0x7F070009 public const int abc_btn_check_material = 2131165193; - + // aapt resource value: 0x7F07000A public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - + // aapt resource value: 0x7F07000B public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - + // aapt resource value: 0x7F07000C public const int abc_btn_colored_material = 2131165196; - + // aapt resource value: 0x7F07000D public const int abc_btn_default_mtrl_shape = 2131165197; - + // aapt resource value: 0x7F07000E public const int abc_btn_radio_material = 2131165198; - + // aapt resource value: 0x7F07000F public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - + // aapt resource value: 0x7F070010 public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - + // aapt resource value: 0x7F070011 public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - + // aapt resource value: 0x7F070012 public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - + // aapt resource value: 0x7F070013 public const int abc_cab_background_internal_bg = 2131165203; - + // aapt resource value: 0x7F070014 public const int abc_cab_background_top_material = 2131165204; - + // aapt resource value: 0x7F070015 public const int abc_cab_background_top_mtrl_alpha = 2131165205; - + // aapt resource value: 0x7F070016 public const int abc_control_background_material = 2131165206; - + // aapt resource value: 0x7F070017 public const int abc_dialog_material_background = 2131165207; - + // aapt resource value: 0x7F070018 public const int abc_edit_text_material = 2131165208; - + // aapt resource value: 0x7F070019 public const int abc_ic_ab_back_material = 2131165209; - + // aapt resource value: 0x7F07001A public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - + // aapt resource value: 0x7F07001B public const int abc_ic_clear_material = 2131165211; - + // aapt resource value: 0x7F07001C public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - + // aapt resource value: 0x7F07001D public const int abc_ic_go_search_api_material = 2131165213; - + // aapt resource value: 0x7F07001E public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - + // aapt resource value: 0x7F07001F public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - + // aapt resource value: 0x7F070020 public const int abc_ic_menu_overflow_material = 2131165216; - + // aapt resource value: 0x7F070021 public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - + // aapt resource value: 0x7F070022 public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - + // aapt resource value: 0x7F070023 public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - + // aapt resource value: 0x7F070024 public const int abc_ic_search_api_material = 2131165220; - + // aapt resource value: 0x7F070025 public const int abc_ic_star_black_16dp = 2131165221; - + // aapt resource value: 0x7F070026 public const int abc_ic_star_black_36dp = 2131165222; - + // aapt resource value: 0x7F070027 public const int abc_ic_star_black_48dp = 2131165223; - + // aapt resource value: 0x7F070028 public const int abc_ic_star_half_black_16dp = 2131165224; - + // aapt resource value: 0x7F070029 public const int abc_ic_star_half_black_36dp = 2131165225; - + // aapt resource value: 0x7F07002A public const int abc_ic_star_half_black_48dp = 2131165226; - + // aapt resource value: 0x7F07002B public const int abc_ic_voice_search_api_material = 2131165227; - + // aapt resource value: 0x7F07002C public const int abc_item_background_holo_dark = 2131165228; - + // aapt resource value: 0x7F07002D public const int abc_item_background_holo_light = 2131165229; - + // aapt resource value: 0x7F07002E public const int abc_list_divider_mtrl_alpha = 2131165230; - + // aapt resource value: 0x7F07002F public const int abc_list_focused_holo = 2131165231; - + // aapt resource value: 0x7F070030 public const int abc_list_longpressed_holo = 2131165232; - + // aapt resource value: 0x7F070031 public const int abc_list_pressed_holo_dark = 2131165233; - + // aapt resource value: 0x7F070032 public const int abc_list_pressed_holo_light = 2131165234; - + // aapt resource value: 0x7F070033 public const int abc_list_selector_background_transition_holo_dark = 2131165235; - + // aapt resource value: 0x7F070034 public const int abc_list_selector_background_transition_holo_light = 2131165236; - + // aapt resource value: 0x7F070035 public const int abc_list_selector_disabled_holo_dark = 2131165237; - + // aapt resource value: 0x7F070036 public const int abc_list_selector_disabled_holo_light = 2131165238; - + // aapt resource value: 0x7F070037 public const int abc_list_selector_holo_dark = 2131165239; - + // aapt resource value: 0x7F070038 public const int abc_list_selector_holo_light = 2131165240; - + // aapt resource value: 0x7F070039 public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - + // aapt resource value: 0x7F07003A public const int abc_popup_background_mtrl_mult = 2131165242; - + // aapt resource value: 0x7F07003B public const int abc_ratingbar_indicator_material = 2131165243; - + // aapt resource value: 0x7F07003C public const int abc_ratingbar_material = 2131165244; - + // aapt resource value: 0x7F07003D public const int abc_ratingbar_small_material = 2131165245; - + // aapt resource value: 0x7F07003E public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - + // aapt resource value: 0x7F07003F public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - + // aapt resource value: 0x7F070040 public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - + // aapt resource value: 0x7F070041 public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - + // aapt resource value: 0x7F070042 public const int abc_scrubber_track_mtrl_alpha = 2131165250; - + // aapt resource value: 0x7F070043 public const int abc_seekbar_thumb_material = 2131165251; - + // aapt resource value: 0x7F070044 public const int abc_seekbar_tick_mark_material = 2131165252; - + // aapt resource value: 0x7F070045 public const int abc_seekbar_track_material = 2131165253; - + // aapt resource value: 0x7F070046 public const int abc_spinner_mtrl_am_alpha = 2131165254; - + // aapt resource value: 0x7F070047 public const int abc_spinner_textfield_background_material = 2131165255; - + // aapt resource value: 0x7F070048 public const int abc_switch_thumb_material = 2131165256; - + // aapt resource value: 0x7F070049 public const int abc_switch_track_mtrl_alpha = 2131165257; - + // aapt resource value: 0x7F07004A public const int abc_tab_indicator_material = 2131165258; - + // aapt resource value: 0x7F07004B public const int abc_tab_indicator_mtrl_alpha = 2131165259; - + // aapt resource value: 0x7F070053 public const int abc_textfield_activated_mtrl_alpha = 2131165267; - + // aapt resource value: 0x7F070054 public const int abc_textfield_default_mtrl_alpha = 2131165268; - + // aapt resource value: 0x7F070055 public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - + // aapt resource value: 0x7F070056 public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - + // aapt resource value: 0x7F070057 public const int abc_textfield_search_material = 2131165271; - + // aapt resource value: 0x7F07004C public const int abc_text_cursor_material = 2131165260; - + // aapt resource value: 0x7F07004D public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - + // aapt resource value: 0x7F07004E public const int abc_text_select_handle_left_mtrl_light = 2131165262; - + // aapt resource value: 0x7F07004F public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - + // aapt resource value: 0x7F070050 public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - + // aapt resource value: 0x7F070051 public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - + // aapt resource value: 0x7F070052 public const int abc_text_select_handle_right_mtrl_light = 2131165266; - + // aapt resource value: 0x7F070058 public const int abc_vector_test = 2131165272; - + // aapt resource value: 0x7F070059 public const int avd_hide_password = 2131165273; - + // aapt resource value: 0x7F07005A public const int avd_show_password = 2131165274; - + // aapt resource value: 0x7F07005B public const int design_bottom_navigation_item_background = 2131165275; - + // aapt resource value: 0x7F07005C public const int design_fab_background = 2131165276; - + // aapt resource value: 0x7F07005D public const int design_ic_visibility = 2131165277; - + // aapt resource value: 0x7F07005E public const int design_ic_visibility_off = 2131165278; - + // aapt resource value: 0x7F07005F public const int design_password_eye = 2131165279; - + // aapt resource value: 0x7F070060 public const int design_snackbar_background = 2131165280; - + // aapt resource value: 0x7F070061 public const int ic_audiotrack_dark = 2131165281; - + // aapt resource value: 0x7F070062 public const int ic_audiotrack_light = 2131165282; - + // aapt resource value: 0x7F070063 public const int ic_dialog_close_dark = 2131165283; - + // aapt resource value: 0x7F070064 public const int ic_dialog_close_light = 2131165284; - + // aapt resource value: 0x7F070065 public const int ic_group_collapse_00 = 2131165285; - + // aapt resource value: 0x7F070066 public const int ic_group_collapse_01 = 2131165286; - + // aapt resource value: 0x7F070067 public const int ic_group_collapse_02 = 2131165287; - + // aapt resource value: 0x7F070068 public const int ic_group_collapse_03 = 2131165288; - + // aapt resource value: 0x7F070069 public const int ic_group_collapse_04 = 2131165289; - + // aapt resource value: 0x7F07006A public const int ic_group_collapse_05 = 2131165290; - + // aapt resource value: 0x7F07006B public const int ic_group_collapse_06 = 2131165291; - + // aapt resource value: 0x7F07006C public const int ic_group_collapse_07 = 2131165292; - + // aapt resource value: 0x7F07006D public const int ic_group_collapse_08 = 2131165293; - + // aapt resource value: 0x7F07006E public const int ic_group_collapse_09 = 2131165294; - + // aapt resource value: 0x7F07006F public const int ic_group_collapse_10 = 2131165295; - + // aapt resource value: 0x7F070070 public const int ic_group_collapse_11 = 2131165296; - + // aapt resource value: 0x7F070071 public const int ic_group_collapse_12 = 2131165297; - + // aapt resource value: 0x7F070072 public const int ic_group_collapse_13 = 2131165298; - + // aapt resource value: 0x7F070073 public const int ic_group_collapse_14 = 2131165299; - + // aapt resource value: 0x7F070074 public const int ic_group_collapse_15 = 2131165300; - + // aapt resource value: 0x7F070075 public const int ic_group_expand_00 = 2131165301; - + // aapt resource value: 0x7F070076 public const int ic_group_expand_01 = 2131165302; - + // aapt resource value: 0x7F070077 public const int ic_group_expand_02 = 2131165303; - + // aapt resource value: 0x7F070078 public const int ic_group_expand_03 = 2131165304; - + // aapt resource value: 0x7F070079 public const int ic_group_expand_04 = 2131165305; - + // aapt resource value: 0x7F07007A public const int ic_group_expand_05 = 2131165306; - + // aapt resource value: 0x7F07007B public const int ic_group_expand_06 = 2131165307; - + // aapt resource value: 0x7F07007C public const int ic_group_expand_07 = 2131165308; - + // aapt resource value: 0x7F07007D public const int ic_group_expand_08 = 2131165309; - + // aapt resource value: 0x7F07007E public const int ic_group_expand_09 = 2131165310; - + // aapt resource value: 0x7F07007F public const int ic_group_expand_10 = 2131165311; - + // aapt resource value: 0x7F070080 public const int ic_group_expand_11 = 2131165312; - + // aapt resource value: 0x7F070081 public const int ic_group_expand_12 = 2131165313; - + // aapt resource value: 0x7F070082 public const int ic_group_expand_13 = 2131165314; - + // aapt resource value: 0x7F070083 public const int ic_group_expand_14 = 2131165315; - + // aapt resource value: 0x7F070084 public const int ic_group_expand_15 = 2131165316; - + // aapt resource value: 0x7F070085 public const int ic_media_pause_dark = 2131165317; - + // aapt resource value: 0x7F070086 public const int ic_media_pause_light = 2131165318; - + // aapt resource value: 0x7F070087 public const int ic_media_play_dark = 2131165319; - + // aapt resource value: 0x7F070088 public const int ic_media_play_light = 2131165320; - + // aapt resource value: 0x7F070089 public const int ic_media_stop_dark = 2131165321; - + // aapt resource value: 0x7F07008A public const int ic_media_stop_light = 2131165322; - + // aapt resource value: 0x7F07008B public const int ic_mr_button_connected_00_dark = 2131165323; - + // aapt resource value: 0x7F07008C public const int ic_mr_button_connected_00_light = 2131165324; - + // aapt resource value: 0x7F07008D public const int ic_mr_button_connected_01_dark = 2131165325; - + // aapt resource value: 0x7F07008E public const int ic_mr_button_connected_01_light = 2131165326; - + // aapt resource value: 0x7F07008F public const int ic_mr_button_connected_02_dark = 2131165327; - + // aapt resource value: 0x7F070090 public const int ic_mr_button_connected_02_light = 2131165328; - + // aapt resource value: 0x7F070091 public const int ic_mr_button_connected_03_dark = 2131165329; - + // aapt resource value: 0x7F070092 public const int ic_mr_button_connected_03_light = 2131165330; - + // aapt resource value: 0x7F070093 public const int ic_mr_button_connected_04_dark = 2131165331; - + // aapt resource value: 0x7F070094 public const int ic_mr_button_connected_04_light = 2131165332; - + // aapt resource value: 0x7F070095 public const int ic_mr_button_connected_05_dark = 2131165333; - + // aapt resource value: 0x7F070096 public const int ic_mr_button_connected_05_light = 2131165334; - + // aapt resource value: 0x7F070097 public const int ic_mr_button_connected_06_dark = 2131165335; - + // aapt resource value: 0x7F070098 public const int ic_mr_button_connected_06_light = 2131165336; - + // aapt resource value: 0x7F070099 public const int ic_mr_button_connected_07_dark = 2131165337; - + // aapt resource value: 0x7F07009A public const int ic_mr_button_connected_07_light = 2131165338; - + // aapt resource value: 0x7F07009B public const int ic_mr_button_connected_08_dark = 2131165339; - + // aapt resource value: 0x7F07009C public const int ic_mr_button_connected_08_light = 2131165340; - + // aapt resource value: 0x7F07009D public const int ic_mr_button_connected_09_dark = 2131165341; - + // aapt resource value: 0x7F07009E public const int ic_mr_button_connected_09_light = 2131165342; - + // aapt resource value: 0x7F07009F public const int ic_mr_button_connected_10_dark = 2131165343; - + // aapt resource value: 0x7F0700A0 public const int ic_mr_button_connected_10_light = 2131165344; - + // aapt resource value: 0x7F0700A1 public const int ic_mr_button_connected_11_dark = 2131165345; - + // aapt resource value: 0x7F0700A2 public const int ic_mr_button_connected_11_light = 2131165346; - + // aapt resource value: 0x7F0700A3 public const int ic_mr_button_connected_12_dark = 2131165347; - + // aapt resource value: 0x7F0700A4 public const int ic_mr_button_connected_12_light = 2131165348; - + // aapt resource value: 0x7F0700A5 public const int ic_mr_button_connected_13_dark = 2131165349; - + // aapt resource value: 0x7F0700A6 public const int ic_mr_button_connected_13_light = 2131165350; - + // aapt resource value: 0x7F0700A7 public const int ic_mr_button_connected_14_dark = 2131165351; - + // aapt resource value: 0x7F0700A8 public const int ic_mr_button_connected_14_light = 2131165352; - + // aapt resource value: 0x7F0700A9 public const int ic_mr_button_connected_15_dark = 2131165353; - + // aapt resource value: 0x7F0700AA public const int ic_mr_button_connected_15_light = 2131165354; - + // aapt resource value: 0x7F0700AB public const int ic_mr_button_connected_16_dark = 2131165355; - + // aapt resource value: 0x7F0700AC public const int ic_mr_button_connected_16_light = 2131165356; - + // aapt resource value: 0x7F0700AD public const int ic_mr_button_connected_17_dark = 2131165357; - + // aapt resource value: 0x7F0700AE public const int ic_mr_button_connected_17_light = 2131165358; - + // aapt resource value: 0x7F0700AF public const int ic_mr_button_connected_18_dark = 2131165359; - + // aapt resource value: 0x7F0700B0 public const int ic_mr_button_connected_18_light = 2131165360; - + // aapt resource value: 0x7F0700B1 public const int ic_mr_button_connected_19_dark = 2131165361; - + // aapt resource value: 0x7F0700B2 public const int ic_mr_button_connected_19_light = 2131165362; - + // aapt resource value: 0x7F0700B3 public const int ic_mr_button_connected_20_dark = 2131165363; - + // aapt resource value: 0x7F0700B4 public const int ic_mr_button_connected_20_light = 2131165364; - + // aapt resource value: 0x7F0700B5 public const int ic_mr_button_connected_21_dark = 2131165365; - + // aapt resource value: 0x7F0700B6 public const int ic_mr_button_connected_21_light = 2131165366; - + // aapt resource value: 0x7F0700B7 public const int ic_mr_button_connected_22_dark = 2131165367; - + // aapt resource value: 0x7F0700B8 public const int ic_mr_button_connected_22_light = 2131165368; - + // aapt resource value: 0x7F0700B9 public const int ic_mr_button_connected_23_dark = 2131165369; - + // aapt resource value: 0x7F0700BA public const int ic_mr_button_connected_23_light = 2131165370; - + // aapt resource value: 0x7F0700BB public const int ic_mr_button_connected_24_dark = 2131165371; - + // aapt resource value: 0x7F0700BC public const int ic_mr_button_connected_24_light = 2131165372; - + // aapt resource value: 0x7F0700BD public const int ic_mr_button_connected_25_dark = 2131165373; - + // aapt resource value: 0x7F0700BE public const int ic_mr_button_connected_25_light = 2131165374; - + // aapt resource value: 0x7F0700BF public const int ic_mr_button_connected_26_dark = 2131165375; - + // aapt resource value: 0x7F0700C0 public const int ic_mr_button_connected_26_light = 2131165376; - + // aapt resource value: 0x7F0700C1 public const int ic_mr_button_connected_27_dark = 2131165377; - + // aapt resource value: 0x7F0700C2 public const int ic_mr_button_connected_27_light = 2131165378; - + // aapt resource value: 0x7F0700C3 public const int ic_mr_button_connected_28_dark = 2131165379; - + // aapt resource value: 0x7F0700C4 public const int ic_mr_button_connected_28_light = 2131165380; - + // aapt resource value: 0x7F0700C5 public const int ic_mr_button_connected_29_dark = 2131165381; - + // aapt resource value: 0x7F0700C6 public const int ic_mr_button_connected_29_light = 2131165382; - + // aapt resource value: 0x7F0700C7 public const int ic_mr_button_connected_30_dark = 2131165383; - + // aapt resource value: 0x7F0700C8 public const int ic_mr_button_connected_30_light = 2131165384; - + // aapt resource value: 0x7F0700C9 public const int ic_mr_button_connecting_00_dark = 2131165385; - + // aapt resource value: 0x7F0700CA public const int ic_mr_button_connecting_00_light = 2131165386; - + // aapt resource value: 0x7F0700CB public const int ic_mr_button_connecting_01_dark = 2131165387; - + // aapt resource value: 0x7F0700CC public const int ic_mr_button_connecting_01_light = 2131165388; - + // aapt resource value: 0x7F0700CD public const int ic_mr_button_connecting_02_dark = 2131165389; - + // aapt resource value: 0x7F0700CE public const int ic_mr_button_connecting_02_light = 2131165390; - + // aapt resource value: 0x7F0700CF public const int ic_mr_button_connecting_03_dark = 2131165391; - + // aapt resource value: 0x7F0700D0 public const int ic_mr_button_connecting_03_light = 2131165392; - + // aapt resource value: 0x7F0700D1 public const int ic_mr_button_connecting_04_dark = 2131165393; - + // aapt resource value: 0x7F0700D2 public const int ic_mr_button_connecting_04_light = 2131165394; - + // aapt resource value: 0x7F0700D3 public const int ic_mr_button_connecting_05_dark = 2131165395; - + // aapt resource value: 0x7F0700D4 public const int ic_mr_button_connecting_05_light = 2131165396; - + // aapt resource value: 0x7F0700D5 public const int ic_mr_button_connecting_06_dark = 2131165397; - + // aapt resource value: 0x7F0700D6 public const int ic_mr_button_connecting_06_light = 2131165398; - + // aapt resource value: 0x7F0700D7 public const int ic_mr_button_connecting_07_dark = 2131165399; - + // aapt resource value: 0x7F0700D8 public const int ic_mr_button_connecting_07_light = 2131165400; - + // aapt resource value: 0x7F0700D9 public const int ic_mr_button_connecting_08_dark = 2131165401; - + // aapt resource value: 0x7F0700DA public const int ic_mr_button_connecting_08_light = 2131165402; - + // aapt resource value: 0x7F0700DB public const int ic_mr_button_connecting_09_dark = 2131165403; - + // aapt resource value: 0x7F0700DC public const int ic_mr_button_connecting_09_light = 2131165404; - + // aapt resource value: 0x7F0700DD public const int ic_mr_button_connecting_10_dark = 2131165405; - + // aapt resource value: 0x7F0700DE public const int ic_mr_button_connecting_10_light = 2131165406; - + // aapt resource value: 0x7F0700DF public const int ic_mr_button_connecting_11_dark = 2131165407; - + // aapt resource value: 0x7F0700E0 public const int ic_mr_button_connecting_11_light = 2131165408; - + // aapt resource value: 0x7F0700E1 public const int ic_mr_button_connecting_12_dark = 2131165409; - + // aapt resource value: 0x7F0700E2 public const int ic_mr_button_connecting_12_light = 2131165410; - + // aapt resource value: 0x7F0700E3 public const int ic_mr_button_connecting_13_dark = 2131165411; - + // aapt resource value: 0x7F0700E4 public const int ic_mr_button_connecting_13_light = 2131165412; - + // aapt resource value: 0x7F0700E5 public const int ic_mr_button_connecting_14_dark = 2131165413; - + // aapt resource value: 0x7F0700E6 public const int ic_mr_button_connecting_14_light = 2131165414; - + // aapt resource value: 0x7F0700E7 public const int ic_mr_button_connecting_15_dark = 2131165415; - + // aapt resource value: 0x7F0700E8 public const int ic_mr_button_connecting_15_light = 2131165416; - + // aapt resource value: 0x7F0700E9 public const int ic_mr_button_connecting_16_dark = 2131165417; - + // aapt resource value: 0x7F0700EA public const int ic_mr_button_connecting_16_light = 2131165418; - + // aapt resource value: 0x7F0700EB public const int ic_mr_button_connecting_17_dark = 2131165419; - + // aapt resource value: 0x7F0700EC public const int ic_mr_button_connecting_17_light = 2131165420; - + // aapt resource value: 0x7F0700ED public const int ic_mr_button_connecting_18_dark = 2131165421; - + // aapt resource value: 0x7F0700EE public const int ic_mr_button_connecting_18_light = 2131165422; - + // aapt resource value: 0x7F0700EF public const int ic_mr_button_connecting_19_dark = 2131165423; - + // aapt resource value: 0x7F0700F0 public const int ic_mr_button_connecting_19_light = 2131165424; - + // aapt resource value: 0x7F0700F1 public const int ic_mr_button_connecting_20_dark = 2131165425; - + // aapt resource value: 0x7F0700F2 public const int ic_mr_button_connecting_20_light = 2131165426; - + // aapt resource value: 0x7F0700F3 public const int ic_mr_button_connecting_21_dark = 2131165427; - + // aapt resource value: 0x7F0700F4 public const int ic_mr_button_connecting_21_light = 2131165428; - + // aapt resource value: 0x7F0700F5 public const int ic_mr_button_connecting_22_dark = 2131165429; - + // aapt resource value: 0x7F0700F6 public const int ic_mr_button_connecting_22_light = 2131165430; - + // aapt resource value: 0x7F0700F7 public const int ic_mr_button_connecting_23_dark = 2131165431; - + // aapt resource value: 0x7F0700F8 public const int ic_mr_button_connecting_23_light = 2131165432; - + // aapt resource value: 0x7F0700F9 public const int ic_mr_button_connecting_24_dark = 2131165433; - + // aapt resource value: 0x7F0700FA public const int ic_mr_button_connecting_24_light = 2131165434; - + // aapt resource value: 0x7F0700FB public const int ic_mr_button_connecting_25_dark = 2131165435; - + // aapt resource value: 0x7F0700FC public const int ic_mr_button_connecting_25_light = 2131165436; - + // aapt resource value: 0x7F0700FD public const int ic_mr_button_connecting_26_dark = 2131165437; - + // aapt resource value: 0x7F0700FE public const int ic_mr_button_connecting_26_light = 2131165438; - + // aapt resource value: 0x7F0700FF public const int ic_mr_button_connecting_27_dark = 2131165439; - + // aapt resource value: 0x7F070100 public const int ic_mr_button_connecting_27_light = 2131165440; - + // aapt resource value: 0x7F070101 public const int ic_mr_button_connecting_28_dark = 2131165441; - + // aapt resource value: 0x7F070102 public const int ic_mr_button_connecting_28_light = 2131165442; - + // aapt resource value: 0x7F070103 public const int ic_mr_button_connecting_29_dark = 2131165443; - + // aapt resource value: 0x7F070104 public const int ic_mr_button_connecting_29_light = 2131165444; - + // aapt resource value: 0x7F070105 public const int ic_mr_button_connecting_30_dark = 2131165445; - + // aapt resource value: 0x7F070106 public const int ic_mr_button_connecting_30_light = 2131165446; - + // aapt resource value: 0x7F070107 public const int ic_mr_button_disabled_dark = 2131165447; - + // aapt resource value: 0x7F070108 public const int ic_mr_button_disabled_light = 2131165448; - + // aapt resource value: 0x7F070109 public const int ic_mr_button_disconnected_dark = 2131165449; - + // aapt resource value: 0x7F07010A public const int ic_mr_button_disconnected_light = 2131165450; - + // aapt resource value: 0x7F07010B public const int ic_mr_button_grey = 2131165451; - + // aapt resource value: 0x7F07010C public const int ic_vol_type_speaker_dark = 2131165452; - + // aapt resource value: 0x7F07010D public const int ic_vol_type_speaker_group_dark = 2131165453; - + // aapt resource value: 0x7F07010E public const int ic_vol_type_speaker_group_light = 2131165454; - + // aapt resource value: 0x7F07010F public const int ic_vol_type_speaker_light = 2131165455; - + // aapt resource value: 0x7F070110 public const int ic_vol_type_tv_dark = 2131165456; - + // aapt resource value: 0x7F070111 public const int ic_vol_type_tv_light = 2131165457; - + // aapt resource value: 0x7F070112 public const int mr_button_connected_dark = 2131165458; - + // aapt resource value: 0x7F070113 public const int mr_button_connected_light = 2131165459; - + // aapt resource value: 0x7F070114 public const int mr_button_connecting_dark = 2131165460; - + // aapt resource value: 0x7F070115 public const int mr_button_connecting_light = 2131165461; - + // aapt resource value: 0x7F070116 public const int mr_button_dark = 2131165462; - + // aapt resource value: 0x7F070117 public const int mr_button_light = 2131165463; - + // aapt resource value: 0x7F070118 public const int mr_dialog_close_dark = 2131165464; - + // aapt resource value: 0x7F070119 public const int mr_dialog_close_light = 2131165465; - + // aapt resource value: 0x7F07011A public const int mr_dialog_material_background_dark = 2131165466; - + // aapt resource value: 0x7F07011B public const int mr_dialog_material_background_light = 2131165467; - + // aapt resource value: 0x7F07011C public const int mr_group_collapse = 2131165468; - + // aapt resource value: 0x7F07011D public const int mr_group_expand = 2131165469; - + // aapt resource value: 0x7F07011E public const int mr_media_pause_dark = 2131165470; - + // aapt resource value: 0x7F07011F public const int mr_media_pause_light = 2131165471; - + // aapt resource value: 0x7F070120 public const int mr_media_play_dark = 2131165472; - + // aapt resource value: 0x7F070121 public const int mr_media_play_light = 2131165473; - + // aapt resource value: 0x7F070122 public const int mr_media_stop_dark = 2131165474; - + // aapt resource value: 0x7F070123 public const int mr_media_stop_light = 2131165475; - + // aapt resource value: 0x7F070124 public const int mr_vol_type_audiotrack_dark = 2131165476; - + // aapt resource value: 0x7F070125 public const int mr_vol_type_audiotrack_light = 2131165477; - + // aapt resource value: 0x7F070126 public const int navigation_empty_icon = 2131165478; - + // aapt resource value: 0x7F070127 public const int notification_action_background = 2131165479; - + // aapt resource value: 0x7F070128 public const int notification_bg = 2131165480; - + // aapt resource value: 0x7F070129 public const int notification_bg_low = 2131165481; - + // aapt resource value: 0x7F07012A public const int notification_bg_low_normal = 2131165482; - + // aapt resource value: 0x7F07012B public const int notification_bg_low_pressed = 2131165483; - + // aapt resource value: 0x7F07012C public const int notification_bg_normal = 2131165484; - + // aapt resource value: 0x7F07012D public const int notification_bg_normal_pressed = 2131165485; - + // aapt resource value: 0x7F07012E public const int notification_icon_background = 2131165486; - + // aapt resource value: 0x7F07012F public const int notification_template_icon_bg = 2131165487; - + // aapt resource value: 0x7F070130 public const int notification_template_icon_low_bg = 2131165488; - + // aapt resource value: 0x7F070131 public const int notification_tile_bg = 2131165489; - + // aapt resource value: 0x7F070132 public const int notify_panel_notification_icon_bg = 2131165490; - + // aapt resource value: 0x7F070133 public const int tooltip_frame_dark = 2131165491; - + // aapt resource value: 0x7F070134 public const int tooltip_frame_light = 2131165492; - + static Drawable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Drawable() { } } - + public partial class Id { - + // aapt resource value: 0x7F080006 public const int action0 = 2131230726; - + // aapt resource value: 0x7F080018 public const int actions = 2131230744; - + // aapt resource value: 0x7F080007 public const int action_bar = 2131230727; - + // aapt resource value: 0x7F080008 public const int action_bar_activity_content = 2131230728; - + // aapt resource value: 0x7F080009 public const int action_bar_container = 2131230729; - + // aapt resource value: 0x7F08000A public const int action_bar_root = 2131230730; - + // aapt resource value: 0x7F08000B public const int action_bar_spinner = 2131230731; - + // aapt resource value: 0x7F08000C public const int action_bar_subtitle = 2131230732; - + // aapt resource value: 0x7F08000D public const int action_bar_title = 2131230733; - + // aapt resource value: 0x7F08000E public const int action_container = 2131230734; - + // aapt resource value: 0x7F08000F public const int action_context_bar = 2131230735; - + // aapt resource value: 0x7F080010 public const int action_divider = 2131230736; - + // aapt resource value: 0x7F080011 public const int action_image = 2131230737; - + // aapt resource value: 0x7F080012 public const int action_menu_divider = 2131230738; - + // aapt resource value: 0x7F080013 public const int action_menu_presenter = 2131230739; - + // aapt resource value: 0x7F080014 public const int action_mode_bar = 2131230740; - + // aapt resource value: 0x7F080015 public const int action_mode_bar_stub = 2131230741; - + // aapt resource value: 0x7F080016 public const int action_mode_close_button = 2131230742; - + // aapt resource value: 0x7F080017 public const int action_text = 2131230743; - + // aapt resource value: 0x7F080019 public const int activity_chooser_view_content = 2131230745; - + // aapt resource value: 0x7F08001A public const int add = 2131230746; - + // aapt resource value: 0x7F08001B public const int alertTitle = 2131230747; - + // aapt resource value: 0x7F08001C public const int all = 2131230748; - + // aapt resource value: 0x7F080000 public const int ALT = 2131230720; - + // aapt resource value: 0x7F08001D public const int always = 2131230749; - + // aapt resource value: 0x7F08001E public const int async = 2131230750; - + // aapt resource value: 0x7F08001F public const int auto = 2131230751; - + // aapt resource value: 0x7F080020 public const int beginning = 2131230752; - + // aapt resource value: 0x7F080021 public const int blocking = 2131230753; - + // aapt resource value: 0x7F080022 public const int bottom = 2131230754; - + // aapt resource value: 0x7F080023 public const int bottomtab_navarea = 2131230755; - + // aapt resource value: 0x7F080024 public const int bottomtab_tabbar = 2131230756; - + // aapt resource value: 0x7F080025 public const int buttonPanel = 2131230757; - + // aapt resource value: 0x7F080026 public const int cancel_action = 2131230758; - + // aapt resource value: 0x7F080027 public const int center = 2131230759; - + // aapt resource value: 0x7F080028 public const int center_horizontal = 2131230760; - + // aapt resource value: 0x7F080029 public const int center_vertical = 2131230761; - + // aapt resource value: 0x7F08002A public const int checkbox = 2131230762; - + // aapt resource value: 0x7F08002B public const int chronometer = 2131230763; - + // aapt resource value: 0x7F08002C public const int clip_horizontal = 2131230764; - + // aapt resource value: 0x7F08002D public const int clip_vertical = 2131230765; - + // aapt resource value: 0x7F08002E public const int collapseActionView = 2131230766; - + // aapt resource value: 0x7F08002F public const int container = 2131230767; - + // aapt resource value: 0x7F080030 public const int contentPanel = 2131230768; - + // aapt resource value: 0x7F080031 public const int coordinator = 2131230769; - + // aapt resource value: 0x7F080001 public const int CTRL = 2131230721; - + // aapt resource value: 0x7F080032 public const int custom = 2131230770; - + // aapt resource value: 0x7F080033 public const int customPanel = 2131230771; - + // aapt resource value: 0x7F080034 public const int decor_content_parent = 2131230772; - + // aapt resource value: 0x7F080035 public const int default_activity_button = 2131230773; - + // aapt resource value: 0x7F080036 public const int design_bottom_sheet = 2131230774; - + // aapt resource value: 0x7F080037 public const int design_menu_item_action_area = 2131230775; - + // aapt resource value: 0x7F080038 public const int design_menu_item_action_area_stub = 2131230776; - + // aapt resource value: 0x7F080039 public const int design_menu_item_text = 2131230777; - + // aapt resource value: 0x7F08003A public const int design_navigation_view = 2131230778; - + // aapt resource value: 0x7F08003B public const int disableHome = 2131230779; - + // aapt resource value: 0x7F08003C public const int edit_query = 2131230780; - + // aapt resource value: 0x7F08003D public const int end = 2131230781; - + // aapt resource value: 0x7F08003E public const int end_padder = 2131230782; - + // aapt resource value: 0x7F08003F public const int enterAlways = 2131230783; - + // aapt resource value: 0x7F080040 public const int enterAlwaysCollapsed = 2131230784; - + // aapt resource value: 0x7F080041 public const int exitUntilCollapsed = 2131230785; - + // aapt resource value: 0x7F080043 public const int expanded_menu = 2131230787; - + // aapt resource value: 0x7F080042 public const int expand_activities_button = 2131230786; - + // aapt resource value: 0x7F080044 public const int fill = 2131230788; - + // aapt resource value: 0x7F080045 public const int fill_horizontal = 2131230789; - + // aapt resource value: 0x7F080046 public const int fill_vertical = 2131230790; - + // aapt resource value: 0x7F080047 public const int @fixed = 2131230791; - + // aapt resource value: 0x7F080048 public const int flyoutcontent_appbar = 2131230792; - + // aapt resource value: 0x7F080049 public const int flyoutcontent_recycler = 2131230793; - + // aapt resource value: 0x7F08004A public const int forever = 2131230794; - + // aapt resource value: 0x7F080002 public const int FUNCTION = 2131230722; - + // aapt resource value: 0x7F08004B public const int ghost_view = 2131230795; - + // aapt resource value: 0x7F08004C public const int home = 2131230796; - + // aapt resource value: 0x7F08004D public const int homeAsUp = 2131230797; - + // aapt resource value: 0x7F08004E public const int icon = 2131230798; - + // aapt resource value: 0x7F08004F public const int icon_group = 2131230799; - + // aapt resource value: 0x7F080050 public const int ifRoom = 2131230800; - + // aapt resource value: 0x7F080051 public const int image = 2131230801; - + // aapt resource value: 0x7F080052 public const int info = 2131230802; - + // aapt resource value: 0x7F080053 public const int italic = 2131230803; - + // aapt resource value: 0x7F080054 public const int item_touch_helper_previous_elevation = 2131230804; - + // aapt resource value: 0x7F080055 public const int largeLabel = 2131230805; - + // aapt resource value: 0x7F080056 public const int left = 2131230806; - + // aapt resource value: 0x7F080057 public const int line1 = 2131230807; - + // aapt resource value: 0x7F080058 public const int line3 = 2131230808; - + // aapt resource value: 0x7F080059 public const int listMode = 2131230809; - + // aapt resource value: 0x7F08005A public const int list_item = 2131230810; - + // aapt resource value: 0x7F08005B public const int main_appbar = 2131230811; - + // aapt resource value: 0x7F08005C public const int main_scrollview = 2131230812; - + // aapt resource value: 0x7F08005D public const int main_tablayout = 2131230813; - + // aapt resource value: 0x7F08005E public const int main_toolbar = 2131230814; - + // aapt resource value: 0x7F08005F public const int masked = 2131230815; - + // aapt resource value: 0x7F080060 public const int media_actions = 2131230816; - + // aapt resource value: 0x7F080061 public const int message = 2131230817; - + // aapt resource value: 0x7F080003 public const int META = 2131230723; - + // aapt resource value: 0x7F080062 public const int middle = 2131230818; - + // aapt resource value: 0x7F080063 public const int mini = 2131230819; - + // aapt resource value: 0x7F080064 public const int mr_art = 2131230820; - + // aapt resource value: 0x7F080065 public const int mr_chooser_list = 2131230821; - + // aapt resource value: 0x7F080066 public const int mr_chooser_route_desc = 2131230822; - + // aapt resource value: 0x7F080067 public const int mr_chooser_route_icon = 2131230823; - + // aapt resource value: 0x7F080068 public const int mr_chooser_route_name = 2131230824; - + // aapt resource value: 0x7F080069 public const int mr_chooser_title = 2131230825; - + // aapt resource value: 0x7F08006A public const int mr_close = 2131230826; - + // aapt resource value: 0x7F08006B public const int mr_control_divider = 2131230827; - + // aapt resource value: 0x7F08006C public const int mr_control_playback_ctrl = 2131230828; - + // aapt resource value: 0x7F08006D public const int mr_control_subtitle = 2131230829; - + // aapt resource value: 0x7F08006E public const int mr_control_title = 2131230830; - + // aapt resource value: 0x7F08006F public const int mr_control_title_container = 2131230831; - + // aapt resource value: 0x7F080070 public const int mr_custom_control = 2131230832; - + // aapt resource value: 0x7F080071 public const int mr_default_control = 2131230833; - + // aapt resource value: 0x7F080072 public const int mr_dialog_area = 2131230834; - + // aapt resource value: 0x7F080073 public const int mr_expandable_area = 2131230835; - + // aapt resource value: 0x7F080074 public const int mr_group_expand_collapse = 2131230836; - + // aapt resource value: 0x7F080075 public const int mr_media_main_control = 2131230837; - + // aapt resource value: 0x7F080076 public const int mr_name = 2131230838; - + // aapt resource value: 0x7F080077 public const int mr_playback_control = 2131230839; - + // aapt resource value: 0x7F080078 public const int mr_title_bar = 2131230840; - + // aapt resource value: 0x7F080079 public const int mr_volume_control = 2131230841; - + // aapt resource value: 0x7F08007A public const int mr_volume_group_list = 2131230842; - + // aapt resource value: 0x7F08007B public const int mr_volume_item_icon = 2131230843; - + // aapt resource value: 0x7F08007C public const int mr_volume_slider = 2131230844; - + // aapt resource value: 0x7F08007D public const int multiply = 2131230845; - + // aapt resource value: 0x7F08007E public const int navigation_header_container = 2131230846; - + // aapt resource value: 0x7F08007F public const int never = 2131230847; - + // aapt resource value: 0x7F080080 public const int none = 2131230848; - + // aapt resource value: 0x7F080081 public const int normal = 2131230849; - + // aapt resource value: 0x7F080082 public const int notification_background = 2131230850; - + // aapt resource value: 0x7F080083 public const int notification_main_column = 2131230851; - + // aapt resource value: 0x7F080084 public const int notification_main_column_container = 2131230852; - + // aapt resource value: 0x7F080085 public const int parallax = 2131230853; - + // aapt resource value: 0x7F080086 public const int parentPanel = 2131230854; - + // aapt resource value: 0x7F080087 public const int parent_matrix = 2131230855; - + // aapt resource value: 0x7F080088 public const int pin = 2131230856; - + // aapt resource value: 0x7F080089 public const int progress_circular = 2131230857; - + // aapt resource value: 0x7F08008A public const int progress_horizontal = 2131230858; - + // aapt resource value: 0x7F08008B public const int radio = 2131230859; - + // aapt resource value: 0x7F08008C public const int right = 2131230860; - + // aapt resource value: 0x7F08008D public const int right_icon = 2131230861; - + // aapt resource value: 0x7F08008E public const int right_side = 2131230862; - + // aapt resource value: 0x7F08008F public const int save_image_matrix = 2131230863; - + // aapt resource value: 0x7F080090 public const int save_non_transition_alpha = 2131230864; - + // aapt resource value: 0x7F080091 public const int save_scale_type = 2131230865; - + // aapt resource value: 0x7F080092 public const int screen = 2131230866; - + // aapt resource value: 0x7F080093 public const int scroll = 2131230867; - + // aapt resource value: 0x7F080097 public const int scrollable = 2131230871; - + // aapt resource value: 0x7F080094 public const int scrollIndicatorDown = 2131230868; - + // aapt resource value: 0x7F080095 public const int scrollIndicatorUp = 2131230869; - + // aapt resource value: 0x7F080096 public const int scrollView = 2131230870; - + // aapt resource value: 0x7F080098 public const int search_badge = 2131230872; - + // aapt resource value: 0x7F080099 public const int search_bar = 2131230873; - + // aapt resource value: 0x7F08009A public const int search_button = 2131230874; - + // aapt resource value: 0x7F08009B public const int search_close_btn = 2131230875; - + // aapt resource value: 0x7F08009C public const int search_edit_frame = 2131230876; - + // aapt resource value: 0x7F08009D public const int search_go_btn = 2131230877; - + // aapt resource value: 0x7F08009E public const int search_mag_icon = 2131230878; - + // aapt resource value: 0x7F08009F public const int search_plate = 2131230879; - + // aapt resource value: 0x7F0800A0 public const int search_src_text = 2131230880; - + // aapt resource value: 0x7F0800A1 public const int search_voice_btn = 2131230881; - + // aapt resource value: 0x7F0800A2 public const int select_dialog_listview = 2131230882; - + // aapt resource value: 0x7F0800A3 public const int shellcontent_appbar = 2131230883; - + // aapt resource value: 0x7F0800A4 public const int shellcontent_scrollview = 2131230884; - + // aapt resource value: 0x7F0800A5 public const int shellcontent_toolbar = 2131230885; - + // aapt resource value: 0x7F080004 public const int SHIFT = 2131230724; - + // aapt resource value: 0x7F0800A6 public const int shortcut = 2131230886; - + // aapt resource value: 0x7F0800A7 public const int showCustom = 2131230887; - + // aapt resource value: 0x7F0800A8 public const int showHome = 2131230888; - + // aapt resource value: 0x7F0800A9 public const int showTitle = 2131230889; - + // aapt resource value: 0x7F0800AA public const int smallLabel = 2131230890; - + // aapt resource value: 0x7F0800AB public const int snackbar_action = 2131230891; - + // aapt resource value: 0x7F0800AC public const int snackbar_text = 2131230892; - + // aapt resource value: 0x7F0800AD public const int snap = 2131230893; - + // aapt resource value: 0x7F0800AE public const int spacer = 2131230894; - + // aapt resource value: 0x7F0800AF public const int split_action_bar = 2131230895; - + // aapt resource value: 0x7F0800B0 public const int src_atop = 2131230896; - + // aapt resource value: 0x7F0800B1 public const int src_in = 2131230897; - + // aapt resource value: 0x7F0800B2 public const int src_over = 2131230898; - + // aapt resource value: 0x7F0800B3 public const int start = 2131230899; - + // aapt resource value: 0x7F0800B4 public const int status_bar_latest_event_content = 2131230900; - + // aapt resource value: 0x7F0800B5 public const int submenuarrow = 2131230901; - + // aapt resource value: 0x7F0800B6 public const int submit_area = 2131230902; - + // aapt resource value: 0x7F080005 public const int SYM = 2131230725; - + // aapt resource value: 0x7F0800B7 public const int tabMode = 2131230903; - + // aapt resource value: 0x7F0800B8 public const int tag_transition_group = 2131230904; - + // aapt resource value: 0x7F0800B9 public const int text = 2131230905; - + // aapt resource value: 0x7F0800BA public const int text2 = 2131230906; - + // aapt resource value: 0x7F0800BE public const int textinput_counter = 2131230910; - + // aapt resource value: 0x7F0800BF public const int textinput_error = 2131230911; - + // aapt resource value: 0x7F0800BB public const int textSpacerNoButtons = 2131230907; - + // aapt resource value: 0x7F0800BC public const int textSpacerNoTitle = 2131230908; - + // aapt resource value: 0x7F0800BD public const int text_input_password_toggle = 2131230909; - + // aapt resource value: 0x7F0800C0 public const int time = 2131230912; - + // aapt resource value: 0x7F0800C1 public const int title = 2131230913; - + // aapt resource value: 0x7F0800C2 public const int titleDividerNoCustom = 2131230914; - + // aapt resource value: 0x7F0800C3 public const int title_template = 2131230915; - + // aapt resource value: 0x7F0800C4 public const int top = 2131230916; - + // aapt resource value: 0x7F0800C5 public const int topPanel = 2131230917; - + // aapt resource value: 0x7F0800C6 public const int touch_outside = 2131230918; - + // aapt resource value: 0x7F0800C7 public const int transition_current_scene = 2131230919; - + // aapt resource value: 0x7F0800C8 public const int transition_layout_save = 2131230920; - + // aapt resource value: 0x7F0800C9 public const int transition_position = 2131230921; - + // aapt resource value: 0x7F0800CA public const int transition_scene_layoutid_cache = 2131230922; - + // aapt resource value: 0x7F0800CB public const int transition_transform = 2131230923; - + // aapt resource value: 0x7F0800CC public const int uniform = 2131230924; - + // aapt resource value: 0x7F0800CD public const int up = 2131230925; - + // aapt resource value: 0x7F0800CE public const int useLogo = 2131230926; - + // aapt resource value: 0x7F0800CF public const int view_offset_helper = 2131230927; - + // aapt resource value: 0x7F0800D0 public const int visible = 2131230928; - + // aapt resource value: 0x7F0800D1 public const int volume_item_container = 2131230929; - + // aapt resource value: 0x7F0800D2 public const int withText = 2131230930; - + // aapt resource value: 0x7F0800D3 public const int wrap_content = 2131230931; - + static Id() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Id() { } } - + public partial class Integer { - + // aapt resource value: 0x7F090000 public const int abc_config_activityDefaultDur = 2131296256; - + // aapt resource value: 0x7F090001 public const int abc_config_activityShortDur = 2131296257; - + // aapt resource value: 0x7F090002 public const int app_bar_elevation_anim_duration = 2131296258; - + // aapt resource value: 0x7F090003 public const int bottom_sheet_slide_duration = 2131296259; - + // aapt resource value: 0x7F090004 public const int cancel_button_image_alpha = 2131296260; - + // aapt resource value: 0x7F090005 public const int config_tooltipAnimTime = 2131296261; - + // aapt resource value: 0x7F090006 public const int design_snackbar_text_max_lines = 2131296262; - + // aapt resource value: 0x7F090007 public const int hide_password_duration = 2131296263; - + // aapt resource value: 0x7F090008 public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - + // aapt resource value: 0x7F090009 public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - + // aapt resource value: 0x7F09000A public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - + // aapt resource value: 0x7F09000B public const int show_password_duration = 2131296267; - + // aapt resource value: 0x7F09000C public const int status_bar_notification_info_maxnum = 2131296268; - + static Integer() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Integer() { } } - + public partial class Interpolator { - + // aapt resource value: 0x7F0A0000 public const int mr_fast_out_slow_in = 2131361792; - + // aapt resource value: 0x7F0A0001 public const int mr_linear_out_slow_in = 2131361793; - + static Interpolator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Interpolator() { } } - + public partial class Layout { - + // aapt resource value: 0x7F0B0000 public const int abc_action_bar_title_item = 2131427328; - + // aapt resource value: 0x7F0B0001 public const int abc_action_bar_up_container = 2131427329; - + // aapt resource value: 0x7F0B0002 public const int abc_action_menu_item_layout = 2131427330; - + // aapt resource value: 0x7F0B0003 public const int abc_action_menu_layout = 2131427331; - + // aapt resource value: 0x7F0B0004 public const int abc_action_mode_bar = 2131427332; - + // aapt resource value: 0x7F0B0005 public const int abc_action_mode_close_item_material = 2131427333; - + // aapt resource value: 0x7F0B0006 public const int abc_activity_chooser_view = 2131427334; - + // aapt resource value: 0x7F0B0007 public const int abc_activity_chooser_view_list_item = 2131427335; - + // aapt resource value: 0x7F0B0008 public const int abc_alert_dialog_button_bar_material = 2131427336; - + // aapt resource value: 0x7F0B0009 public const int abc_alert_dialog_material = 2131427337; - + // aapt resource value: 0x7F0B000A public const int abc_alert_dialog_title_material = 2131427338; - + // aapt resource value: 0x7F0B000B public const int abc_dialog_title_material = 2131427339; - + // aapt resource value: 0x7F0B000C public const int abc_expanded_menu_layout = 2131427340; - + // aapt resource value: 0x7F0B000D public const int abc_list_menu_item_checkbox = 2131427341; - + // aapt resource value: 0x7F0B000E public const int abc_list_menu_item_icon = 2131427342; - + // aapt resource value: 0x7F0B000F public const int abc_list_menu_item_layout = 2131427343; - + // aapt resource value: 0x7F0B0010 public const int abc_list_menu_item_radio = 2131427344; - + // aapt resource value: 0x7F0B0011 public const int abc_popup_menu_header_item_layout = 2131427345; - + // aapt resource value: 0x7F0B0012 public const int abc_popup_menu_item_layout = 2131427346; - + // aapt resource value: 0x7F0B0013 public const int abc_screen_content_include = 2131427347; - + // aapt resource value: 0x7F0B0014 public const int abc_screen_simple = 2131427348; - + // aapt resource value: 0x7F0B0015 public const int abc_screen_simple_overlay_action_mode = 2131427349; - + // aapt resource value: 0x7F0B0016 public const int abc_screen_toolbar = 2131427350; - + // aapt resource value: 0x7F0B0017 public const int abc_search_dropdown_item_icons_2line = 2131427351; - + // aapt resource value: 0x7F0B0018 public const int abc_search_view = 2131427352; - + // aapt resource value: 0x7F0B0019 public const int abc_select_dialog_material = 2131427353; - + // aapt resource value: 0x7F0B001A public const int activity_main = 2131427354; - + // aapt resource value: 0x7F0B001B public const int BottomTabLayout = 2131427355; - + // aapt resource value: 0x7F0B001C public const int design_bottom_navigation_item = 2131427356; - + // aapt resource value: 0x7F0B001D public const int design_bottom_sheet_dialog = 2131427357; - + // aapt resource value: 0x7F0B001E public const int design_layout_snackbar = 2131427358; - + // aapt resource value: 0x7F0B001F public const int design_layout_snackbar_include = 2131427359; - + // aapt resource value: 0x7F0B0020 public const int design_layout_tab_icon = 2131427360; - + // aapt resource value: 0x7F0B0021 public const int design_layout_tab_text = 2131427361; - + // aapt resource value: 0x7F0B0022 public const int design_menu_item_action_area = 2131427362; - + // aapt resource value: 0x7F0B0023 public const int design_navigation_item = 2131427363; - + // aapt resource value: 0x7F0B0024 public const int design_navigation_item_header = 2131427364; - + // aapt resource value: 0x7F0B0025 public const int design_navigation_item_separator = 2131427365; - + // aapt resource value: 0x7F0B0026 public const int design_navigation_item_subheader = 2131427366; - + // aapt resource value: 0x7F0B0027 public const int design_navigation_menu = 2131427367; - + // aapt resource value: 0x7F0B0028 public const int design_navigation_menu_item = 2131427368; - + // aapt resource value: 0x7F0B0029 public const int design_text_input_password_icon = 2131427369; - + // aapt resource value: 0x7F0B002A public const int FlyoutContent = 2131427370; - + // aapt resource value: 0x7F0B002B public const int mr_chooser_dialog = 2131427371; - + // aapt resource value: 0x7F0B002C public const int mr_chooser_list_item = 2131427372; - + // aapt resource value: 0x7F0B002D public const int mr_controller_material_dialog_b = 2131427373; - + // aapt resource value: 0x7F0B002E public const int mr_controller_volume_item = 2131427374; - + // aapt resource value: 0x7F0B002F public const int mr_playback_control = 2131427375; - + // aapt resource value: 0x7F0B0030 public const int mr_volume_control = 2131427376; - + // aapt resource value: 0x7F0B0031 public const int notification_action = 2131427377; - + // aapt resource value: 0x7F0B0032 public const int notification_action_tombstone = 2131427378; - + // aapt resource value: 0x7F0B0033 public const int notification_media_action = 2131427379; - + // aapt resource value: 0x7F0B0034 public const int notification_media_cancel_action = 2131427380; - + // aapt resource value: 0x7F0B0035 public const int notification_template_big_media = 2131427381; - + // aapt resource value: 0x7F0B0036 public const int notification_template_big_media_custom = 2131427382; - + // aapt resource value: 0x7F0B0037 public const int notification_template_big_media_narrow = 2131427383; - + // aapt resource value: 0x7F0B0038 public const int notification_template_big_media_narrow_custom = 2131427384; - + // aapt resource value: 0x7F0B0039 public const int notification_template_custom_big = 2131427385; - + // aapt resource value: 0x7F0B003A public const int notification_template_icon_group = 2131427386; - + // aapt resource value: 0x7F0B003B public const int notification_template_lines_media = 2131427387; - + // aapt resource value: 0x7F0B003C public const int notification_template_media = 2131427388; - + // aapt resource value: 0x7F0B003D public const int notification_template_media_custom = 2131427389; - + // aapt resource value: 0x7F0B003E public const int notification_template_part_chronometer = 2131427390; - + // aapt resource value: 0x7F0B003F public const int notification_template_part_time = 2131427391; - + // aapt resource value: 0x7F0B0040 public const int RootLayout = 2131427392; - + // aapt resource value: 0x7F0B0041 public const int select_dialog_item_material = 2131427393; - + // aapt resource value: 0x7F0B0042 public const int select_dialog_multichoice_material = 2131427394; - + // aapt resource value: 0x7F0B0043 public const int select_dialog_singlechoice_material = 2131427395; - + // aapt resource value: 0x7F0B0044 public const int ShellContent = 2131427396; - + // aapt resource value: 0x7F0B0045 public const int support_simple_spinner_dropdown_item = 2131427397; - + // aapt resource value: 0x7F0B0046 public const int tooltip = 2131427398; - + static Layout() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Layout() { } } - + public partial class Mipmap { - + // aapt resource value: 0x7F0C0000 public const int ic_launcher = 2131492864; - + // aapt resource value: 0x7F0C0001 public const int ic_launcher_foreground = 2131492865; - + // aapt resource value: 0x7F0C0002 public const int ic_launcher_round = 2131492866; - + static Mipmap() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Mipmap() { } } - + public partial class String { - + // aapt resource value: 0x7F0D0000 public const int abc_action_bar_home_description = 2131558400; - + // aapt resource value: 0x7F0D0001 public const int abc_action_bar_up_description = 2131558401; - + // aapt resource value: 0x7F0D0002 public const int abc_action_menu_overflow_description = 2131558402; - + // aapt resource value: 0x7F0D0003 public const int abc_action_mode_done = 2131558403; - + // aapt resource value: 0x7F0D0005 public const int abc_activitychooserview_choose_application = 2131558405; - + // aapt resource value: 0x7F0D0004 public const int abc_activity_chooser_view_see_all = 2131558404; - + // aapt resource value: 0x7F0D0006 public const int abc_capital_off = 2131558406; - + // aapt resource value: 0x7F0D0007 public const int abc_capital_on = 2131558407; - + // aapt resource value: 0x7F0D0008 public const int abc_font_family_body_1_material = 2131558408; - + // aapt resource value: 0x7F0D0009 public const int abc_font_family_body_2_material = 2131558409; - + // aapt resource value: 0x7F0D000A public const int abc_font_family_button_material = 2131558410; - + // aapt resource value: 0x7F0D000B public const int abc_font_family_caption_material = 2131558411; - + // aapt resource value: 0x7F0D000C public const int abc_font_family_display_1_material = 2131558412; - + // aapt resource value: 0x7F0D000D public const int abc_font_family_display_2_material = 2131558413; - + // aapt resource value: 0x7F0D000E public const int abc_font_family_display_3_material = 2131558414; - + // aapt resource value: 0x7F0D000F public const int abc_font_family_display_4_material = 2131558415; - + // aapt resource value: 0x7F0D0010 public const int abc_font_family_headline_material = 2131558416; - + // aapt resource value: 0x7F0D0011 public const int abc_font_family_menu_material = 2131558417; - + // aapt resource value: 0x7F0D0012 public const int abc_font_family_subhead_material = 2131558418; - + // aapt resource value: 0x7F0D0013 public const int abc_font_family_title_material = 2131558419; - + // aapt resource value: 0x7F0D0015 public const int abc_searchview_description_clear = 2131558421; - + // aapt resource value: 0x7F0D0016 public const int abc_searchview_description_query = 2131558422; - + // aapt resource value: 0x7F0D0017 public const int abc_searchview_description_search = 2131558423; - + // aapt resource value: 0x7F0D0018 public const int abc_searchview_description_submit = 2131558424; - + // aapt resource value: 0x7F0D0019 public const int abc_searchview_description_voice = 2131558425; - + // aapt resource value: 0x7F0D0014 public const int abc_search_hint = 2131558420; - + // aapt resource value: 0x7F0D001A public const int abc_shareactionprovider_share_with = 2131558426; - + // aapt resource value: 0x7F0D001B public const int abc_shareactionprovider_share_with_application = 2131558427; - + // aapt resource value: 0x7F0D001C public const int abc_toolbar_collapse_description = 2131558428; - + // aapt resource value: 0x7F0D001D public const int action_settings = 2131558429; - + // aapt resource value: 0x7F0D001F public const int appbar_scrolling_view_behavior = 2131558431; - + // aapt resource value: 0x7F0D001E public const int app_name = 2131558430; - + // aapt resource value: 0x7F0D0020 public const int bottom_sheet_behavior = 2131558432; - + // aapt resource value: 0x7F0D0021 public const int character_counter_pattern = 2131558433; - + // aapt resource value: 0x7F0D0022 public const int mr_button_content_description = 2131558434; - + // aapt resource value: 0x7F0D0023 public const int mr_cast_button_connected = 2131558435; - + // aapt resource value: 0x7F0D0024 public const int mr_cast_button_connecting = 2131558436; - + // aapt resource value: 0x7F0D0025 public const int mr_cast_button_disconnected = 2131558437; - + // aapt resource value: 0x7F0D0026 public const int mr_chooser_searching = 2131558438; - + // aapt resource value: 0x7F0D0027 public const int mr_chooser_title = 2131558439; - + // aapt resource value: 0x7F0D0028 public const int mr_controller_album_art = 2131558440; - + // aapt resource value: 0x7F0D0029 public const int mr_controller_casting_screen = 2131558441; - + // aapt resource value: 0x7F0D002A public const int mr_controller_close_description = 2131558442; - + // aapt resource value: 0x7F0D002B public const int mr_controller_collapse_group = 2131558443; - + // aapt resource value: 0x7F0D002C public const int mr_controller_disconnect = 2131558444; - + // aapt resource value: 0x7F0D002D public const int mr_controller_expand_group = 2131558445; - + // aapt resource value: 0x7F0D002E public const int mr_controller_no_info_available = 2131558446; - + // aapt resource value: 0x7F0D002F public const int mr_controller_no_media_selected = 2131558447; - + // aapt resource value: 0x7F0D0030 public const int mr_controller_pause = 2131558448; - + // aapt resource value: 0x7F0D0031 public const int mr_controller_play = 2131558449; - + // aapt resource value: 0x7F0D0032 public const int mr_controller_stop = 2131558450; - + // aapt resource value: 0x7F0D0033 public const int mr_controller_stop_casting = 2131558451; - + // aapt resource value: 0x7F0D0034 public const int mr_controller_volume_slider = 2131558452; - + // aapt resource value: 0x7F0D0035 public const int mr_system_route_name = 2131558453; - + // aapt resource value: 0x7F0D0036 public const int mr_user_route_category_name = 2131558454; - + // aapt resource value: 0x7F0D0037 public const int password_toggle_content_description = 2131558455; - + // aapt resource value: 0x7F0D0038 public const int path_password_eye = 2131558456; - + // aapt resource value: 0x7F0D0039 public const int path_password_eye_mask_strike_through = 2131558457; - + // aapt resource value: 0x7F0D003A public const int path_password_eye_mask_visible = 2131558458; - + // aapt resource value: 0x7F0D003B public const int path_password_strike_through = 2131558459; - + // aapt resource value: 0x7F0D003C public const int search_menu_title = 2131558460; - + // aapt resource value: 0x7F0D003D public const int status_bar_notification_info_overflow = 2131558461; - + static String() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private String() { } } - + public partial class Style { - + // aapt resource value: 0x7F0E0000 public const int AlertDialog_AppCompat = 2131623936; - + // aapt resource value: 0x7F0E0001 public const int AlertDialog_AppCompat_Light = 2131623937; - + // aapt resource value: 0x7F0E0002 public const int Animation_AppCompat_Dialog = 2131623938; - + // aapt resource value: 0x7F0E0003 public const int Animation_AppCompat_DropDownUp = 2131623939; - + // aapt resource value: 0x7F0E0004 public const int Animation_AppCompat_Tooltip = 2131623940; - + // aapt resource value: 0x7F0E0005 public const int Animation_Design_BottomSheetDialog = 2131623941; - + // aapt resource value: 0x7F0E0006 public const int Base_AlertDialog_AppCompat = 2131623942; - + // aapt resource value: 0x7F0E0007 public const int Base_AlertDialog_AppCompat_Light = 2131623943; - + // aapt resource value: 0x7F0E0008 public const int Base_Animation_AppCompat_Dialog = 2131623944; - + // aapt resource value: 0x7F0E0009 public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - + // aapt resource value: 0x7F0E000A public const int Base_Animation_AppCompat_Tooltip = 2131623946; - + // aapt resource value: 0x7F0E000B public const int Base_CardView = 2131623947; - + // aapt resource value: 0x7F0E000D public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - + // aapt resource value: 0x7F0E000C public const int Base_DialogWindowTitle_AppCompat = 2131623948; - + // aapt resource value: 0x7F0E000E public const int Base_TextAppearance_AppCompat = 2131623950; - + // aapt resource value: 0x7F0E000F public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - + // aapt resource value: 0x7F0E0010 public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - + // aapt resource value: 0x7F0E0011 public const int Base_TextAppearance_AppCompat_Button = 2131623953; - + // aapt resource value: 0x7F0E0012 public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - + // aapt resource value: 0x7F0E0013 public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - + // aapt resource value: 0x7F0E0014 public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - + // aapt resource value: 0x7F0E0015 public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - + // aapt resource value: 0x7F0E0016 public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - + // aapt resource value: 0x7F0E0017 public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - + // aapt resource value: 0x7F0E0018 public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - + // aapt resource value: 0x7F0E0019 public const int Base_TextAppearance_AppCompat_Large = 2131623961; - + // aapt resource value: 0x7F0E001A public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - + // aapt resource value: 0x7F0E001B public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - + // aapt resource value: 0x7F0E001C public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - + // aapt resource value: 0x7F0E001D public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - + // aapt resource value: 0x7F0E001E public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - + // aapt resource value: 0x7F0E001F public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - + // aapt resource value: 0x7F0E0020 public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - + // aapt resource value: 0x7F0E0021 public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - + // aapt resource value: 0x7F0E0022 public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - + // aapt resource value: 0x7F0E0023 public const int Base_TextAppearance_AppCompat_Small = 2131623971; - + // aapt resource value: 0x7F0E0024 public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - + // aapt resource value: 0x7F0E0025 public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - + // aapt resource value: 0x7F0E0026 public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - + // aapt resource value: 0x7F0E0027 public const int Base_TextAppearance_AppCompat_Title = 2131623975; - + // aapt resource value: 0x7F0E0028 public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - + // aapt resource value: 0x7F0E0029 public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - + // aapt resource value: 0x7F0E002A public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - + // aapt resource value: 0x7F0E002B public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - + // aapt resource value: 0x7F0E002C public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - + // aapt resource value: 0x7F0E002D public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - + // aapt resource value: 0x7F0E002E public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - + // aapt resource value: 0x7F0E002F public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - + // aapt resource value: 0x7F0E0030 public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - + // aapt resource value: 0x7F0E0031 public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - + // aapt resource value: 0x7F0E0032 public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - + // aapt resource value: 0x7F0E0033 public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - + // aapt resource value: 0x7F0E0034 public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - + // aapt resource value: 0x7F0E0035 public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - + // aapt resource value: 0x7F0E0036 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - + // aapt resource value: 0x7F0E0037 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - + // aapt resource value: 0x7F0E0038 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - + // aapt resource value: 0x7F0E0039 public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - + // aapt resource value: 0x7F0E003A public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - + // aapt resource value: 0x7F0E003B public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - + // aapt resource value: 0x7F0E003C public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - + // aapt resource value: 0x7F0E003D public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - + // aapt resource value: 0x7F0E004C public const int Base_ThemeOverlay_AppCompat = 2131624012; - + // aapt resource value: 0x7F0E004D public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - + // aapt resource value: 0x7F0E004E public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - + // aapt resource value: 0x7F0E004F public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - + // aapt resource value: 0x7F0E0050 public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - + // aapt resource value: 0x7F0E0051 public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - + // aapt resource value: 0x7F0E0052 public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - + // aapt resource value: 0x7F0E003E public const int Base_Theme_AppCompat = 2131623998; - + // aapt resource value: 0x7F0E003F public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - + // aapt resource value: 0x7F0E0040 public const int Base_Theme_AppCompat_Dialog = 2131624000; - + // aapt resource value: 0x7F0E0044 public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - + // aapt resource value: 0x7F0E0041 public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - + // aapt resource value: 0x7F0E0042 public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - + // aapt resource value: 0x7F0E0043 public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - + // aapt resource value: 0x7F0E0045 public const int Base_Theme_AppCompat_Light = 2131624005; - + // aapt resource value: 0x7F0E0046 public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - + // aapt resource value: 0x7F0E0047 public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - + // aapt resource value: 0x7F0E004B public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - + // aapt resource value: 0x7F0E0048 public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - + // aapt resource value: 0x7F0E0049 public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - + // aapt resource value: 0x7F0E004A public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - + // aapt resource value: 0x7F0E0055 public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - + // aapt resource value: 0x7F0E0053 public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - + // aapt resource value: 0x7F0E0054 public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - + // aapt resource value: 0x7F0E0056 public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - + // aapt resource value: 0x7F0E0057 public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - + // aapt resource value: 0x7F0E0058 public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - + // aapt resource value: 0x7F0E005D public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - + // aapt resource value: 0x7F0E0059 public const int Base_V21_Theme_AppCompat = 2131624025; - + // aapt resource value: 0x7F0E005A public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - + // aapt resource value: 0x7F0E005B public const int Base_V21_Theme_AppCompat_Light = 2131624027; - + // aapt resource value: 0x7F0E005C public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - + // aapt resource value: 0x7F0E005E public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - + // aapt resource value: 0x7F0E005F public const int Base_V22_Theme_AppCompat = 2131624031; - + // aapt resource value: 0x7F0E0060 public const int Base_V22_Theme_AppCompat_Light = 2131624032; - + // aapt resource value: 0x7F0E0061 public const int Base_V23_Theme_AppCompat = 2131624033; - + // aapt resource value: 0x7F0E0062 public const int Base_V23_Theme_AppCompat_Light = 2131624034; - + // aapt resource value: 0x7F0E0063 public const int Base_V26_Theme_AppCompat = 2131624035; - + // aapt resource value: 0x7F0E0064 public const int Base_V26_Theme_AppCompat_Light = 2131624036; - + // aapt resource value: 0x7F0E0065 public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - + // aapt resource value: 0x7F0E0066 public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - + // aapt resource value: 0x7F0E006B public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - + // aapt resource value: 0x7F0E0067 public const int Base_V7_Theme_AppCompat = 2131624039; - + // aapt resource value: 0x7F0E0068 public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - + // aapt resource value: 0x7F0E0069 public const int Base_V7_Theme_AppCompat_Light = 2131624041; - + // aapt resource value: 0x7F0E006A public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - + // aapt resource value: 0x7F0E006C public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - + // aapt resource value: 0x7F0E006D public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - + // aapt resource value: 0x7F0E006E public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - + // aapt resource value: 0x7F0E006F public const int Base_Widget_AppCompat_ActionBar = 2131624047; - + // aapt resource value: 0x7F0E0070 public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - + // aapt resource value: 0x7F0E0071 public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - + // aapt resource value: 0x7F0E0072 public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - + // aapt resource value: 0x7F0E0073 public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - + // aapt resource value: 0x7F0E0074 public const int Base_Widget_AppCompat_ActionButton = 2131624052; - + // aapt resource value: 0x7F0E0075 public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - + // aapt resource value: 0x7F0E0076 public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - + // aapt resource value: 0x7F0E0077 public const int Base_Widget_AppCompat_ActionMode = 2131624055; - + // aapt resource value: 0x7F0E0078 public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - + // aapt resource value: 0x7F0E0079 public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - + // aapt resource value: 0x7F0E007A public const int Base_Widget_AppCompat_Button = 2131624058; - + // aapt resource value: 0x7F0E0080 public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - + // aapt resource value: 0x7F0E0081 public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - + // aapt resource value: 0x7F0E007B public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - + // aapt resource value: 0x7F0E007C public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - + // aapt resource value: 0x7F0E007D public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - + // aapt resource value: 0x7F0E007E public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - + // aapt resource value: 0x7F0E007F public const int Base_Widget_AppCompat_Button_Small = 2131624063; - + // aapt resource value: 0x7F0E0082 public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - + // aapt resource value: 0x7F0E0083 public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - + // aapt resource value: 0x7F0E0084 public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - + // aapt resource value: 0x7F0E0085 public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - + // aapt resource value: 0x7F0E0086 public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - + // aapt resource value: 0x7F0E0087 public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - + // aapt resource value: 0x7F0E0088 public const int Base_Widget_AppCompat_EditText = 2131624072; - + // aapt resource value: 0x7F0E0089 public const int Base_Widget_AppCompat_ImageButton = 2131624073; - + // aapt resource value: 0x7F0E008A public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - + // aapt resource value: 0x7F0E008B public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - + // aapt resource value: 0x7F0E008C public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - + // aapt resource value: 0x7F0E008D public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - + // aapt resource value: 0x7F0E008E public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - + // aapt resource value: 0x7F0E008F public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - + // aapt resource value: 0x7F0E0090 public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - + // aapt resource value: 0x7F0E0091 public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - + // aapt resource value: 0x7F0E0092 public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - + // aapt resource value: 0x7F0E0093 public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - + // aapt resource value: 0x7F0E0094 public const int Base_Widget_AppCompat_ListView = 2131624084; - + // aapt resource value: 0x7F0E0095 public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - + // aapt resource value: 0x7F0E0096 public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - + // aapt resource value: 0x7F0E0097 public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - + // aapt resource value: 0x7F0E0098 public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - + // aapt resource value: 0x7F0E0099 public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - + // aapt resource value: 0x7F0E009A public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - + // aapt resource value: 0x7F0E009B public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - + // aapt resource value: 0x7F0E009C public const int Base_Widget_AppCompat_RatingBar = 2131624092; - + // aapt resource value: 0x7F0E009D public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - + // aapt resource value: 0x7F0E009E public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - + // aapt resource value: 0x7F0E009F public const int Base_Widget_AppCompat_SearchView = 2131624095; - + // aapt resource value: 0x7F0E00A0 public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - + // aapt resource value: 0x7F0E00A1 public const int Base_Widget_AppCompat_SeekBar = 2131624097; - + // aapt resource value: 0x7F0E00A2 public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - + // aapt resource value: 0x7F0E00A3 public const int Base_Widget_AppCompat_Spinner = 2131624099; - + // aapt resource value: 0x7F0E00A4 public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - + // aapt resource value: 0x7F0E00A5 public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - + // aapt resource value: 0x7F0E00A6 public const int Base_Widget_AppCompat_Toolbar = 2131624102; - + // aapt resource value: 0x7F0E00A7 public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - + // aapt resource value: 0x7F0E00A8 public const int Base_Widget_Design_AppBarLayout = 2131624104; - + // aapt resource value: 0x7F0E00A9 public const int Base_Widget_Design_TabLayout = 2131624105; - + // aapt resource value: 0x7F0E00AA public const int CardView = 2131624106; - + // aapt resource value: 0x7F0E00AB public const int CardView_Dark = 2131624107; - + // aapt resource value: 0x7F0E00AC public const int CardView_Light = 2131624108; - + // aapt resource value: 0x7F0E00AD public const int Platform_AppCompat = 2131624109; - + // aapt resource value: 0x7F0E00AE public const int Platform_AppCompat_Light = 2131624110; - + // aapt resource value: 0x7F0E00AF public const int Platform_ThemeOverlay_AppCompat = 2131624111; - + // aapt resource value: 0x7F0E00B0 public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - + // aapt resource value: 0x7F0E00B1 public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - + // aapt resource value: 0x7F0E00B2 public const int Platform_V11_AppCompat = 2131624114; - + // aapt resource value: 0x7F0E00B3 public const int Platform_V11_AppCompat_Light = 2131624115; - + // aapt resource value: 0x7F0E00B4 public const int Platform_V14_AppCompat = 2131624116; - + // aapt resource value: 0x7F0E00B5 public const int Platform_V14_AppCompat_Light = 2131624117; - + // aapt resource value: 0x7F0E00B6 public const int Platform_V21_AppCompat = 2131624118; - + // aapt resource value: 0x7F0E00B7 public const int Platform_V21_AppCompat_Light = 2131624119; - + // aapt resource value: 0x7F0E00B8 public const int Platform_V25_AppCompat = 2131624120; - + // aapt resource value: 0x7F0E00B9 public const int Platform_V25_AppCompat_Light = 2131624121; - + // aapt resource value: 0x7F0E00BA public const int Platform_Widget_AppCompat_Spinner = 2131624122; - + // aapt resource value: 0x7F0E00BB public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - + // aapt resource value: 0x7F0E00BC public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - + // aapt resource value: 0x7F0E00BD public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - + // aapt resource value: 0x7F0E00BE public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - + // aapt resource value: 0x7F0E00BF public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - + // aapt resource value: 0x7F0E00C0 public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - + // aapt resource value: 0x7F0E00C6 public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - + // aapt resource value: 0x7F0E00C1 public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - + // aapt resource value: 0x7F0E00C2 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - + // aapt resource value: 0x7F0E00C3 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - + // aapt resource value: 0x7F0E00C4 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - + // aapt resource value: 0x7F0E00C5 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - + // aapt resource value: 0x7F0E00C7 public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - + // aapt resource value: 0x7F0E00C8 public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - + // aapt resource value: 0x7F0E00C9 public const int TextAppearance_AppCompat = 2131624137; - + // aapt resource value: 0x7F0E00CA public const int TextAppearance_AppCompat_Body1 = 2131624138; - + // aapt resource value: 0x7F0E00CB public const int TextAppearance_AppCompat_Body2 = 2131624139; - + // aapt resource value: 0x7F0E00CC public const int TextAppearance_AppCompat_Button = 2131624140; - + // aapt resource value: 0x7F0E00CD public const int TextAppearance_AppCompat_Caption = 2131624141; - + // aapt resource value: 0x7F0E00CE public const int TextAppearance_AppCompat_Display1 = 2131624142; - + // aapt resource value: 0x7F0E00CF public const int TextAppearance_AppCompat_Display2 = 2131624143; - + // aapt resource value: 0x7F0E00D0 public const int TextAppearance_AppCompat_Display3 = 2131624144; - + // aapt resource value: 0x7F0E00D1 public const int TextAppearance_AppCompat_Display4 = 2131624145; - + // aapt resource value: 0x7F0E00D2 public const int TextAppearance_AppCompat_Headline = 2131624146; - + // aapt resource value: 0x7F0E00D3 public const int TextAppearance_AppCompat_Inverse = 2131624147; - + // aapt resource value: 0x7F0E00D4 public const int TextAppearance_AppCompat_Large = 2131624148; - + // aapt resource value: 0x7F0E00D5 public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - + // aapt resource value: 0x7F0E00D6 public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - + // aapt resource value: 0x7F0E00D7 public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - + // aapt resource value: 0x7F0E00D8 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - + // aapt resource value: 0x7F0E00D9 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - + // aapt resource value: 0x7F0E00DA public const int TextAppearance_AppCompat_Medium = 2131624154; - + // aapt resource value: 0x7F0E00DB public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - + // aapt resource value: 0x7F0E00DC public const int TextAppearance_AppCompat_Menu = 2131624156; - + // aapt resource value: 0x7F0E00DD public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - + // aapt resource value: 0x7F0E00DE public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - + // aapt resource value: 0x7F0E00DF public const int TextAppearance_AppCompat_Small = 2131624159; - + // aapt resource value: 0x7F0E00E0 public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - + // aapt resource value: 0x7F0E00E1 public const int TextAppearance_AppCompat_Subhead = 2131624161; - + // aapt resource value: 0x7F0E00E2 public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - + // aapt resource value: 0x7F0E00E3 public const int TextAppearance_AppCompat_Title = 2131624163; - + // aapt resource value: 0x7F0E00E4 public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - + // aapt resource value: 0x7F0E00E5 public const int TextAppearance_AppCompat_Tooltip = 2131624165; - + // aapt resource value: 0x7F0E00E6 public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - + // aapt resource value: 0x7F0E00E7 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - + // aapt resource value: 0x7F0E00E8 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - + // aapt resource value: 0x7F0E00E9 public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - + // aapt resource value: 0x7F0E00EA public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - + // aapt resource value: 0x7F0E00EB public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - + // aapt resource value: 0x7F0E00EC public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - + // aapt resource value: 0x7F0E00ED public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - + // aapt resource value: 0x7F0E00EE public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - + // aapt resource value: 0x7F0E00EF public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - + // aapt resource value: 0x7F0E00F0 public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - + // aapt resource value: 0x7F0E00F1 public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - + // aapt resource value: 0x7F0E00F2 public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - + // aapt resource value: 0x7F0E00F3 public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - + // aapt resource value: 0x7F0E00F4 public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - + // aapt resource value: 0x7F0E00F5 public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - + // aapt resource value: 0x7F0E00F6 public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - + // aapt resource value: 0x7F0E00F7 public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - + // aapt resource value: 0x7F0E00F8 public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - + // aapt resource value: 0x7F0E00F9 public const int TextAppearance_Compat_Notification = 2131624185; - + // aapt resource value: 0x7F0E00FA public const int TextAppearance_Compat_Notification_Info = 2131624186; - + // aapt resource value: 0x7F0E00FB public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - + // aapt resource value: 0x7F0E00FC public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - + // aapt resource value: 0x7F0E00FD public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - + // aapt resource value: 0x7F0E00FE public const int TextAppearance_Compat_Notification_Media = 2131624190; - + // aapt resource value: 0x7F0E00FF public const int TextAppearance_Compat_Notification_Time = 2131624191; - + // aapt resource value: 0x7F0E0100 public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - + // aapt resource value: 0x7F0E0101 public const int TextAppearance_Compat_Notification_Title = 2131624193; - + // aapt resource value: 0x7F0E0102 public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - + // aapt resource value: 0x7F0E0103 public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - + // aapt resource value: 0x7F0E0104 public const int TextAppearance_Design_Counter = 2131624196; - + // aapt resource value: 0x7F0E0105 public const int TextAppearance_Design_Counter_Overflow = 2131624197; - + // aapt resource value: 0x7F0E0106 public const int TextAppearance_Design_Error = 2131624198; - + // aapt resource value: 0x7F0E0107 public const int TextAppearance_Design_Hint = 2131624199; - + // aapt resource value: 0x7F0E0108 public const int TextAppearance_Design_Snackbar_Message = 2131624200; - + // aapt resource value: 0x7F0E0109 public const int TextAppearance_Design_Tab = 2131624201; - + // aapt resource value: 0x7F0E010A public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - + // aapt resource value: 0x7F0E010B public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - + // aapt resource value: 0x7F0E010C public const int TextAppearance_MediaRouter_Title = 2131624204; - + // aapt resource value: 0x7F0E010D public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - + // aapt resource value: 0x7F0E010E public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - + // aapt resource value: 0x7F0E010F public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - + // aapt resource value: 0x7F0E012F public const int ThemeOverlay_AppCompat = 2131624239; - + // aapt resource value: 0x7F0E0130 public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - + // aapt resource value: 0x7F0E0131 public const int ThemeOverlay_AppCompat_Dark = 2131624241; - + // aapt resource value: 0x7F0E0132 public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - + // aapt resource value: 0x7F0E0133 public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - + // aapt resource value: 0x7F0E0134 public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - + // aapt resource value: 0x7F0E0135 public const int ThemeOverlay_AppCompat_Light = 2131624245; - + // aapt resource value: 0x7F0E0136 public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - + // aapt resource value: 0x7F0E0137 public const int ThemeOverlay_MediaRouter_Light = 2131624247; - + // aapt resource value: 0x7F0E0110 public const int Theme_AppCompat = 2131624208; - + // aapt resource value: 0x7F0E0111 public const int Theme_AppCompat_CompactMenu = 2131624209; - + // aapt resource value: 0x7F0E0112 public const int Theme_AppCompat_DayNight = 2131624210; - + // aapt resource value: 0x7F0E0113 public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - + // aapt resource value: 0x7F0E0114 public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - + // aapt resource value: 0x7F0E0117 public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - + // aapt resource value: 0x7F0E0115 public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - + // aapt resource value: 0x7F0E0116 public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - + // aapt resource value: 0x7F0E0118 public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - + // aapt resource value: 0x7F0E0119 public const int Theme_AppCompat_Dialog = 2131624217; - + // aapt resource value: 0x7F0E011C public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - + // aapt resource value: 0x7F0E011A public const int Theme_AppCompat_Dialog_Alert = 2131624218; - + // aapt resource value: 0x7F0E011B public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - + // aapt resource value: 0x7F0E011D public const int Theme_AppCompat_Light = 2131624221; - + // aapt resource value: 0x7F0E011E public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - + // aapt resource value: 0x7F0E011F public const int Theme_AppCompat_Light_Dialog = 2131624223; - + // aapt resource value: 0x7F0E0122 public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - + // aapt resource value: 0x7F0E0120 public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - + // aapt resource value: 0x7F0E0121 public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - + // aapt resource value: 0x7F0E0123 public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - + // aapt resource value: 0x7F0E0124 public const int Theme_AppCompat_NoActionBar = 2131624228; - + // aapt resource value: 0x7F0E0125 public const int Theme_Design = 2131624229; - + // aapt resource value: 0x7F0E0126 public const int Theme_Design_BottomSheetDialog = 2131624230; - + // aapt resource value: 0x7F0E0127 public const int Theme_Design_Light = 2131624231; - + // aapt resource value: 0x7F0E0128 public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - + // aapt resource value: 0x7F0E0129 public const int Theme_Design_Light_NoActionBar = 2131624233; - + // aapt resource value: 0x7F0E012A public const int Theme_Design_NoActionBar = 2131624234; - + // aapt resource value: 0x7F0E012B public const int Theme_MediaRouter = 2131624235; - + // aapt resource value: 0x7F0E012C public const int Theme_MediaRouter_Light = 2131624236; - + // aapt resource value: 0x7F0E012E public const int Theme_MediaRouter_LightControlPanel = 2131624238; - + // aapt resource value: 0x7F0E012D public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - + // aapt resource value: 0x7F0E0138 public const int Widget_AppCompat_ActionBar = 2131624248; - + // aapt resource value: 0x7F0E0139 public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - + // aapt resource value: 0x7F0E013A public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - + // aapt resource value: 0x7F0E013B public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - + // aapt resource value: 0x7F0E013C public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - + // aapt resource value: 0x7F0E013D public const int Widget_AppCompat_ActionButton = 2131624253; - + // aapt resource value: 0x7F0E013E public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - + // aapt resource value: 0x7F0E013F public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - + // aapt resource value: 0x7F0E0140 public const int Widget_AppCompat_ActionMode = 2131624256; - + // aapt resource value: 0x7F0E0141 public const int Widget_AppCompat_ActivityChooserView = 2131624257; - + // aapt resource value: 0x7F0E0142 public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - + // aapt resource value: 0x7F0E0143 public const int Widget_AppCompat_Button = 2131624259; - + // aapt resource value: 0x7F0E0149 public const int Widget_AppCompat_ButtonBar = 2131624265; - + // aapt resource value: 0x7F0E014A public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - + // aapt resource value: 0x7F0E0144 public const int Widget_AppCompat_Button_Borderless = 2131624260; - + // aapt resource value: 0x7F0E0145 public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - + // aapt resource value: 0x7F0E0146 public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - + // aapt resource value: 0x7F0E0147 public const int Widget_AppCompat_Button_Colored = 2131624263; - + // aapt resource value: 0x7F0E0148 public const int Widget_AppCompat_Button_Small = 2131624264; - + // aapt resource value: 0x7F0E014B public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - + // aapt resource value: 0x7F0E014C public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - + // aapt resource value: 0x7F0E014D public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - + // aapt resource value: 0x7F0E014E public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - + // aapt resource value: 0x7F0E014F public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - + // aapt resource value: 0x7F0E0150 public const int Widget_AppCompat_EditText = 2131624272; - + // aapt resource value: 0x7F0E0151 public const int Widget_AppCompat_ImageButton = 2131624273; - + // aapt resource value: 0x7F0E0152 public const int Widget_AppCompat_Light_ActionBar = 2131624274; - + // aapt resource value: 0x7F0E0153 public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - + // aapt resource value: 0x7F0E0154 public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - + // aapt resource value: 0x7F0E0155 public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - + // aapt resource value: 0x7F0E0156 public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - + // aapt resource value: 0x7F0E0157 public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - + // aapt resource value: 0x7F0E0158 public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - + // aapt resource value: 0x7F0E0159 public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - + // aapt resource value: 0x7F0E015A public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - + // aapt resource value: 0x7F0E015B public const int Widget_AppCompat_Light_ActionButton = 2131624283; - + // aapt resource value: 0x7F0E015C public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - + // aapt resource value: 0x7F0E015D public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - + // aapt resource value: 0x7F0E015E public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - + // aapt resource value: 0x7F0E015F public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - + // aapt resource value: 0x7F0E0160 public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - + // aapt resource value: 0x7F0E0161 public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - + // aapt resource value: 0x7F0E0162 public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - + // aapt resource value: 0x7F0E0163 public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - + // aapt resource value: 0x7F0E0164 public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - + // aapt resource value: 0x7F0E0165 public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - + // aapt resource value: 0x7F0E0166 public const int Widget_AppCompat_Light_SearchView = 2131624294; - + // aapt resource value: 0x7F0E0167 public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - + // aapt resource value: 0x7F0E0168 public const int Widget_AppCompat_ListMenuView = 2131624296; - + // aapt resource value: 0x7F0E0169 public const int Widget_AppCompat_ListPopupWindow = 2131624297; - + // aapt resource value: 0x7F0E016A public const int Widget_AppCompat_ListView = 2131624298; - + // aapt resource value: 0x7F0E016B public const int Widget_AppCompat_ListView_DropDown = 2131624299; - + // aapt resource value: 0x7F0E016C public const int Widget_AppCompat_ListView_Menu = 2131624300; - + // aapt resource value: 0x7F0E016D public const int Widget_AppCompat_PopupMenu = 2131624301; - + // aapt resource value: 0x7F0E016E public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - + // aapt resource value: 0x7F0E016F public const int Widget_AppCompat_PopupWindow = 2131624303; - + // aapt resource value: 0x7F0E0170 public const int Widget_AppCompat_ProgressBar = 2131624304; - + // aapt resource value: 0x7F0E0171 public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - + // aapt resource value: 0x7F0E0172 public const int Widget_AppCompat_RatingBar = 2131624306; - + // aapt resource value: 0x7F0E0173 public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - + // aapt resource value: 0x7F0E0174 public const int Widget_AppCompat_RatingBar_Small = 2131624308; - + // aapt resource value: 0x7F0E0175 public const int Widget_AppCompat_SearchView = 2131624309; - + // aapt resource value: 0x7F0E0176 public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - + // aapt resource value: 0x7F0E0177 public const int Widget_AppCompat_SeekBar = 2131624311; - + // aapt resource value: 0x7F0E0178 public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - + // aapt resource value: 0x7F0E0179 public const int Widget_AppCompat_Spinner = 2131624313; - + // aapt resource value: 0x7F0E017A public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - + // aapt resource value: 0x7F0E017B public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - + // aapt resource value: 0x7F0E017C public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - + // aapt resource value: 0x7F0E017D public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - + // aapt resource value: 0x7F0E017E public const int Widget_AppCompat_Toolbar = 2131624318; - + // aapt resource value: 0x7F0E017F public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - + // aapt resource value: 0x7F0E0180 public const int Widget_Compat_NotificationActionContainer = 2131624320; - + // aapt resource value: 0x7F0E0181 public const int Widget_Compat_NotificationActionText = 2131624321; - + // aapt resource value: 0x7F0E0182 public const int Widget_Design_AppBarLayout = 2131624322; - + // aapt resource value: 0x7F0E0183 public const int Widget_Design_BottomNavigationView = 2131624323; - + // aapt resource value: 0x7F0E0184 public const int Widget_Design_BottomSheet_Modal = 2131624324; - + // aapt resource value: 0x7F0E0185 public const int Widget_Design_CollapsingToolbar = 2131624325; - + // aapt resource value: 0x7F0E0186 public const int Widget_Design_CoordinatorLayout = 2131624326; - + // aapt resource value: 0x7F0E0187 public const int Widget_Design_FloatingActionButton = 2131624327; - + // aapt resource value: 0x7F0E0188 public const int Widget_Design_NavigationView = 2131624328; - + // aapt resource value: 0x7F0E0189 public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - + // aapt resource value: 0x7F0E018A public const int Widget_Design_Snackbar = 2131624330; - + // aapt resource value: 0x7F0E018B public const int Widget_Design_TabLayout = 2131624331; - + // aapt resource value: 0x7F0E018C public const int Widget_Design_TextInputLayout = 2131624332; - + // aapt resource value: 0x7F0E018D public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - + // aapt resource value: 0x7F0E018E public const int Widget_MediaRouter_MediaRouteButton = 2131624334; - + static Style() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Style() { } } - + public partial class Styleable { - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } public static int[] ActionBar = new int[] { 2130903089, @@ -7417,112 +7540,112 @@ public partial class Styleable 2130903335, 2130903379, 2130903389}; - + // aapt resource value: { 0x10100B3 } public static int[] ActionBarLayout = new int[] { 16842931}; - + // aapt resource value: 0 public const int ActionBarLayout_android_layout_gravity = 0; - + // aapt resource value: 0 public const int ActionBar_background = 0; - + // aapt resource value: 1 public const int ActionBar_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionBar_backgroundStacked = 2; - + // aapt resource value: 3 public const int ActionBar_contentInsetEnd = 3; - + // aapt resource value: 4 public const int ActionBar_contentInsetEndWithActions = 4; - + // aapt resource value: 5 public const int ActionBar_contentInsetLeft = 5; - + // aapt resource value: 6 public const int ActionBar_contentInsetRight = 6; - + // aapt resource value: 7 public const int ActionBar_contentInsetStart = 7; - + // aapt resource value: 8 public const int ActionBar_contentInsetStartWithNavigation = 8; - + // aapt resource value: 9 public const int ActionBar_customNavigationLayout = 9; - + // aapt resource value: 10 public const int ActionBar_displayOptions = 10; - + // aapt resource value: 11 public const int ActionBar_divider = 11; - + // aapt resource value: 12 public const int ActionBar_elevation = 12; - + // aapt resource value: 13 public const int ActionBar_height = 13; - + // aapt resource value: 14 public const int ActionBar_hideOnContentScroll = 14; - + // aapt resource value: 15 public const int ActionBar_homeAsUpIndicator = 15; - + // aapt resource value: 16 public const int ActionBar_homeLayout = 16; - + // aapt resource value: 17 public const int ActionBar_icon = 17; - + // aapt resource value: 18 public const int ActionBar_indeterminateProgressStyle = 18; - + // aapt resource value: 19 public const int ActionBar_itemPadding = 19; - + // aapt resource value: 20 public const int ActionBar_logo = 20; - + // aapt resource value: 21 public const int ActionBar_navigationMode = 21; - + // aapt resource value: 22 public const int ActionBar_popupTheme = 22; - + // aapt resource value: 23 public const int ActionBar_progressBarPadding = 23; - + // aapt resource value: 24 public const int ActionBar_progressBarStyle = 24; - + // aapt resource value: 25 public const int ActionBar_subtitle = 25; - + // aapt resource value: 26 public const int ActionBar_subtitleTextStyle = 26; - + // aapt resource value: 27 public const int ActionBar_title = 27; - + // aapt resource value: 28 public const int ActionBar_titleTextStyle = 28; - + // aapt resource value: { 0x101013F } public static int[] ActionMenuItemView = new int[] { 16843071}; - + // aapt resource value: 0 public const int ActionMenuItemView_android_minWidth = 0; - + // aapt resource value: { 0xFFFFFFFF } public static int[] ActionMenuView = new int[] { -1}; - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } public static int[] ActionMode = new int[] { 2130903089, @@ -7531,36 +7654,36 @@ public partial class Styleable 2130903208, 2130903335, 2130903389}; - + // aapt resource value: 0 public const int ActionMode_background = 0; - + // aapt resource value: 1 public const int ActionMode_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionMode_closeItemLayout = 2; - + // aapt resource value: 3 public const int ActionMode_height = 3; - + // aapt resource value: 4 public const int ActionMode_subtitleTextStyle = 4; - + // aapt resource value: 5 public const int ActionMode_titleTextStyle = 5; - + // aapt resource value: { 0x7F03008A,0x7F0300B5 } public static int[] ActivityChooserView = new int[] { 2130903178, 2130903221}; - + // aapt resource value: 0 public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; - + // aapt resource value: 1 public const int ActivityChooserView_initialActivityCount = 1; - + // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } public static int[] AlertDialog = new int[] { 16842994, @@ -7570,28 +7693,28 @@ public partial class Styleable 2130903272, 2130903316, 2130903317}; - + // aapt resource value: 0 public const int AlertDialog_android_layout = 0; - + // aapt resource value: 1 public const int AlertDialog_buttonPanelSideLayout = 1; - + // aapt resource value: 2 public const int AlertDialog_listItemLayout = 2; - + // aapt resource value: 3 public const int AlertDialog_listLayout = 3; - + // aapt resource value: 4 public const int AlertDialog_multiChoiceItemLayout = 4; - + // aapt resource value: 5 public const int AlertDialog_showTitle = 5; - + // aapt resource value: 6 public const int AlertDialog_singleChoiceItemLayout = 6; - + // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } public static int[] AppBarLayout = new int[] { 16842964, @@ -7599,82 +7722,82 @@ public partial class Styleable 16844096, 2130903175, 2130903179}; - + // aapt resource value: { 0x7F03011E,0x7F03011F } public static int[] AppBarLayoutStates = new int[] { 2130903326, 2130903327}; - + // aapt resource value: 0 public const int AppBarLayoutStates_state_collapsed = 0; - + // aapt resource value: 1 public const int AppBarLayoutStates_state_collapsible = 1; - + // aapt resource value: 0 public const int AppBarLayout_android_background = 0; - + // aapt resource value: 2 public const int AppBarLayout_android_keyboardNavigationCluster = 2; - + // aapt resource value: 1 public const int AppBarLayout_android_touchscreenBlocksFocus = 1; - + // aapt resource value: 3 public const int AppBarLayout_elevation = 3; - + // aapt resource value: 4 public const int AppBarLayout_expanded = 4; - + // aapt resource value: { 0x7F0300C8,0x7F0300C9 } public static int[] AppBarLayout_Layout = new int[] { 2130903240, 2130903241}; - + // aapt resource value: 0 public const int AppBarLayout_Layout_layout_scrollFlags = 0; - + // aapt resource value: 1 public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; - + // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } public static int[] AppCompatImageView = new int[] { 16843033, 2130903323, 2130903377, 2130903378}; - + // aapt resource value: 0 public const int AppCompatImageView_android_src = 0; - + // aapt resource value: 1 public const int AppCompatImageView_srcCompat = 1; - + // aapt resource value: 2 public const int AppCompatImageView_tint = 2; - + // aapt resource value: 3 public const int AppCompatImageView_tintMode = 3; - + // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } public static int[] AppCompatSeekBar = new int[] { 16843074, 2130903374, 2130903375, 2130903376}; - + // aapt resource value: 0 public const int AppCompatSeekBar_android_thumb = 0; - + // aapt resource value: 1 public const int AppCompatSeekBar_tickMark = 1; - + // aapt resource value: 2 public const int AppCompatSeekBar_tickMarkTint = 2; - + // aapt resource value: 3 public const int AppCompatSeekBar_tickMarkTintMode = 3; - + // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } public static int[] AppCompatTextHelper = new int[] { 16842804, @@ -7684,28 +7807,28 @@ public partial class Styleable 16843120, 16843666, 16843667}; - + // aapt resource value: 2 public const int AppCompatTextHelper_android_drawableBottom = 2; - + // aapt resource value: 6 public const int AppCompatTextHelper_android_drawableEnd = 6; - + // aapt resource value: 3 public const int AppCompatTextHelper_android_drawableLeft = 3; - + // aapt resource value: 4 public const int AppCompatTextHelper_android_drawableRight = 4; - + // aapt resource value: 5 public const int AppCompatTextHelper_android_drawableStart = 5; - + // aapt resource value: 1 public const int AppCompatTextHelper_android_drawableTop = 1; - + // aapt resource value: 0 public const int AppCompatTextHelper_android_textAppearance = 0; - + // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } public static int[] AppCompatTextView = new int[] { 16842804, @@ -7716,31 +7839,31 @@ public partial class Styleable 2130903088, 2130903195, 2130903357}; - + // aapt resource value: 0 public const int AppCompatTextView_android_textAppearance = 0; - + // aapt resource value: 1 public const int AppCompatTextView_autoSizeMaxTextSize = 1; - + // aapt resource value: 2 public const int AppCompatTextView_autoSizeMinTextSize = 2; - + // aapt resource value: 3 public const int AppCompatTextView_autoSizePresetSizes = 3; - + // aapt resource value: 4 public const int AppCompatTextView_autoSizeStepGranularity = 4; - + // aapt resource value: 5 public const int AppCompatTextView_autoSizeTextType = 5; - + // aapt resource value: 6 public const int AppCompatTextView_fontFamily = 6; - + // aapt resource value: 7 public const int AppCompatTextView_textAllCaps = 7; - + // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } public static int[] AppCompatTheme = new int[] { 16842839, @@ -7862,364 +7985,364 @@ public partial class Styleable 2130903408, 2130903409, 2130903410}; - + // aapt resource value: 2 public const int AppCompatTheme_actionBarDivider = 2; - + // aapt resource value: 3 public const int AppCompatTheme_actionBarItemBackground = 3; - + // aapt resource value: 4 public const int AppCompatTheme_actionBarPopupTheme = 4; - + // aapt resource value: 5 public const int AppCompatTheme_actionBarSize = 5; - + // aapt resource value: 6 public const int AppCompatTheme_actionBarSplitStyle = 6; - + // aapt resource value: 7 public const int AppCompatTheme_actionBarStyle = 7; - + // aapt resource value: 8 public const int AppCompatTheme_actionBarTabBarStyle = 8; - + // aapt resource value: 9 public const int AppCompatTheme_actionBarTabStyle = 9; - + // aapt resource value: 10 public const int AppCompatTheme_actionBarTabTextStyle = 10; - + // aapt resource value: 11 public const int AppCompatTheme_actionBarTheme = 11; - + // aapt resource value: 12 public const int AppCompatTheme_actionBarWidgetTheme = 12; - + // aapt resource value: 13 public const int AppCompatTheme_actionButtonStyle = 13; - + // aapt resource value: 14 public const int AppCompatTheme_actionDropDownStyle = 14; - + // aapt resource value: 15 public const int AppCompatTheme_actionMenuTextAppearance = 15; - + // aapt resource value: 16 public const int AppCompatTheme_actionMenuTextColor = 16; - + // aapt resource value: 17 public const int AppCompatTheme_actionModeBackground = 17; - + // aapt resource value: 18 public const int AppCompatTheme_actionModeCloseButtonStyle = 18; - + // aapt resource value: 19 public const int AppCompatTheme_actionModeCloseDrawable = 19; - + // aapt resource value: 20 public const int AppCompatTheme_actionModeCopyDrawable = 20; - + // aapt resource value: 21 public const int AppCompatTheme_actionModeCutDrawable = 21; - + // aapt resource value: 22 public const int AppCompatTheme_actionModeFindDrawable = 22; - + // aapt resource value: 23 public const int AppCompatTheme_actionModePasteDrawable = 23; - + // aapt resource value: 24 public const int AppCompatTheme_actionModePopupWindowStyle = 24; - + // aapt resource value: 25 public const int AppCompatTheme_actionModeSelectAllDrawable = 25; - + // aapt resource value: 26 public const int AppCompatTheme_actionModeShareDrawable = 26; - + // aapt resource value: 27 public const int AppCompatTheme_actionModeSplitBackground = 27; - + // aapt resource value: 28 public const int AppCompatTheme_actionModeStyle = 28; - + // aapt resource value: 29 public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - + // aapt resource value: 30 public const int AppCompatTheme_actionOverflowButtonStyle = 30; - + // aapt resource value: 31 public const int AppCompatTheme_actionOverflowMenuStyle = 31; - + // aapt resource value: 32 public const int AppCompatTheme_activityChooserViewStyle = 32; - + // aapt resource value: 33 public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; - + // aapt resource value: 34 public const int AppCompatTheme_alertDialogCenterButtons = 34; - + // aapt resource value: 35 public const int AppCompatTheme_alertDialogStyle = 35; - + // aapt resource value: 36 public const int AppCompatTheme_alertDialogTheme = 36; - + // aapt resource value: 1 public const int AppCompatTheme_android_windowAnimationStyle = 1; - + // aapt resource value: 0 public const int AppCompatTheme_android_windowIsFloating = 0; - + // aapt resource value: 37 public const int AppCompatTheme_autoCompleteTextViewStyle = 37; - + // aapt resource value: 38 public const int AppCompatTheme_borderlessButtonStyle = 38; - + // aapt resource value: 39 public const int AppCompatTheme_buttonBarButtonStyle = 39; - + // aapt resource value: 40 public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - + // aapt resource value: 41 public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - + // aapt resource value: 42 public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - + // aapt resource value: 43 public const int AppCompatTheme_buttonBarStyle = 43; - + // aapt resource value: 44 public const int AppCompatTheme_buttonStyle = 44; - + // aapt resource value: 45 public const int AppCompatTheme_buttonStyleSmall = 45; - + // aapt resource value: 46 public const int AppCompatTheme_checkboxStyle = 46; - + // aapt resource value: 47 public const int AppCompatTheme_checkedTextViewStyle = 47; - + // aapt resource value: 48 public const int AppCompatTheme_colorAccent = 48; - + // aapt resource value: 49 public const int AppCompatTheme_colorBackgroundFloating = 49; - + // aapt resource value: 50 public const int AppCompatTheme_colorButtonNormal = 50; - + // aapt resource value: 51 public const int AppCompatTheme_colorControlActivated = 51; - + // aapt resource value: 52 public const int AppCompatTheme_colorControlHighlight = 52; - + // aapt resource value: 53 public const int AppCompatTheme_colorControlNormal = 53; - + // aapt resource value: 54 public const int AppCompatTheme_colorError = 54; - + // aapt resource value: 55 public const int AppCompatTheme_colorPrimary = 55; - + // aapt resource value: 56 public const int AppCompatTheme_colorPrimaryDark = 56; - + // aapt resource value: 57 public const int AppCompatTheme_colorSwitchThumbNormal = 57; - + // aapt resource value: 58 public const int AppCompatTheme_controlBackground = 58; - + // aapt resource value: 59 public const int AppCompatTheme_dialogPreferredPadding = 59; - + // aapt resource value: 60 public const int AppCompatTheme_dialogTheme = 60; - + // aapt resource value: 61 public const int AppCompatTheme_dividerHorizontal = 61; - + // aapt resource value: 62 public const int AppCompatTheme_dividerVertical = 62; - + // aapt resource value: 64 public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - + // aapt resource value: 63 public const int AppCompatTheme_dropDownListViewStyle = 63; - + // aapt resource value: 65 public const int AppCompatTheme_editTextBackground = 65; - + // aapt resource value: 66 public const int AppCompatTheme_editTextColor = 66; - + // aapt resource value: 67 public const int AppCompatTheme_editTextStyle = 67; - + // aapt resource value: 68 public const int AppCompatTheme_homeAsUpIndicator = 68; - + // aapt resource value: 69 public const int AppCompatTheme_imageButtonStyle = 69; - + // aapt resource value: 70 public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - + // aapt resource value: 71 public const int AppCompatTheme_listDividerAlertDialog = 71; - + // aapt resource value: 72 public const int AppCompatTheme_listMenuViewStyle = 72; - + // aapt resource value: 73 public const int AppCompatTheme_listPopupWindowStyle = 73; - + // aapt resource value: 74 public const int AppCompatTheme_listPreferredItemHeight = 74; - + // aapt resource value: 75 public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - + // aapt resource value: 76 public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - + // aapt resource value: 77 public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - + // aapt resource value: 78 public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - + // aapt resource value: 79 public const int AppCompatTheme_panelBackground = 79; - + // aapt resource value: 80 public const int AppCompatTheme_panelMenuListTheme = 80; - + // aapt resource value: 81 public const int AppCompatTheme_panelMenuListWidth = 81; - + // aapt resource value: 82 public const int AppCompatTheme_popupMenuStyle = 82; - + // aapt resource value: 83 public const int AppCompatTheme_popupWindowStyle = 83; - + // aapt resource value: 84 public const int AppCompatTheme_radioButtonStyle = 84; - + // aapt resource value: 85 public const int AppCompatTheme_ratingBarStyle = 85; - + // aapt resource value: 86 public const int AppCompatTheme_ratingBarStyleIndicator = 86; - + // aapt resource value: 87 public const int AppCompatTheme_ratingBarStyleSmall = 87; - + // aapt resource value: 88 public const int AppCompatTheme_searchViewStyle = 88; - + // aapt resource value: 89 public const int AppCompatTheme_seekBarStyle = 89; - + // aapt resource value: 90 public const int AppCompatTheme_selectableItemBackground = 90; - + // aapt resource value: 91 public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - + // aapt resource value: 92 public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - + // aapt resource value: 93 public const int AppCompatTheme_spinnerStyle = 93; - + // aapt resource value: 94 public const int AppCompatTheme_switchStyle = 94; - + // aapt resource value: 95 public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - + // aapt resource value: 96 public const int AppCompatTheme_textAppearanceListItem = 96; - + // aapt resource value: 97 public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - + // aapt resource value: 98 public const int AppCompatTheme_textAppearanceListItemSmall = 98; - + // aapt resource value: 99 public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - + // aapt resource value: 100 public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - + // aapt resource value: 101 public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - + // aapt resource value: 102 public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - + // aapt resource value: 103 public const int AppCompatTheme_textColorAlertDialogListItem = 103; - + // aapt resource value: 104 public const int AppCompatTheme_textColorSearchUrl = 104; - + // aapt resource value: 105 public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - + // aapt resource value: 106 public const int AppCompatTheme_toolbarStyle = 106; - + // aapt resource value: 107 public const int AppCompatTheme_tooltipForegroundColor = 107; - + // aapt resource value: 108 public const int AppCompatTheme_tooltipFrameBackground = 108; - + // aapt resource value: 109 public const int AppCompatTheme_windowActionBar = 109; - + // aapt resource value: 110 public const int AppCompatTheme_windowActionBarOverlay = 110; - + // aapt resource value: 111 public const int AppCompatTheme_windowActionModeOverlay = 111; - + // aapt resource value: 112 public const int AppCompatTheme_windowFixedHeightMajor = 112; - + // aapt resource value: 113 public const int AppCompatTheme_windowFixedHeightMinor = 113; - + // aapt resource value: 114 public const int AppCompatTheme_windowFixedWidthMajor = 114; - + // aapt resource value: 115 public const int AppCompatTheme_windowFixedWidthMinor = 115; - + // aapt resource value: 116 public const int AppCompatTheme_windowMinWidthMajor = 116; - + // aapt resource value: 117 public const int AppCompatTheme_windowMinWidthMinor = 117; - + // aapt resource value: 118 public const int AppCompatTheme_windowNoTitle = 118; - + // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } public static int[] BottomNavigationView = new int[] { 2130903175, @@ -8227,44 +8350,44 @@ public partial class Styleable 2130903225, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int BottomNavigationView_elevation = 0; - + // aapt resource value: 1 public const int BottomNavigationView_itemBackground = 1; - + // aapt resource value: 2 public const int BottomNavigationView_itemIconTint = 2; - + // aapt resource value: 3 public const int BottomNavigationView_itemTextColor = 3; - + // aapt resource value: 4 public const int BottomNavigationView_menu = 4; - + // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } public static int[] BottomSheetBehavior_Layout = new int[] { 2130903096, 2130903098, 2130903099}; - + // aapt resource value: 0 public const int BottomSheetBehavior_Layout_behavior_hideable = 0; - + // aapt resource value: 1 public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; - + // aapt resource value: 2 public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; - + // aapt resource value: { 0x7F030026 } public static int[] ButtonBarLayout = new int[] { 2130903078}; - + // aapt resource value: 0 public const int ButtonBarLayout_allowStacking = 0; - + // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } public static int[] CardView = new int[] { 16843071, @@ -8280,46 +8403,46 @@ public partial class Styleable 2130903150, 2130903151, 2130903152}; - + // aapt resource value: 1 public const int CardView_android_minHeight = 1; - + // aapt resource value: 0 public const int CardView_android_minWidth = 0; - + // aapt resource value: 2 public const int CardView_cardBackgroundColor = 2; - + // aapt resource value: 3 public const int CardView_cardCornerRadius = 3; - + // aapt resource value: 4 public const int CardView_cardElevation = 4; - + // aapt resource value: 5 public const int CardView_cardMaxElevation = 5; - + // aapt resource value: 6 public const int CardView_cardPreventCornerOverlap = 6; - + // aapt resource value: 7 public const int CardView_cardUseCompatPadding = 7; - + // aapt resource value: 8 public const int CardView_contentPadding = 8; - + // aapt resource value: 9 public const int CardView_contentPaddingBottom = 9; - + // aapt resource value: 10 public const int CardView_contentPaddingLeft = 10; - + // aapt resource value: 11 public const int CardView_contentPaddingRight = 11; - + // aapt resource value: 12 public const int CardView_contentPaddingTop = 12; - + // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } public static int[] CollapsingToolbarLayout = new int[] { 2130903127, @@ -8338,104 +8461,104 @@ public partial class Styleable 2130903379, 2130903380, 2130903390}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - + // aapt resource value: 2 public const int CollapsingToolbarLayout_contentScrim = 2; - + // aapt resource value: 3 public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - + // aapt resource value: 4 public const int CollapsingToolbarLayout_expandedTitleMargin = 4; - + // aapt resource value: 5 public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - + // aapt resource value: 6 public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - + // aapt resource value: 7 public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - + // aapt resource value: 8 public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; - + // aapt resource value: 9 public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; - + // aapt resource value: { 0x7F0300C3,0x7F0300C4 } public static int[] CollapsingToolbarLayout_Layout = new int[] { 2130903235, 2130903236}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; - + // aapt resource value: 10 public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; - + // aapt resource value: 11 public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; - + // aapt resource value: 12 public const int CollapsingToolbarLayout_statusBarScrim = 12; - + // aapt resource value: 13 public const int CollapsingToolbarLayout_title = 13; - + // aapt resource value: 14 public const int CollapsingToolbarLayout_titleEnabled = 14; - + // aapt resource value: 15 public const int CollapsingToolbarLayout_toolbarId = 15; - + // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } public static int[] ColorStateListItem = new int[] { 16843173, 16843551, 2130903079}; - + // aapt resource value: 2 public const int ColorStateListItem_alpha = 2; - + // aapt resource value: 1 public const int ColorStateListItem_android_alpha = 1; - + // aapt resource value: 0 public const int ColorStateListItem_android_color = 0; - + // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } public static int[] CompoundButton = new int[] { 16843015, 2130903113, 2130903114}; - + // aapt resource value: 0 public const int CompoundButton_android_button = 0; - + // aapt resource value: 1 public const int CompoundButton_buttonTint = 1; - + // aapt resource value: 2 public const int CompoundButton_buttonTintMode = 2; - + // aapt resource value: { 0x7F0300BD,0x7F030120 } public static int[] CoordinatorLayout = new int[] { 2130903229, 2130903328}; - + // aapt resource value: 0 public const int CoordinatorLayout_keylines = 0; - + // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } public static int[] CoordinatorLayout_Layout = new int[] { 16842931, @@ -8445,46 +8568,46 @@ public partial class Styleable 2130903237, 2130903238, 2130903239}; - + // aapt resource value: 0 public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - + // aapt resource value: 1 public const int CoordinatorLayout_Layout_layout_anchor = 1; - + // aapt resource value: 2 public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - + // aapt resource value: 3 public const int CoordinatorLayout_Layout_layout_behavior = 3; - + // aapt resource value: 4 public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; - + // aapt resource value: 5 public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - + // aapt resource value: 6 public const int CoordinatorLayout_Layout_layout_keyline = 6; - + // aapt resource value: 1 public const int CoordinatorLayout_statusBarBackground = 1; - + // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } public static int[] DesignTheme = new int[] { 2130903102, 2130903103, 2130903367}; - + // aapt resource value: 0 public const int DesignTheme_bottomSheetDialogTheme = 0; - + // aapt resource value: 1 public const int DesignTheme_bottomSheetStyle = 1; - + // aapt resource value: 2 public const int DesignTheme_textColorError = 2; - + // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } public static int[] DrawerArrowToggle = new int[] { 2130903081, @@ -8495,31 +8618,31 @@ public partial class Styleable 2130903205, 2130903319, 2130903370}; - + // aapt resource value: 0 public const int DrawerArrowToggle_arrowHeadLength = 0; - + // aapt resource value: 1 public const int DrawerArrowToggle_arrowShaftLength = 1; - + // aapt resource value: 2 public const int DrawerArrowToggle_barLength = 2; - + // aapt resource value: 3 public const int DrawerArrowToggle_color = 3; - + // aapt resource value: 4 public const int DrawerArrowToggle_drawableSize = 4; - + // aapt resource value: 5 public const int DrawerArrowToggle_gapBetweenBars = 5; - + // aapt resource value: 6 public const int DrawerArrowToggle_spinBars = 6; - + // aapt resource value: 7 public const int DrawerArrowToggle_thickness = 7; - + // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } public static int[] FloatingActionButton = new int[] { 2130903092, @@ -8530,38 +8653,38 @@ public partial class Styleable 2130903294, 2130903304, 2130903399}; - + // aapt resource value: 0 public const int FloatingActionButton_backgroundTint = 0; - + // aapt resource value: 1 public const int FloatingActionButton_backgroundTintMode = 1; - + // aapt resource value: { 0x7F030037 } public static int[] FloatingActionButton_Behavior_Layout = new int[] { 2130903095}; - + // aapt resource value: 0 public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - + // aapt resource value: 2 public const int FloatingActionButton_borderWidth = 2; - + // aapt resource value: 3 public const int FloatingActionButton_elevation = 3; - + // aapt resource value: 4 public const int FloatingActionButton_fabSize = 4; - + // aapt resource value: 5 public const int FloatingActionButton_pressedTranslationZ = 5; - + // aapt resource value: 6 public const int FloatingActionButton_rippleColor = 6; - + // aapt resource value: 7 public const int FloatingActionButton_useCompatPadding = 7; - + // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } public static int[] FontFamily = new int[] { 2130903196, @@ -8570,7 +8693,7 @@ public partial class Styleable 2130903199, 2130903200, 2130903201}; - + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } public static int[] FontFamilyFont = new int[] { 16844082, @@ -8579,58 +8702,58 @@ public partial class Styleable 2130903194, 2130903202, 2130903203}; - + // aapt resource value: 0 public const int FontFamilyFont_android_font = 0; - + // aapt resource value: 2 public const int FontFamilyFont_android_fontStyle = 2; - + // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; - + // aapt resource value: 3 public const int FontFamilyFont_font = 3; - + // aapt resource value: 4 public const int FontFamilyFont_fontStyle = 4; - + // aapt resource value: 5 public const int FontFamilyFont_fontWeight = 5; - + // aapt resource value: 0 public const int FontFamily_fontProviderAuthority = 0; - + // aapt resource value: 1 public const int FontFamily_fontProviderCerts = 1; - + // aapt resource value: 2 public const int FontFamily_fontProviderFetchStrategy = 2; - + // aapt resource value: 3 public const int FontFamily_fontProviderFetchTimeout = 3; - + // aapt resource value: 4 public const int FontFamily_fontProviderPackage = 4; - + // aapt resource value: 5 public const int FontFamily_fontProviderQuery = 5; - + // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } public static int[] ForegroundLinearLayout = new int[] { 16843017, 16843264, 2130903204}; - + // aapt resource value: 0 public const int ForegroundLinearLayout_android_foreground = 0; - + // aapt resource value: 1 public const int ForegroundLinearLayout_android_foregroundGravity = 1; - + // aapt resource value: 2 public const int ForegroundLinearLayout_foregroundInsidePadding = 2; - + // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } public static int[] LinearLayoutCompat = new int[] { 16842927, @@ -8642,83 +8765,83 @@ public partial class Styleable 2130903166, 2130903257, 2130903314}; - + // aapt resource value: 2 public const int LinearLayoutCompat_android_baselineAligned = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; - + // aapt resource value: 0 public const int LinearLayoutCompat_android_gravity = 0; - + // aapt resource value: 1 public const int LinearLayoutCompat_android_orientation = 1; - + // aapt resource value: 4 public const int LinearLayoutCompat_android_weightSum = 4; - + // aapt resource value: 5 public const int LinearLayoutCompat_divider = 5; - + // aapt resource value: 6 public const int LinearLayoutCompat_dividerPadding = 6; - + // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } public static int[] LinearLayoutCompat_Layout = new int[] { 16842931, 16842996, 16842997, 16843137}; - + // aapt resource value: 0 public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; - + // aapt resource value: 2 public const int LinearLayoutCompat_Layout_android_layout_height = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_Layout_android_layout_weight = 3; - + // aapt resource value: 1 public const int LinearLayoutCompat_Layout_android_layout_width = 1; - + // aapt resource value: 7 public const int LinearLayoutCompat_measureWithLargestChild = 7; - + // aapt resource value: 8 public const int LinearLayoutCompat_showDividers = 8; - + // aapt resource value: { 0x10102AC,0x10102AD } public static int[] ListPopupWindow = new int[] { 16843436, 16843437}; - + // aapt resource value: 0 public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; - + // aapt resource value: 1 public const int ListPopupWindow_android_dropDownVerticalOffset = 1; - + // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } public static int[] MediaRouteButton = new int[] { 16843071, 16843072, 2130903187, 2130903260}; - + // aapt resource value: 1 public const int MediaRouteButton_android_minHeight = 1; - + // aapt resource value: 0 public const int MediaRouteButton_android_minWidth = 0; - + // aapt resource value: 2 public const int MediaRouteButton_externalRouteEnabledDrawable = 2; - + // aapt resource value: 3 public const int MediaRouteButton_mediaRouteButtonTint = 3; - + // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } public static int[] MenuGroup = new int[] { 16842766, @@ -8727,25 +8850,25 @@ public partial class Styleable 16843230, 16843231, 16843232}; - + // aapt resource value: 5 public const int MenuGroup_android_checkableBehavior = 5; - + // aapt resource value: 0 public const int MenuGroup_android_enabled = 0; - + // aapt resource value: 1 public const int MenuGroup_android_id = 1; - + // aapt resource value: 3 public const int MenuGroup_android_menuCategory = 3; - + // aapt resource value: 4 public const int MenuGroup_android_orderInCategory = 4; - + // aapt resource value: 2 public const int MenuGroup_android_visible = 2; - + // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } public static int[] MenuItem = new int[] { 16842754, @@ -8771,76 +8894,76 @@ public partial class Styleable 2130903276, 2130903313, 2130903395}; - + // aapt resource value: 13 public const int MenuItem_actionLayout = 13; - + // aapt resource value: 14 public const int MenuItem_actionProviderClass = 14; - + // aapt resource value: 15 public const int MenuItem_actionViewClass = 15; - + // aapt resource value: 16 public const int MenuItem_alphabeticModifiers = 16; - + // aapt resource value: 9 public const int MenuItem_android_alphabeticShortcut = 9; - + // aapt resource value: 11 public const int MenuItem_android_checkable = 11; - + // aapt resource value: 3 public const int MenuItem_android_checked = 3; - + // aapt resource value: 1 public const int MenuItem_android_enabled = 1; - + // aapt resource value: 0 public const int MenuItem_android_icon = 0; - + // aapt resource value: 2 public const int MenuItem_android_id = 2; - + // aapt resource value: 5 public const int MenuItem_android_menuCategory = 5; - + // aapt resource value: 10 public const int MenuItem_android_numericShortcut = 10; - + // aapt resource value: 12 public const int MenuItem_android_onClick = 12; - + // aapt resource value: 6 public const int MenuItem_android_orderInCategory = 6; - + // aapt resource value: 7 public const int MenuItem_android_title = 7; - + // aapt resource value: 8 public const int MenuItem_android_titleCondensed = 8; - + // aapt resource value: 4 public const int MenuItem_android_visible = 4; - + // aapt resource value: 17 public const int MenuItem_contentDescription = 17; - + // aapt resource value: 18 public const int MenuItem_iconTint = 18; - + // aapt resource value: 19 public const int MenuItem_iconTintMode = 19; - + // aapt resource value: 20 public const int MenuItem_numericModifiers = 20; - + // aapt resource value: 21 public const int MenuItem_showAsAction = 21; - + // aapt resource value: 22 public const int MenuItem_tooltipText = 22; - + // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } public static int[] MenuView = new int[] { 16842926, @@ -8852,34 +8975,34 @@ public partial class Styleable 16843057, 2130903293, 2130903330}; - + // aapt resource value: 4 public const int MenuView_android_headerBackground = 4; - + // aapt resource value: 2 public const int MenuView_android_horizontalDivider = 2; - + // aapt resource value: 5 public const int MenuView_android_itemBackground = 5; - + // aapt resource value: 6 public const int MenuView_android_itemIconDisabledAlpha = 6; - + // aapt resource value: 1 public const int MenuView_android_itemTextAppearance = 1; - + // aapt resource value: 3 public const int MenuView_android_verticalDivider = 3; - + // aapt resource value: 0 public const int MenuView_android_windowAnimationStyle = 0; - + // aapt resource value: 7 public const int MenuView_preserveIconSpacing = 7; - + // aapt resource value: 8 public const int MenuView_subMenuArrow = 8; - + // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } public static int[] NavigationView = new int[] { 16842964, @@ -8892,70 +9015,70 @@ public partial class Styleable 2130903227, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int NavigationView_android_background = 0; - + // aapt resource value: 1 public const int NavigationView_android_fitsSystemWindows = 1; - + // aapt resource value: 2 public const int NavigationView_android_maxWidth = 2; - + // aapt resource value: 3 public const int NavigationView_elevation = 3; - + // aapt resource value: 4 public const int NavigationView_headerLayout = 4; - + // aapt resource value: 5 public const int NavigationView_itemBackground = 5; - + // aapt resource value: 6 public const int NavigationView_itemIconTint = 6; - + // aapt resource value: 7 public const int NavigationView_itemTextAppearance = 7; - + // aapt resource value: 8 public const int NavigationView_itemTextColor = 8; - + // aapt resource value: 9 public const int NavigationView_menu = 9; - + // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } public static int[] PopupWindow = new int[] { 16843126, 16843465, 2130903277}; - + // aapt resource value: { 0x7F03011D } public static int[] PopupWindowBackgroundState = new int[] { 2130903325}; - + // aapt resource value: 0 public const int PopupWindowBackgroundState_state_above_anchor = 0; - + // aapt resource value: 1 public const int PopupWindow_android_popupAnimationStyle = 1; - + // aapt resource value: 0 public const int PopupWindow_android_popupBackground = 0; - + // aapt resource value: 2 public const int PopupWindow_overlapAnchor = 2; - + // aapt resource value: { 0x7F0300EE,0x7F0300F1 } public static int[] RecycleListView = new int[] { 2130903278, 2130903281}; - + // aapt resource value: 0 public const int RecycleListView_paddingBottomNoButtons = 0; - + // aapt resource value: 1 public const int RecycleListView_paddingTopNoTitle = 1; - + // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } public static int[] RecyclerView = new int[] { 16842948, @@ -8969,54 +9092,54 @@ public partial class Styleable 2130903303, 2130903318, 2130903324}; - + // aapt resource value: 1 public const int RecyclerView_android_descendantFocusability = 1; - + // aapt resource value: 0 public const int RecyclerView_android_orientation = 0; - + // aapt resource value: 2 public const int RecyclerView_fastScrollEnabled = 2; - + // aapt resource value: 3 public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - + // aapt resource value: 4 public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - + // aapt resource value: 5 public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - + // aapt resource value: 6 public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - + // aapt resource value: 7 public const int RecyclerView_layoutManager = 7; - + // aapt resource value: 8 public const int RecyclerView_reverseLayout = 8; - + // aapt resource value: 9 public const int RecyclerView_spanCount = 9; - + // aapt resource value: 10 public const int RecyclerView_stackFromEnd = 10; - + // aapt resource value: { 0x7F0300B6 } public static int[] ScrimInsetsFrameLayout = new int[] { 2130903222}; - + // aapt resource value: 0 public const int ScrimInsetsFrameLayout_insetForeground = 0; - + // aapt resource value: { 0x7F030039 } public static int[] ScrollingViewBehavior_Layout = new int[] { 2130903097}; - + // aapt resource value: 0 public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; - + // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } public static int[] SearchView = new int[] { 16842970, @@ -9036,73 +9159,73 @@ public partial class Styleable 2130903331, 2130903336, 2130903400}; - + // aapt resource value: 0 public const int SearchView_android_focusable = 0; - + // aapt resource value: 3 public const int SearchView_android_imeOptions = 3; - + // aapt resource value: 2 public const int SearchView_android_inputType = 2; - + // aapt resource value: 1 public const int SearchView_android_maxWidth = 1; - + // aapt resource value: 4 public const int SearchView_closeIcon = 4; - + // aapt resource value: 5 public const int SearchView_commitIcon = 5; - + // aapt resource value: 6 public const int SearchView_defaultQueryHint = 6; - + // aapt resource value: 7 public const int SearchView_goIcon = 7; - + // aapt resource value: 8 public const int SearchView_iconifiedByDefault = 8; - + // aapt resource value: 9 public const int SearchView_layout = 9; - + // aapt resource value: 10 public const int SearchView_queryBackground = 10; - + // aapt resource value: 11 public const int SearchView_queryHint = 11; - + // aapt resource value: 12 public const int SearchView_searchHintIcon = 12; - + // aapt resource value: 13 public const int SearchView_searchIcon = 13; - + // aapt resource value: 14 public const int SearchView_submitBackground = 14; - + // aapt resource value: 15 public const int SearchView_suggestionRowLayout = 15; - + // aapt resource value: 16 public const int SearchView_voiceIcon = 16; - + // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } public static int[] SnackbarLayout = new int[] { 16843039, 2130903175, 2130903255}; - + // aapt resource value: 0 public const int SnackbarLayout_android_maxWidth = 0; - + // aapt resource value: 1 public const int SnackbarLayout_elevation = 1; - + // aapt resource value: 2 public const int SnackbarLayout_maxActionInlineWidth = 2; - + // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } public static int[] Spinner = new int[] { 16842930, @@ -9110,22 +9233,22 @@ public partial class Styleable 16843131, 16843362, 2130903291}; - + // aapt resource value: 3 public const int Spinner_android_dropDownWidth = 3; - + // aapt resource value: 0 public const int Spinner_android_entries = 0; - + // aapt resource value: 1 public const int Spinner_android_popupBackground = 1; - + // aapt resource value: 2 public const int Spinner_android_prompt = 2; - + // aapt resource value: 4 public const int Spinner_popupTheme = 4; - + // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } public static int[] SwitchCompat = new int[] { 16843044, @@ -9142,64 +9265,64 @@ public partial class Styleable 2130903396, 2130903397, 2130903398}; - + // aapt resource value: 1 public const int SwitchCompat_android_textOff = 1; - + // aapt resource value: 0 public const int SwitchCompat_android_textOn = 0; - + // aapt resource value: 2 public const int SwitchCompat_android_thumb = 2; - + // aapt resource value: 3 public const int SwitchCompat_showText = 3; - + // aapt resource value: 4 public const int SwitchCompat_splitTrack = 4; - + // aapt resource value: 5 public const int SwitchCompat_switchMinWidth = 5; - + // aapt resource value: 6 public const int SwitchCompat_switchPadding = 6; - + // aapt resource value: 7 public const int SwitchCompat_switchTextAppearance = 7; - + // aapt resource value: 8 public const int SwitchCompat_thumbTextPadding = 8; - + // aapt resource value: 9 public const int SwitchCompat_thumbTint = 9; - + // aapt resource value: 10 public const int SwitchCompat_thumbTintMode = 10; - + // aapt resource value: 11 public const int SwitchCompat_track = 11; - + // aapt resource value: 12 public const int SwitchCompat_trackTint = 12; - + // aapt resource value: 13 public const int SwitchCompat_trackTintMode = 13; - + // aapt resource value: { 0x1010002,0x10100F2,0x101014F } public static int[] TabItem = new int[] { 16842754, 16842994, 16843087}; - + // aapt resource value: 0 public const int TabItem_android_icon = 0; - + // aapt resource value: 1 public const int TabItem_android_layout = 1; - + // aapt resource value: 2 public const int TabItem_android_text = 2; - + // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } public static int[] TabLayout = new int[] { 2130903341, @@ -9218,55 +9341,55 @@ public partial class Styleable 2130903354, 2130903355, 2130903356}; - + // aapt resource value: 0 public const int TabLayout_tabBackground = 0; - + // aapt resource value: 1 public const int TabLayout_tabContentStart = 1; - + // aapt resource value: 2 public const int TabLayout_tabGravity = 2; - + // aapt resource value: 3 public const int TabLayout_tabIndicatorColor = 3; - + // aapt resource value: 4 public const int TabLayout_tabIndicatorHeight = 4; - + // aapt resource value: 5 public const int TabLayout_tabMaxWidth = 5; - + // aapt resource value: 6 public const int TabLayout_tabMinWidth = 6; - + // aapt resource value: 7 public const int TabLayout_tabMode = 7; - + // aapt resource value: 8 public const int TabLayout_tabPadding = 8; - + // aapt resource value: 9 public const int TabLayout_tabPaddingBottom = 9; - + // aapt resource value: 10 public const int TabLayout_tabPaddingEnd = 10; - + // aapt resource value: 11 public const int TabLayout_tabPaddingStart = 11; - + // aapt resource value: 12 public const int TabLayout_tabPaddingTop = 12; - + // aapt resource value: 13 public const int TabLayout_tabSelectedTextColor = 13; - + // aapt resource value: 14 public const int TabLayout_tabTextAppearance = 14; - + // aapt resource value: 15 public const int TabLayout_tabTextColor = 15; - + // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } public static int[] TextAppearance = new int[] { 16842901, @@ -9282,46 +9405,46 @@ public partial class Styleable 16843692, 2130903195, 2130903357}; - + // aapt resource value: 10 public const int TextAppearance_android_fontFamily = 10; - + // aapt resource value: 6 public const int TextAppearance_android_shadowColor = 6; - + // aapt resource value: 7 public const int TextAppearance_android_shadowDx = 7; - + // aapt resource value: 8 public const int TextAppearance_android_shadowDy = 8; - + // aapt resource value: 9 public const int TextAppearance_android_shadowRadius = 9; - + // aapt resource value: 3 public const int TextAppearance_android_textColor = 3; - + // aapt resource value: 4 public const int TextAppearance_android_textColorHint = 4; - + // aapt resource value: 5 public const int TextAppearance_android_textColorLink = 5; - + // aapt resource value: 0 public const int TextAppearance_android_textSize = 0; - + // aapt resource value: 2 public const int TextAppearance_android_textStyle = 2; - + // aapt resource value: 1 public const int TextAppearance_android_typeface = 1; - + // aapt resource value: 11 public const int TextAppearance_fontFamily = 11; - + // aapt resource value: 12 public const int TextAppearance_textAllCaps = 12; - + // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } public static int[] TextInputLayout = new int[] { 16842906, @@ -9340,55 +9463,55 @@ public partial class Styleable 2130903287, 2130903288, 2130903289}; - + // aapt resource value: 1 public const int TextInputLayout_android_hint = 1; - + // aapt resource value: 0 public const int TextInputLayout_android_textColorHint = 0; - + // aapt resource value: 2 public const int TextInputLayout_counterEnabled = 2; - + // aapt resource value: 3 public const int TextInputLayout_counterMaxLength = 3; - + // aapt resource value: 4 public const int TextInputLayout_counterOverflowTextAppearance = 4; - + // aapt resource value: 5 public const int TextInputLayout_counterTextAppearance = 5; - + // aapt resource value: 6 public const int TextInputLayout_errorEnabled = 6; - + // aapt resource value: 7 public const int TextInputLayout_errorTextAppearance = 7; - + // aapt resource value: 8 public const int TextInputLayout_hintAnimationEnabled = 8; - + // aapt resource value: 9 public const int TextInputLayout_hintEnabled = 9; - + // aapt resource value: 10 public const int TextInputLayout_hintTextAppearance = 10; - + // aapt resource value: 11 public const int TextInputLayout_passwordToggleContentDescription = 11; - + // aapt resource value: 12 public const int TextInputLayout_passwordToggleDrawable = 12; - + // aapt resource value: 13 public const int TextInputLayout_passwordToggleEnabled = 13; - + // aapt resource value: 14 public const int TextInputLayout_passwordToggleTint = 14; - + // aapt resource value: 15 public const int TextInputLayout_passwordToggleTintMode = 15; - + // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } public static int[] Toolbar = new int[] { 16842927, @@ -9420,94 +9543,94 @@ public partial class Styleable 2130903386, 2130903387, 2130903388}; - + // aapt resource value: 0 public const int Toolbar_android_gravity = 0; - + // aapt resource value: 1 public const int Toolbar_android_minHeight = 1; - + // aapt resource value: 2 public const int Toolbar_buttonGravity = 2; - + // aapt resource value: 3 public const int Toolbar_collapseContentDescription = 3; - + // aapt resource value: 4 public const int Toolbar_collapseIcon = 4; - + // aapt resource value: 5 public const int Toolbar_contentInsetEnd = 5; - + // aapt resource value: 6 public const int Toolbar_contentInsetEndWithActions = 6; - + // aapt resource value: 7 public const int Toolbar_contentInsetLeft = 7; - + // aapt resource value: 8 public const int Toolbar_contentInsetRight = 8; - + // aapt resource value: 9 public const int Toolbar_contentInsetStart = 9; - + // aapt resource value: 10 public const int Toolbar_contentInsetStartWithNavigation = 10; - + // aapt resource value: 11 public const int Toolbar_logo = 11; - + // aapt resource value: 12 public const int Toolbar_logoDescription = 12; - + // aapt resource value: 13 public const int Toolbar_maxButtonHeight = 13; - + // aapt resource value: 14 public const int Toolbar_navigationContentDescription = 14; - + // aapt resource value: 15 public const int Toolbar_navigationIcon = 15; - + // aapt resource value: 16 public const int Toolbar_popupTheme = 16; - + // aapt resource value: 17 public const int Toolbar_subtitle = 17; - + // aapt resource value: 18 public const int Toolbar_subtitleTextAppearance = 18; - + // aapt resource value: 19 public const int Toolbar_subtitleTextColor = 19; - + // aapt resource value: 20 public const int Toolbar_title = 20; - + // aapt resource value: 21 public const int Toolbar_titleMargin = 21; - + // aapt resource value: 22 public const int Toolbar_titleMarginBottom = 22; - + // aapt resource value: 23 public const int Toolbar_titleMarginEnd = 23; - + // aapt resource value: 26 public const int Toolbar_titleMargins = 26; - + // aapt resource value: 24 public const int Toolbar_titleMarginStart = 24; - + // aapt resource value: 25 public const int Toolbar_titleMarginTop = 25; - + // aapt resource value: 27 public const int Toolbar_titleTextAppearance = 27; - + // aapt resource value: 28 public const int Toolbar_titleTextColor = 28; - + // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } public static int[] View = new int[] { 16842752, @@ -9515,57 +9638,57 @@ public partial class Styleable 2130903279, 2130903280, 2130903369}; - + // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } public static int[] ViewBackgroundHelper = new int[] { 16842964, 2130903092, 2130903093}; - + // aapt resource value: 0 public const int ViewBackgroundHelper_android_background = 0; - + // aapt resource value: 1 public const int ViewBackgroundHelper_backgroundTint = 1; - + // aapt resource value: 2 public const int ViewBackgroundHelper_backgroundTintMode = 2; - + // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } public static int[] ViewStubCompat = new int[] { 16842960, 16842994, 16842995}; - + // aapt resource value: 0 public const int ViewStubCompat_android_id = 0; - + // aapt resource value: 2 public const int ViewStubCompat_android_inflatedId = 2; - + // aapt resource value: 1 public const int ViewStubCompat_android_layout = 1; - + // aapt resource value: 1 public const int View_android_focusable = 1; - + // aapt resource value: 0 public const int View_android_theme = 0; - + // aapt resource value: 2 public const int View_paddingEnd = 2; - + // aapt resource value: 3 public const int View_paddingStart = 3; - + // aapt resource value: 4 public const int View_theme = 4; - + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Styleable() { } From b10108ac6e7ef54f91216abbf7f235081904184a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 08:58:06 -0700 Subject: [PATCH 473/499] revert change to Resource.designer.cs --- .../Resources/Resource.designer.cs | 4799 ++++++++--------- 1 file changed, 2338 insertions(+), 2461 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index c57802dc..046c4004 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -2,6 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,143 +13,19 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.123")] + + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] public partial class Resource { - + static Resource() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + public static void UpdateIdValues() { - global::LaunchDarkly.Sdk.Client.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; - global::LaunchDarkly.Sdk.Client.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; - global::LaunchDarkly.Sdk.Client.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; - global::LaunchDarkly.Sdk.Client.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; - global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; - global::LaunchDarkly.Sdk.Client.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; - global::LaunchDarkly.Sdk.Client.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; - global::LaunchDarkly.Sdk.Client.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::LaunchDarkly.Sdk.Client.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; - global::LaunchDarkly.Sdk.Client.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; - global::LaunchDarkly.Sdk.Client.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; - global::LaunchDarkly.Sdk.Client.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; - global::LaunchDarkly.Sdk.Client.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; - global::LaunchDarkly.Sdk.Client.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; - global::LaunchDarkly.Sdk.Client.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; - global::LaunchDarkly.Sdk.Client.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; - global::LaunchDarkly.Sdk.Client.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; - global::LaunchDarkly.Sdk.Client.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; - global::LaunchDarkly.Sdk.Client.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; - global::LaunchDarkly.Sdk.Client.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; - global::LaunchDarkly.Sdk.Client.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; - global::LaunchDarkly.Sdk.Client.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; - global::LaunchDarkly.Sdk.Client.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; - global::LaunchDarkly.Sdk.Client.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; - global::LaunchDarkly.Sdk.Client.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; - global::LaunchDarkly.Sdk.Client.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; - global::LaunchDarkly.Sdk.Client.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; - global::LaunchDarkly.Sdk.Client.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; - global::LaunchDarkly.Sdk.Client.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; - global::LaunchDarkly.Sdk.Client.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; - global::LaunchDarkly.Sdk.Client.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; - global::LaunchDarkly.Sdk.Client.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; - global::LaunchDarkly.Sdk.Client.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; - global::LaunchDarkly.Sdk.Client.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; - global::LaunchDarkly.Sdk.Client.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; - global::LaunchDarkly.Sdk.Client.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::LaunchDarkly.Sdk.Client.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; - global::LaunchDarkly.Sdk.Client.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::LaunchDarkly.Sdk.Client.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; - global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::LaunchDarkly.Sdk.Client.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::LaunchDarkly.Sdk.Client.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; @@ -2136,5379 +2013,5379 @@ public static void UpdateIdValues() global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; } - + public partial class Animation { - + // aapt resource value: 0x7F010000 public const int abc_fade_in = 2130771968; - + // aapt resource value: 0x7F010001 public const int abc_fade_out = 2130771969; - + // aapt resource value: 0x7F010002 public const int abc_grow_fade_in_from_bottom = 2130771970; - + // aapt resource value: 0x7F010003 public const int abc_popup_enter = 2130771971; - + // aapt resource value: 0x7F010004 public const int abc_popup_exit = 2130771972; - + // aapt resource value: 0x7F010005 public const int abc_shrink_fade_out_from_bottom = 2130771973; - + // aapt resource value: 0x7F010006 public const int abc_slide_in_bottom = 2130771974; - + // aapt resource value: 0x7F010007 public const int abc_slide_in_top = 2130771975; - + // aapt resource value: 0x7F010008 public const int abc_slide_out_bottom = 2130771976; - + // aapt resource value: 0x7F010009 public const int abc_slide_out_top = 2130771977; - + // aapt resource value: 0x7F01000A public const int design_bottom_sheet_slide_in = 2130771978; - + // aapt resource value: 0x7F01000B public const int design_bottom_sheet_slide_out = 2130771979; - + // aapt resource value: 0x7F01000C public const int design_snackbar_in = 2130771980; - + // aapt resource value: 0x7F01000D public const int design_snackbar_out = 2130771981; - + // aapt resource value: 0x7F01000E public const int EnterFromLeft = 2130771982; - + // aapt resource value: 0x7F01000F public const int EnterFromRight = 2130771983; - + // aapt resource value: 0x7F010010 public const int ExitToLeft = 2130771984; - + // aapt resource value: 0x7F010011 public const int ExitToRight = 2130771985; - + // aapt resource value: 0x7F010012 public const int tooltip_enter = 2130771986; - + // aapt resource value: 0x7F010013 public const int tooltip_exit = 2130771987; - + static Animation() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animation() { } } - + public partial class Animator { - + // aapt resource value: 0x7F020000 public const int design_appbar_state_list_animator = 2130837504; - + static Animator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animator() { } } - + public partial class Attribute { - + // aapt resource value: 0x7F030000 public const int actionBarDivider = 2130903040; - + // aapt resource value: 0x7F030001 public const int actionBarItemBackground = 2130903041; - + // aapt resource value: 0x7F030002 public const int actionBarPopupTheme = 2130903042; - + // aapt resource value: 0x7F030003 public const int actionBarSize = 2130903043; - + // aapt resource value: 0x7F030004 public const int actionBarSplitStyle = 2130903044; - + // aapt resource value: 0x7F030005 public const int actionBarStyle = 2130903045; - + // aapt resource value: 0x7F030006 public const int actionBarTabBarStyle = 2130903046; - + // aapt resource value: 0x7F030007 public const int actionBarTabStyle = 2130903047; - + // aapt resource value: 0x7F030008 public const int actionBarTabTextStyle = 2130903048; - + // aapt resource value: 0x7F030009 public const int actionBarTheme = 2130903049; - + // aapt resource value: 0x7F03000A public const int actionBarWidgetTheme = 2130903050; - + // aapt resource value: 0x7F03000B public const int actionButtonStyle = 2130903051; - + // aapt resource value: 0x7F03000C public const int actionDropDownStyle = 2130903052; - + // aapt resource value: 0x7F03000D public const int actionLayout = 2130903053; - + // aapt resource value: 0x7F03000E public const int actionMenuTextAppearance = 2130903054; - + // aapt resource value: 0x7F03000F public const int actionMenuTextColor = 2130903055; - + // aapt resource value: 0x7F030010 public const int actionModeBackground = 2130903056; - + // aapt resource value: 0x7F030011 public const int actionModeCloseButtonStyle = 2130903057; - + // aapt resource value: 0x7F030012 public const int actionModeCloseDrawable = 2130903058; - + // aapt resource value: 0x7F030013 public const int actionModeCopyDrawable = 2130903059; - + // aapt resource value: 0x7F030014 public const int actionModeCutDrawable = 2130903060; - + // aapt resource value: 0x7F030015 public const int actionModeFindDrawable = 2130903061; - + // aapt resource value: 0x7F030016 public const int actionModePasteDrawable = 2130903062; - + // aapt resource value: 0x7F030017 public const int actionModePopupWindowStyle = 2130903063; - + // aapt resource value: 0x7F030018 public const int actionModeSelectAllDrawable = 2130903064; - + // aapt resource value: 0x7F030019 public const int actionModeShareDrawable = 2130903065; - + // aapt resource value: 0x7F03001A public const int actionModeSplitBackground = 2130903066; - + // aapt resource value: 0x7F03001B public const int actionModeStyle = 2130903067; - + // aapt resource value: 0x7F03001C public const int actionModeWebSearchDrawable = 2130903068; - + // aapt resource value: 0x7F03001D public const int actionOverflowButtonStyle = 2130903069; - + // aapt resource value: 0x7F03001E public const int actionOverflowMenuStyle = 2130903070; - + // aapt resource value: 0x7F03001F public const int actionProviderClass = 2130903071; - + // aapt resource value: 0x7F030020 public const int actionViewClass = 2130903072; - + // aapt resource value: 0x7F030021 public const int activityChooserViewStyle = 2130903073; - + // aapt resource value: 0x7F030022 public const int alertDialogButtonGroupStyle = 2130903074; - + // aapt resource value: 0x7F030023 public const int alertDialogCenterButtons = 2130903075; - + // aapt resource value: 0x7F030024 public const int alertDialogStyle = 2130903076; - + // aapt resource value: 0x7F030025 public const int alertDialogTheme = 2130903077; - + // aapt resource value: 0x7F030026 public const int allowStacking = 2130903078; - + // aapt resource value: 0x7F030027 public const int alpha = 2130903079; - + // aapt resource value: 0x7F030028 public const int alphabeticModifiers = 2130903080; - + // aapt resource value: 0x7F030029 public const int arrowHeadLength = 2130903081; - + // aapt resource value: 0x7F03002A public const int arrowShaftLength = 2130903082; - + // aapt resource value: 0x7F03002B public const int autoCompleteTextViewStyle = 2130903083; - + // aapt resource value: 0x7F03002C public const int autoSizeMaxTextSize = 2130903084; - + // aapt resource value: 0x7F03002D public const int autoSizeMinTextSize = 2130903085; - + // aapt resource value: 0x7F03002E public const int autoSizePresetSizes = 2130903086; - + // aapt resource value: 0x7F03002F public const int autoSizeStepGranularity = 2130903087; - + // aapt resource value: 0x7F030030 public const int autoSizeTextType = 2130903088; - + // aapt resource value: 0x7F030031 public const int background = 2130903089; - + // aapt resource value: 0x7F030032 public const int backgroundSplit = 2130903090; - + // aapt resource value: 0x7F030033 public const int backgroundStacked = 2130903091; - + // aapt resource value: 0x7F030034 public const int backgroundTint = 2130903092; - + // aapt resource value: 0x7F030035 public const int backgroundTintMode = 2130903093; - + // aapt resource value: 0x7F030036 public const int barLength = 2130903094; - + // aapt resource value: 0x7F030037 public const int behavior_autoHide = 2130903095; - + // aapt resource value: 0x7F030038 public const int behavior_hideable = 2130903096; - + // aapt resource value: 0x7F030039 public const int behavior_overlapTop = 2130903097; - + // aapt resource value: 0x7F03003A public const int behavior_peekHeight = 2130903098; - + // aapt resource value: 0x7F03003B public const int behavior_skipCollapsed = 2130903099; - + // aapt resource value: 0x7F03003D public const int borderlessButtonStyle = 2130903101; - + // aapt resource value: 0x7F03003C public const int borderWidth = 2130903100; - + // aapt resource value: 0x7F03003E public const int bottomSheetDialogTheme = 2130903102; - + // aapt resource value: 0x7F03003F public const int bottomSheetStyle = 2130903103; - + // aapt resource value: 0x7F030040 public const int buttonBarButtonStyle = 2130903104; - + // aapt resource value: 0x7F030041 public const int buttonBarNegativeButtonStyle = 2130903105; - + // aapt resource value: 0x7F030042 public const int buttonBarNeutralButtonStyle = 2130903106; - + // aapt resource value: 0x7F030043 public const int buttonBarPositiveButtonStyle = 2130903107; - + // aapt resource value: 0x7F030044 public const int buttonBarStyle = 2130903108; - + // aapt resource value: 0x7F030045 public const int buttonGravity = 2130903109; - + // aapt resource value: 0x7F030046 public const int buttonPanelSideLayout = 2130903110; - + // aapt resource value: 0x7F030047 public const int buttonStyle = 2130903111; - + // aapt resource value: 0x7F030048 public const int buttonStyleSmall = 2130903112; - + // aapt resource value: 0x7F030049 public const int buttonTint = 2130903113; - + // aapt resource value: 0x7F03004A public const int buttonTintMode = 2130903114; - + // aapt resource value: 0x7F03004B public const int cardBackgroundColor = 2130903115; - + // aapt resource value: 0x7F03004C public const int cardCornerRadius = 2130903116; - + // aapt resource value: 0x7F03004D public const int cardElevation = 2130903117; - + // aapt resource value: 0x7F03004E public const int cardMaxElevation = 2130903118; - + // aapt resource value: 0x7F03004F public const int cardPreventCornerOverlap = 2130903119; - + // aapt resource value: 0x7F030050 public const int cardUseCompatPadding = 2130903120; - + // aapt resource value: 0x7F030051 public const int checkboxStyle = 2130903121; - + // aapt resource value: 0x7F030052 public const int checkedTextViewStyle = 2130903122; - + // aapt resource value: 0x7F030053 public const int closeIcon = 2130903123; - + // aapt resource value: 0x7F030054 public const int closeItemLayout = 2130903124; - + // aapt resource value: 0x7F030055 public const int collapseContentDescription = 2130903125; - + // aapt resource value: 0x7F030057 public const int collapsedTitleGravity = 2130903127; - + // aapt resource value: 0x7F030058 public const int collapsedTitleTextAppearance = 2130903128; - + // aapt resource value: 0x7F030056 public const int collapseIcon = 2130903126; - + // aapt resource value: 0x7F030059 public const int color = 2130903129; - + // aapt resource value: 0x7F03005A public const int colorAccent = 2130903130; - + // aapt resource value: 0x7F03005B public const int colorBackgroundFloating = 2130903131; - + // aapt resource value: 0x7F03005C public const int colorButtonNormal = 2130903132; - + // aapt resource value: 0x7F03005D public const int colorControlActivated = 2130903133; - + // aapt resource value: 0x7F03005E public const int colorControlHighlight = 2130903134; - + // aapt resource value: 0x7F03005F public const int colorControlNormal = 2130903135; - + // aapt resource value: 0x7F030060 public const int colorError = 2130903136; - + // aapt resource value: 0x7F030061 public const int colorPrimary = 2130903137; - + // aapt resource value: 0x7F030062 public const int colorPrimaryDark = 2130903138; - + // aapt resource value: 0x7F030063 public const int colorSwitchThumbNormal = 2130903139; - + // aapt resource value: 0x7F030064 public const int commitIcon = 2130903140; - + // aapt resource value: 0x7F030065 public const int contentDescription = 2130903141; - + // aapt resource value: 0x7F030066 public const int contentInsetEnd = 2130903142; - + // aapt resource value: 0x7F030067 public const int contentInsetEndWithActions = 2130903143; - + // aapt resource value: 0x7F030068 public const int contentInsetLeft = 2130903144; - + // aapt resource value: 0x7F030069 public const int contentInsetRight = 2130903145; - + // aapt resource value: 0x7F03006A public const int contentInsetStart = 2130903146; - + // aapt resource value: 0x7F03006B public const int contentInsetStartWithNavigation = 2130903147; - + // aapt resource value: 0x7F03006C public const int contentPadding = 2130903148; - + // aapt resource value: 0x7F03006D public const int contentPaddingBottom = 2130903149; - + // aapt resource value: 0x7F03006E public const int contentPaddingLeft = 2130903150; - + // aapt resource value: 0x7F03006F public const int contentPaddingRight = 2130903151; - + // aapt resource value: 0x7F030070 public const int contentPaddingTop = 2130903152; - + // aapt resource value: 0x7F030071 public const int contentScrim = 2130903153; - + // aapt resource value: 0x7F030072 public const int controlBackground = 2130903154; - + // aapt resource value: 0x7F030073 public const int counterEnabled = 2130903155; - + // aapt resource value: 0x7F030074 public const int counterMaxLength = 2130903156; - + // aapt resource value: 0x7F030075 public const int counterOverflowTextAppearance = 2130903157; - + // aapt resource value: 0x7F030076 public const int counterTextAppearance = 2130903158; - + // aapt resource value: 0x7F030077 public const int customNavigationLayout = 2130903159; - + // aapt resource value: 0x7F030078 public const int defaultQueryHint = 2130903160; - + // aapt resource value: 0x7F030079 public const int dialogPreferredPadding = 2130903161; - + // aapt resource value: 0x7F03007A public const int dialogTheme = 2130903162; - + // aapt resource value: 0x7F03007B public const int displayOptions = 2130903163; - + // aapt resource value: 0x7F03007C public const int divider = 2130903164; - + // aapt resource value: 0x7F03007D public const int dividerHorizontal = 2130903165; - + // aapt resource value: 0x7F03007E public const int dividerPadding = 2130903166; - + // aapt resource value: 0x7F03007F public const int dividerVertical = 2130903167; - + // aapt resource value: 0x7F030080 public const int drawableSize = 2130903168; - + // aapt resource value: 0x7F030081 public const int drawerArrowStyle = 2130903169; - + // aapt resource value: 0x7F030083 public const int dropdownListPreferredItemHeight = 2130903171; - + // aapt resource value: 0x7F030082 public const int dropDownListViewStyle = 2130903170; - + // aapt resource value: 0x7F030084 public const int editTextBackground = 2130903172; - + // aapt resource value: 0x7F030085 public const int editTextColor = 2130903173; - + // aapt resource value: 0x7F030086 public const int editTextStyle = 2130903174; - + // aapt resource value: 0x7F030087 public const int elevation = 2130903175; - + // aapt resource value: 0x7F030088 public const int errorEnabled = 2130903176; - + // aapt resource value: 0x7F030089 public const int errorTextAppearance = 2130903177; - + // aapt resource value: 0x7F03008A public const int expandActivityOverflowButtonDrawable = 2130903178; - + // aapt resource value: 0x7F03008B public const int expanded = 2130903179; - + // aapt resource value: 0x7F03008C public const int expandedTitleGravity = 2130903180; - + // aapt resource value: 0x7F03008D public const int expandedTitleMargin = 2130903181; - + // aapt resource value: 0x7F03008E public const int expandedTitleMarginBottom = 2130903182; - + // aapt resource value: 0x7F03008F public const int expandedTitleMarginEnd = 2130903183; - + // aapt resource value: 0x7F030090 public const int expandedTitleMarginStart = 2130903184; - + // aapt resource value: 0x7F030091 public const int expandedTitleMarginTop = 2130903185; - + // aapt resource value: 0x7F030092 public const int expandedTitleTextAppearance = 2130903186; - + // aapt resource value: 0x7F030093 public const int externalRouteEnabledDrawable = 2130903187; - + // aapt resource value: 0x7F030094 public const int fabSize = 2130903188; - + // aapt resource value: 0x7F030095 public const int fastScrollEnabled = 2130903189; - + // aapt resource value: 0x7F030096 public const int fastScrollHorizontalThumbDrawable = 2130903190; - + // aapt resource value: 0x7F030097 public const int fastScrollHorizontalTrackDrawable = 2130903191; - + // aapt resource value: 0x7F030098 public const int fastScrollVerticalThumbDrawable = 2130903192; - + // aapt resource value: 0x7F030099 public const int fastScrollVerticalTrackDrawable = 2130903193; - + // aapt resource value: 0x7F03009A public const int font = 2130903194; - + // aapt resource value: 0x7F03009B public const int fontFamily = 2130903195; - + // aapt resource value: 0x7F03009C public const int fontProviderAuthority = 2130903196; - + // aapt resource value: 0x7F03009D public const int fontProviderCerts = 2130903197; - + // aapt resource value: 0x7F03009E public const int fontProviderFetchStrategy = 2130903198; - + // aapt resource value: 0x7F03009F public const int fontProviderFetchTimeout = 2130903199; - + // aapt resource value: 0x7F0300A0 public const int fontProviderPackage = 2130903200; - + // aapt resource value: 0x7F0300A1 public const int fontProviderQuery = 2130903201; - + // aapt resource value: 0x7F0300A2 public const int fontStyle = 2130903202; - + // aapt resource value: 0x7F0300A3 public const int fontWeight = 2130903203; - + // aapt resource value: 0x7F0300A4 public const int foregroundInsidePadding = 2130903204; - + // aapt resource value: 0x7F0300A5 public const int gapBetweenBars = 2130903205; - + // aapt resource value: 0x7F0300A6 public const int goIcon = 2130903206; - + // aapt resource value: 0x7F0300A7 public const int headerLayout = 2130903207; - + // aapt resource value: 0x7F0300A8 public const int height = 2130903208; - + // aapt resource value: 0x7F0300A9 public const int hideOnContentScroll = 2130903209; - + // aapt resource value: 0x7F0300AA public const int hintAnimationEnabled = 2130903210; - + // aapt resource value: 0x7F0300AB public const int hintEnabled = 2130903211; - + // aapt resource value: 0x7F0300AC public const int hintTextAppearance = 2130903212; - + // aapt resource value: 0x7F0300AD public const int homeAsUpIndicator = 2130903213; - + // aapt resource value: 0x7F0300AE public const int homeLayout = 2130903214; - + // aapt resource value: 0x7F0300AF public const int icon = 2130903215; - + // aapt resource value: 0x7F0300B2 public const int iconifiedByDefault = 2130903218; - + // aapt resource value: 0x7F0300B0 public const int iconTint = 2130903216; - + // aapt resource value: 0x7F0300B1 public const int iconTintMode = 2130903217; - + // aapt resource value: 0x7F0300B3 public const int imageButtonStyle = 2130903219; - + // aapt resource value: 0x7F0300B4 public const int indeterminateProgressStyle = 2130903220; - + // aapt resource value: 0x7F0300B5 public const int initialActivityCount = 2130903221; - + // aapt resource value: 0x7F0300B6 public const int insetForeground = 2130903222; - + // aapt resource value: 0x7F0300B7 public const int isLightTheme = 2130903223; - + // aapt resource value: 0x7F0300B8 public const int itemBackground = 2130903224; - + // aapt resource value: 0x7F0300B9 public const int itemIconTint = 2130903225; - + // aapt resource value: 0x7F0300BA public const int itemPadding = 2130903226; - + // aapt resource value: 0x7F0300BB public const int itemTextAppearance = 2130903227; - + // aapt resource value: 0x7F0300BC public const int itemTextColor = 2130903228; - + // aapt resource value: 0x7F0300BD public const int keylines = 2130903229; - + // aapt resource value: 0x7F0300BE public const int layout = 2130903230; - + // aapt resource value: 0x7F0300BF public const int layoutManager = 2130903231; - + // aapt resource value: 0x7F0300C0 public const int layout_anchor = 2130903232; - + // aapt resource value: 0x7F0300C1 public const int layout_anchorGravity = 2130903233; - + // aapt resource value: 0x7F0300C2 public const int layout_behavior = 2130903234; - + // aapt resource value: 0x7F0300C3 public const int layout_collapseMode = 2130903235; - + // aapt resource value: 0x7F0300C4 public const int layout_collapseParallaxMultiplier = 2130903236; - + // aapt resource value: 0x7F0300C5 public const int layout_dodgeInsetEdges = 2130903237; - + // aapt resource value: 0x7F0300C6 public const int layout_insetEdge = 2130903238; - + // aapt resource value: 0x7F0300C7 public const int layout_keyline = 2130903239; - + // aapt resource value: 0x7F0300C8 public const int layout_scrollFlags = 2130903240; - + // aapt resource value: 0x7F0300C9 public const int layout_scrollInterpolator = 2130903241; - + // aapt resource value: 0x7F0300CA public const int listChoiceBackgroundIndicator = 2130903242; - + // aapt resource value: 0x7F0300CB public const int listDividerAlertDialog = 2130903243; - + // aapt resource value: 0x7F0300CC public const int listItemLayout = 2130903244; - + // aapt resource value: 0x7F0300CD public const int listLayout = 2130903245; - + // aapt resource value: 0x7F0300CE public const int listMenuViewStyle = 2130903246; - + // aapt resource value: 0x7F0300CF public const int listPopupWindowStyle = 2130903247; - + // aapt resource value: 0x7F0300D0 public const int listPreferredItemHeight = 2130903248; - + // aapt resource value: 0x7F0300D1 public const int listPreferredItemHeightLarge = 2130903249; - + // aapt resource value: 0x7F0300D2 public const int listPreferredItemHeightSmall = 2130903250; - + // aapt resource value: 0x7F0300D3 public const int listPreferredItemPaddingLeft = 2130903251; - + // aapt resource value: 0x7F0300D4 public const int listPreferredItemPaddingRight = 2130903252; - + // aapt resource value: 0x7F0300D5 public const int logo = 2130903253; - + // aapt resource value: 0x7F0300D6 public const int logoDescription = 2130903254; - + // aapt resource value: 0x7F0300D7 public const int maxActionInlineWidth = 2130903255; - + // aapt resource value: 0x7F0300D8 public const int maxButtonHeight = 2130903256; - + // aapt resource value: 0x7F0300D9 public const int measureWithLargestChild = 2130903257; - + // aapt resource value: 0x7F0300DA public const int mediaRouteAudioTrackDrawable = 2130903258; - + // aapt resource value: 0x7F0300DB public const int mediaRouteButtonStyle = 2130903259; - + // aapt resource value: 0x7F0300DC public const int mediaRouteButtonTint = 2130903260; - + // aapt resource value: 0x7F0300DD public const int mediaRouteCloseDrawable = 2130903261; - + // aapt resource value: 0x7F0300DE public const int mediaRouteControlPanelThemeOverlay = 2130903262; - + // aapt resource value: 0x7F0300DF public const int mediaRouteDefaultIconDrawable = 2130903263; - + // aapt resource value: 0x7F0300E0 public const int mediaRoutePauseDrawable = 2130903264; - + // aapt resource value: 0x7F0300E1 public const int mediaRoutePlayDrawable = 2130903265; - + // aapt resource value: 0x7F0300E2 public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - + // aapt resource value: 0x7F0300E3 public const int mediaRouteSpeakerIconDrawable = 2130903267; - + // aapt resource value: 0x7F0300E4 public const int mediaRouteStopDrawable = 2130903268; - + // aapt resource value: 0x7F0300E5 public const int mediaRouteTheme = 2130903269; - + // aapt resource value: 0x7F0300E6 public const int mediaRouteTvIconDrawable = 2130903270; - + // aapt resource value: 0x7F0300E7 public const int menu = 2130903271; - + // aapt resource value: 0x7F0300E8 public const int multiChoiceItemLayout = 2130903272; - + // aapt resource value: 0x7F0300E9 public const int navigationContentDescription = 2130903273; - + // aapt resource value: 0x7F0300EA public const int navigationIcon = 2130903274; - + // aapt resource value: 0x7F0300EB public const int navigationMode = 2130903275; - + // aapt resource value: 0x7F0300EC public const int numericModifiers = 2130903276; - + // aapt resource value: 0x7F0300ED public const int overlapAnchor = 2130903277; - + // aapt resource value: 0x7F0300EE public const int paddingBottomNoButtons = 2130903278; - + // aapt resource value: 0x7F0300EF public const int paddingEnd = 2130903279; - + // aapt resource value: 0x7F0300F0 public const int paddingStart = 2130903280; - + // aapt resource value: 0x7F0300F1 public const int paddingTopNoTitle = 2130903281; - + // aapt resource value: 0x7F0300F2 public const int panelBackground = 2130903282; - + // aapt resource value: 0x7F0300F3 public const int panelMenuListTheme = 2130903283; - + // aapt resource value: 0x7F0300F4 public const int panelMenuListWidth = 2130903284; - + // aapt resource value: 0x7F0300F5 public const int passwordToggleContentDescription = 2130903285; - + // aapt resource value: 0x7F0300F6 public const int passwordToggleDrawable = 2130903286; - + // aapt resource value: 0x7F0300F7 public const int passwordToggleEnabled = 2130903287; - + // aapt resource value: 0x7F0300F8 public const int passwordToggleTint = 2130903288; - + // aapt resource value: 0x7F0300F9 public const int passwordToggleTintMode = 2130903289; - + // aapt resource value: 0x7F0300FA public const int popupMenuStyle = 2130903290; - + // aapt resource value: 0x7F0300FB public const int popupTheme = 2130903291; - + // aapt resource value: 0x7F0300FC public const int popupWindowStyle = 2130903292; - + // aapt resource value: 0x7F0300FD public const int preserveIconSpacing = 2130903293; - + // aapt resource value: 0x7F0300FE public const int pressedTranslationZ = 2130903294; - + // aapt resource value: 0x7F0300FF public const int progressBarPadding = 2130903295; - + // aapt resource value: 0x7F030100 public const int progressBarStyle = 2130903296; - + // aapt resource value: 0x7F030101 public const int queryBackground = 2130903297; - + // aapt resource value: 0x7F030102 public const int queryHint = 2130903298; - + // aapt resource value: 0x7F030103 public const int radioButtonStyle = 2130903299; - + // aapt resource value: 0x7F030104 public const int ratingBarStyle = 2130903300; - + // aapt resource value: 0x7F030105 public const int ratingBarStyleIndicator = 2130903301; - + // aapt resource value: 0x7F030106 public const int ratingBarStyleSmall = 2130903302; - + // aapt resource value: 0x7F030107 public const int reverseLayout = 2130903303; - + // aapt resource value: 0x7F030108 public const int rippleColor = 2130903304; - + // aapt resource value: 0x7F030109 public const int scrimAnimationDuration = 2130903305; - + // aapt resource value: 0x7F03010A public const int scrimVisibleHeightTrigger = 2130903306; - + // aapt resource value: 0x7F03010B public const int searchHintIcon = 2130903307; - + // aapt resource value: 0x7F03010C public const int searchIcon = 2130903308; - + // aapt resource value: 0x7F03010D public const int searchViewStyle = 2130903309; - + // aapt resource value: 0x7F03010E public const int seekBarStyle = 2130903310; - + // aapt resource value: 0x7F03010F public const int selectableItemBackground = 2130903311; - + // aapt resource value: 0x7F030110 public const int selectableItemBackgroundBorderless = 2130903312; - + // aapt resource value: 0x7F030111 public const int showAsAction = 2130903313; - + // aapt resource value: 0x7F030112 public const int showDividers = 2130903314; - + // aapt resource value: 0x7F030113 public const int showText = 2130903315; - + // aapt resource value: 0x7F030114 public const int showTitle = 2130903316; - + // aapt resource value: 0x7F030115 public const int singleChoiceItemLayout = 2130903317; - + // aapt resource value: 0x7F030116 public const int spanCount = 2130903318; - + // aapt resource value: 0x7F030117 public const int spinBars = 2130903319; - + // aapt resource value: 0x7F030118 public const int spinnerDropDownItemStyle = 2130903320; - + // aapt resource value: 0x7F030119 public const int spinnerStyle = 2130903321; - + // aapt resource value: 0x7F03011A public const int splitTrack = 2130903322; - + // aapt resource value: 0x7F03011B public const int srcCompat = 2130903323; - + // aapt resource value: 0x7F03011C public const int stackFromEnd = 2130903324; - + // aapt resource value: 0x7F03011D public const int state_above_anchor = 2130903325; - + // aapt resource value: 0x7F03011E public const int state_collapsed = 2130903326; - + // aapt resource value: 0x7F03011F public const int state_collapsible = 2130903327; - + // aapt resource value: 0x7F030120 public const int statusBarBackground = 2130903328; - + // aapt resource value: 0x7F030121 public const int statusBarScrim = 2130903329; - + // aapt resource value: 0x7F030122 public const int subMenuArrow = 2130903330; - + // aapt resource value: 0x7F030123 public const int submitBackground = 2130903331; - + // aapt resource value: 0x7F030124 public const int subtitle = 2130903332; - + // aapt resource value: 0x7F030125 public const int subtitleTextAppearance = 2130903333; - + // aapt resource value: 0x7F030126 public const int subtitleTextColor = 2130903334; - + // aapt resource value: 0x7F030127 public const int subtitleTextStyle = 2130903335; - + // aapt resource value: 0x7F030128 public const int suggestionRowLayout = 2130903336; - + // aapt resource value: 0x7F030129 public const int switchMinWidth = 2130903337; - + // aapt resource value: 0x7F03012A public const int switchPadding = 2130903338; - + // aapt resource value: 0x7F03012B public const int switchStyle = 2130903339; - + // aapt resource value: 0x7F03012C public const int switchTextAppearance = 2130903340; - + // aapt resource value: 0x7F03012D public const int tabBackground = 2130903341; - + // aapt resource value: 0x7F03012E public const int tabContentStart = 2130903342; - + // aapt resource value: 0x7F03012F public const int tabGravity = 2130903343; - + // aapt resource value: 0x7F030130 public const int tabIndicatorColor = 2130903344; - + // aapt resource value: 0x7F030131 public const int tabIndicatorHeight = 2130903345; - + // aapt resource value: 0x7F030132 public const int tabMaxWidth = 2130903346; - + // aapt resource value: 0x7F030133 public const int tabMinWidth = 2130903347; - + // aapt resource value: 0x7F030134 public const int tabMode = 2130903348; - + // aapt resource value: 0x7F030135 public const int tabPadding = 2130903349; - + // aapt resource value: 0x7F030136 public const int tabPaddingBottom = 2130903350; - + // aapt resource value: 0x7F030137 public const int tabPaddingEnd = 2130903351; - + // aapt resource value: 0x7F030138 public const int tabPaddingStart = 2130903352; - + // aapt resource value: 0x7F030139 public const int tabPaddingTop = 2130903353; - + // aapt resource value: 0x7F03013A public const int tabSelectedTextColor = 2130903354; - + // aapt resource value: 0x7F03013B public const int tabTextAppearance = 2130903355; - + // aapt resource value: 0x7F03013C public const int tabTextColor = 2130903356; - + // aapt resource value: 0x7F03013D public const int textAllCaps = 2130903357; - + // aapt resource value: 0x7F03013E public const int textAppearanceLargePopupMenu = 2130903358; - + // aapt resource value: 0x7F03013F public const int textAppearanceListItem = 2130903359; - + // aapt resource value: 0x7F030140 public const int textAppearanceListItemSecondary = 2130903360; - + // aapt resource value: 0x7F030141 public const int textAppearanceListItemSmall = 2130903361; - + // aapt resource value: 0x7F030142 public const int textAppearancePopupMenuHeader = 2130903362; - + // aapt resource value: 0x7F030143 public const int textAppearanceSearchResultSubtitle = 2130903363; - + // aapt resource value: 0x7F030144 public const int textAppearanceSearchResultTitle = 2130903364; - + // aapt resource value: 0x7F030145 public const int textAppearanceSmallPopupMenu = 2130903365; - + // aapt resource value: 0x7F030146 public const int textColorAlertDialogListItem = 2130903366; - + // aapt resource value: 0x7F030147 public const int textColorError = 2130903367; - + // aapt resource value: 0x7F030148 public const int textColorSearchUrl = 2130903368; - + // aapt resource value: 0x7F030149 public const int theme = 2130903369; - + // aapt resource value: 0x7F03014A public const int thickness = 2130903370; - + // aapt resource value: 0x7F03014B public const int thumbTextPadding = 2130903371; - + // aapt resource value: 0x7F03014C public const int thumbTint = 2130903372; - + // aapt resource value: 0x7F03014D public const int thumbTintMode = 2130903373; - + // aapt resource value: 0x7F03014E public const int tickMark = 2130903374; - + // aapt resource value: 0x7F03014F public const int tickMarkTint = 2130903375; - + // aapt resource value: 0x7F030150 public const int tickMarkTintMode = 2130903376; - + // aapt resource value: 0x7F030151 public const int tint = 2130903377; - + // aapt resource value: 0x7F030152 public const int tintMode = 2130903378; - + // aapt resource value: 0x7F030153 public const int title = 2130903379; - + // aapt resource value: 0x7F030154 public const int titleEnabled = 2130903380; - + // aapt resource value: 0x7F030155 public const int titleMargin = 2130903381; - + // aapt resource value: 0x7F030156 public const int titleMarginBottom = 2130903382; - + // aapt resource value: 0x7F030157 public const int titleMarginEnd = 2130903383; - + // aapt resource value: 0x7F03015A public const int titleMargins = 2130903386; - + // aapt resource value: 0x7F030158 public const int titleMarginStart = 2130903384; - + // aapt resource value: 0x7F030159 public const int titleMarginTop = 2130903385; - + // aapt resource value: 0x7F03015B public const int titleTextAppearance = 2130903387; - + // aapt resource value: 0x7F03015C public const int titleTextColor = 2130903388; - + // aapt resource value: 0x7F03015D public const int titleTextStyle = 2130903389; - + // aapt resource value: 0x7F03015E public const int toolbarId = 2130903390; - + // aapt resource value: 0x7F03015F public const int toolbarNavigationButtonStyle = 2130903391; - + // aapt resource value: 0x7F030160 public const int toolbarStyle = 2130903392; - + // aapt resource value: 0x7F030161 public const int tooltipForegroundColor = 2130903393; - + // aapt resource value: 0x7F030162 public const int tooltipFrameBackground = 2130903394; - + // aapt resource value: 0x7F030163 public const int tooltipText = 2130903395; - + // aapt resource value: 0x7F030164 public const int track = 2130903396; - + // aapt resource value: 0x7F030165 public const int trackTint = 2130903397; - + // aapt resource value: 0x7F030166 public const int trackTintMode = 2130903398; - + // aapt resource value: 0x7F030167 public const int useCompatPadding = 2130903399; - + // aapt resource value: 0x7F030168 public const int voiceIcon = 2130903400; - + // aapt resource value: 0x7F030169 public const int windowActionBar = 2130903401; - + // aapt resource value: 0x7F03016A public const int windowActionBarOverlay = 2130903402; - + // aapt resource value: 0x7F03016B public const int windowActionModeOverlay = 2130903403; - + // aapt resource value: 0x7F03016C public const int windowFixedHeightMajor = 2130903404; - + // aapt resource value: 0x7F03016D public const int windowFixedHeightMinor = 2130903405; - + // aapt resource value: 0x7F03016E public const int windowFixedWidthMajor = 2130903406; - + // aapt resource value: 0x7F03016F public const int windowFixedWidthMinor = 2130903407; - + // aapt resource value: 0x7F030170 public const int windowMinWidthMajor = 2130903408; - + // aapt resource value: 0x7F030171 public const int windowMinWidthMinor = 2130903409; - + // aapt resource value: 0x7F030172 public const int windowNoTitle = 2130903410; - + static Attribute() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Attribute() { } } - + public partial class Boolean { - + // aapt resource value: 0x7F040000 public const int abc_action_bar_embed_tabs = 2130968576; - + // aapt resource value: 0x7F040001 public const int abc_allow_stacked_button_bar = 2130968577; - + // aapt resource value: 0x7F040002 public const int abc_config_actionMenuItemAllCaps = 2130968578; - + // aapt resource value: 0x7F040003 public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - + // aapt resource value: 0x7F040004 public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; - + static Boolean() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Boolean() { } } - + public partial class Color { - + // aapt resource value: 0x7F050000 public const int abc_background_cache_hint_selector_material_dark = 2131034112; - + // aapt resource value: 0x7F050001 public const int abc_background_cache_hint_selector_material_light = 2131034113; - + // aapt resource value: 0x7F050002 public const int abc_btn_colored_borderless_text_material = 2131034114; - + // aapt resource value: 0x7F050003 public const int abc_btn_colored_text_material = 2131034115; - + // aapt resource value: 0x7F050004 public const int abc_color_highlight_material = 2131034116; - + // aapt resource value: 0x7F050005 public const int abc_hint_foreground_material_dark = 2131034117; - + // aapt resource value: 0x7F050006 public const int abc_hint_foreground_material_light = 2131034118; - + // aapt resource value: 0x7F050007 public const int abc_input_method_navigation_guard = 2131034119; - + // aapt resource value: 0x7F050008 public const int abc_primary_text_disable_only_material_dark = 2131034120; - + // aapt resource value: 0x7F050009 public const int abc_primary_text_disable_only_material_light = 2131034121; - + // aapt resource value: 0x7F05000A public const int abc_primary_text_material_dark = 2131034122; - + // aapt resource value: 0x7F05000B public const int abc_primary_text_material_light = 2131034123; - + // aapt resource value: 0x7F05000C public const int abc_search_url_text = 2131034124; - + // aapt resource value: 0x7F05000D public const int abc_search_url_text_normal = 2131034125; - + // aapt resource value: 0x7F05000E public const int abc_search_url_text_pressed = 2131034126; - + // aapt resource value: 0x7F05000F public const int abc_search_url_text_selected = 2131034127; - + // aapt resource value: 0x7F050010 public const int abc_secondary_text_material_dark = 2131034128; - + // aapt resource value: 0x7F050011 public const int abc_secondary_text_material_light = 2131034129; - + // aapt resource value: 0x7F050012 public const int abc_tint_btn_checkable = 2131034130; - + // aapt resource value: 0x7F050013 public const int abc_tint_default = 2131034131; - + // aapt resource value: 0x7F050014 public const int abc_tint_edittext = 2131034132; - + // aapt resource value: 0x7F050015 public const int abc_tint_seek_thumb = 2131034133; - + // aapt resource value: 0x7F050016 public const int abc_tint_spinner = 2131034134; - + // aapt resource value: 0x7F050017 public const int abc_tint_switch_track = 2131034135; - + // aapt resource value: 0x7F050018 public const int accent_material_dark = 2131034136; - + // aapt resource value: 0x7F050019 public const int accent_material_light = 2131034137; - + // aapt resource value: 0x7F05001A public const int background_floating_material_dark = 2131034138; - + // aapt resource value: 0x7F05001B public const int background_floating_material_light = 2131034139; - + // aapt resource value: 0x7F05001C public const int background_material_dark = 2131034140; - + // aapt resource value: 0x7F05001D public const int background_material_light = 2131034141; - + // aapt resource value: 0x7F05001E public const int bright_foreground_disabled_material_dark = 2131034142; - + // aapt resource value: 0x7F05001F public const int bright_foreground_disabled_material_light = 2131034143; - + // aapt resource value: 0x7F050020 public const int bright_foreground_inverse_material_dark = 2131034144; - + // aapt resource value: 0x7F050021 public const int bright_foreground_inverse_material_light = 2131034145; - + // aapt resource value: 0x7F050022 public const int bright_foreground_material_dark = 2131034146; - + // aapt resource value: 0x7F050023 public const int bright_foreground_material_light = 2131034147; - + // aapt resource value: 0x7F050024 public const int button_material_dark = 2131034148; - + // aapt resource value: 0x7F050025 public const int button_material_light = 2131034149; - + // aapt resource value: 0x7F050026 public const int cardview_dark_background = 2131034150; - + // aapt resource value: 0x7F050027 public const int cardview_light_background = 2131034151; - + // aapt resource value: 0x7F050028 public const int cardview_shadow_end_color = 2131034152; - + // aapt resource value: 0x7F050029 public const int cardview_shadow_start_color = 2131034153; - + // aapt resource value: 0x7F05002A public const int colorAccent = 2131034154; - + // aapt resource value: 0x7F05002B public const int colorPrimary = 2131034155; - + // aapt resource value: 0x7F05002C public const int colorPrimaryDark = 2131034156; - + // aapt resource value: 0x7F05002D public const int design_bottom_navigation_shadow_color = 2131034157; - + // aapt resource value: 0x7F05002E public const int design_error = 2131034158; - + // aapt resource value: 0x7F05002F public const int design_fab_shadow_end_color = 2131034159; - + // aapt resource value: 0x7F050030 public const int design_fab_shadow_mid_color = 2131034160; - + // aapt resource value: 0x7F050031 public const int design_fab_shadow_start_color = 2131034161; - + // aapt resource value: 0x7F050032 public const int design_fab_stroke_end_inner_color = 2131034162; - + // aapt resource value: 0x7F050033 public const int design_fab_stroke_end_outer_color = 2131034163; - + // aapt resource value: 0x7F050034 public const int design_fab_stroke_top_inner_color = 2131034164; - + // aapt resource value: 0x7F050035 public const int design_fab_stroke_top_outer_color = 2131034165; - + // aapt resource value: 0x7F050036 public const int design_snackbar_background_color = 2131034166; - + // aapt resource value: 0x7F050037 public const int design_tint_password_toggle = 2131034167; - + // aapt resource value: 0x7F050038 public const int dim_foreground_disabled_material_dark = 2131034168; - + // aapt resource value: 0x7F050039 public const int dim_foreground_disabled_material_light = 2131034169; - + // aapt resource value: 0x7F05003A public const int dim_foreground_material_dark = 2131034170; - + // aapt resource value: 0x7F05003B public const int dim_foreground_material_light = 2131034171; - + // aapt resource value: 0x7F05003C public const int error_color_material = 2131034172; - + // aapt resource value: 0x7F05003D public const int foreground_material_dark = 2131034173; - + // aapt resource value: 0x7F05003E public const int foreground_material_light = 2131034174; - + // aapt resource value: 0x7F05003F public const int highlighted_text_material_dark = 2131034175; - + // aapt resource value: 0x7F050040 public const int highlighted_text_material_light = 2131034176; - + // aapt resource value: 0x7F050041 public const int ic_launcher_background = 2131034177; - + // aapt resource value: 0x7F050042 public const int material_blue_grey_800 = 2131034178; - + // aapt resource value: 0x7F050043 public const int material_blue_grey_900 = 2131034179; - + // aapt resource value: 0x7F050044 public const int material_blue_grey_950 = 2131034180; - + // aapt resource value: 0x7F050045 public const int material_deep_teal_200 = 2131034181; - + // aapt resource value: 0x7F050046 public const int material_deep_teal_500 = 2131034182; - + // aapt resource value: 0x7F050047 public const int material_grey_100 = 2131034183; - + // aapt resource value: 0x7F050048 public const int material_grey_300 = 2131034184; - + // aapt resource value: 0x7F050049 public const int material_grey_50 = 2131034185; - + // aapt resource value: 0x7F05004A public const int material_grey_600 = 2131034186; - + // aapt resource value: 0x7F05004B public const int material_grey_800 = 2131034187; - + // aapt resource value: 0x7F05004C public const int material_grey_850 = 2131034188; - + // aapt resource value: 0x7F05004D public const int material_grey_900 = 2131034189; - + // aapt resource value: 0x7F05004E public const int notification_action_color_filter = 2131034190; - + // aapt resource value: 0x7F05004F public const int notification_icon_bg_color = 2131034191; - + // aapt resource value: 0x7F050050 public const int notification_material_background_media_default_color = 2131034192; - + // aapt resource value: 0x7F050051 public const int primary_dark_material_dark = 2131034193; - + // aapt resource value: 0x7F050052 public const int primary_dark_material_light = 2131034194; - + // aapt resource value: 0x7F050053 public const int primary_material_dark = 2131034195; - + // aapt resource value: 0x7F050054 public const int primary_material_light = 2131034196; - + // aapt resource value: 0x7F050055 public const int primary_text_default_material_dark = 2131034197; - + // aapt resource value: 0x7F050056 public const int primary_text_default_material_light = 2131034198; - + // aapt resource value: 0x7F050057 public const int primary_text_disabled_material_dark = 2131034199; - + // aapt resource value: 0x7F050058 public const int primary_text_disabled_material_light = 2131034200; - + // aapt resource value: 0x7F050059 public const int ripple_material_dark = 2131034201; - + // aapt resource value: 0x7F05005A public const int ripple_material_light = 2131034202; - + // aapt resource value: 0x7F05005B public const int secondary_text_default_material_dark = 2131034203; - + // aapt resource value: 0x7F05005C public const int secondary_text_default_material_light = 2131034204; - + // aapt resource value: 0x7F05005D public const int secondary_text_disabled_material_dark = 2131034205; - + // aapt resource value: 0x7F05005E public const int secondary_text_disabled_material_light = 2131034206; - + // aapt resource value: 0x7F05005F public const int switch_thumb_disabled_material_dark = 2131034207; - + // aapt resource value: 0x7F050060 public const int switch_thumb_disabled_material_light = 2131034208; - + // aapt resource value: 0x7F050061 public const int switch_thumb_material_dark = 2131034209; - + // aapt resource value: 0x7F050062 public const int switch_thumb_material_light = 2131034210; - + // aapt resource value: 0x7F050063 public const int switch_thumb_normal_material_dark = 2131034211; - + // aapt resource value: 0x7F050064 public const int switch_thumb_normal_material_light = 2131034212; - + // aapt resource value: 0x7F050065 public const int tooltip_background_dark = 2131034213; - + // aapt resource value: 0x7F050066 public const int tooltip_background_light = 2131034214; - + static Color() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Color() { } } - + public partial class Dimension { - + // aapt resource value: 0x7F060000 public const int abc_action_bar_content_inset_material = 2131099648; - + // aapt resource value: 0x7F060001 public const int abc_action_bar_content_inset_with_nav = 2131099649; - + // aapt resource value: 0x7F060002 public const int abc_action_bar_default_height_material = 2131099650; - + // aapt resource value: 0x7F060003 public const int abc_action_bar_default_padding_end_material = 2131099651; - + // aapt resource value: 0x7F060004 public const int abc_action_bar_default_padding_start_material = 2131099652; - + // aapt resource value: 0x7F060005 public const int abc_action_bar_elevation_material = 2131099653; - + // aapt resource value: 0x7F060006 public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - + // aapt resource value: 0x7F060007 public const int abc_action_bar_overflow_padding_end_material = 2131099655; - + // aapt resource value: 0x7F060008 public const int abc_action_bar_overflow_padding_start_material = 2131099656; - + // aapt resource value: 0x7F060009 public const int abc_action_bar_progress_bar_size = 2131099657; - + // aapt resource value: 0x7F06000A public const int abc_action_bar_stacked_max_height = 2131099658; - + // aapt resource value: 0x7F06000B public const int abc_action_bar_stacked_tab_max_width = 2131099659; - + // aapt resource value: 0x7F06000C public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - + // aapt resource value: 0x7F06000D public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - + // aapt resource value: 0x7F06000E public const int abc_action_button_min_height_material = 2131099662; - + // aapt resource value: 0x7F06000F public const int abc_action_button_min_width_material = 2131099663; - + // aapt resource value: 0x7F060010 public const int abc_action_button_min_width_overflow_material = 2131099664; - + // aapt resource value: 0x7F060011 public const int abc_alert_dialog_button_bar_height = 2131099665; - + // aapt resource value: 0x7F060012 public const int abc_button_inset_horizontal_material = 2131099666; - + // aapt resource value: 0x7F060013 public const int abc_button_inset_vertical_material = 2131099667; - + // aapt resource value: 0x7F060014 public const int abc_button_padding_horizontal_material = 2131099668; - + // aapt resource value: 0x7F060015 public const int abc_button_padding_vertical_material = 2131099669; - + // aapt resource value: 0x7F060016 public const int abc_cascading_menus_min_smallest_width = 2131099670; - + // aapt resource value: 0x7F060017 public const int abc_config_prefDialogWidth = 2131099671; - + // aapt resource value: 0x7F060018 public const int abc_control_corner_material = 2131099672; - + // aapt resource value: 0x7F060019 public const int abc_control_inset_material = 2131099673; - + // aapt resource value: 0x7F06001A public const int abc_control_padding_material = 2131099674; - + // aapt resource value: 0x7F06001B public const int abc_dialog_fixed_height_major = 2131099675; - + // aapt resource value: 0x7F06001C public const int abc_dialog_fixed_height_minor = 2131099676; - + // aapt resource value: 0x7F06001D public const int abc_dialog_fixed_width_major = 2131099677; - + // aapt resource value: 0x7F06001E public const int abc_dialog_fixed_width_minor = 2131099678; - + // aapt resource value: 0x7F06001F public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - + // aapt resource value: 0x7F060020 public const int abc_dialog_list_padding_top_no_title = 2131099680; - + // aapt resource value: 0x7F060021 public const int abc_dialog_min_width_major = 2131099681; - + // aapt resource value: 0x7F060022 public const int abc_dialog_min_width_minor = 2131099682; - + // aapt resource value: 0x7F060023 public const int abc_dialog_padding_material = 2131099683; - + // aapt resource value: 0x7F060024 public const int abc_dialog_padding_top_material = 2131099684; - + // aapt resource value: 0x7F060025 public const int abc_dialog_title_divider_material = 2131099685; - + // aapt resource value: 0x7F060026 public const int abc_disabled_alpha_material_dark = 2131099686; - + // aapt resource value: 0x7F060027 public const int abc_disabled_alpha_material_light = 2131099687; - + // aapt resource value: 0x7F060028 public const int abc_dropdownitem_icon_width = 2131099688; - + // aapt resource value: 0x7F060029 public const int abc_dropdownitem_text_padding_left = 2131099689; - + // aapt resource value: 0x7F06002A public const int abc_dropdownitem_text_padding_right = 2131099690; - + // aapt resource value: 0x7F06002B public const int abc_edit_text_inset_bottom_material = 2131099691; - + // aapt resource value: 0x7F06002C public const int abc_edit_text_inset_horizontal_material = 2131099692; - + // aapt resource value: 0x7F06002D public const int abc_edit_text_inset_top_material = 2131099693; - + // aapt resource value: 0x7F06002E public const int abc_floating_window_z = 2131099694; - + // aapt resource value: 0x7F06002F public const int abc_list_item_padding_horizontal_material = 2131099695; - + // aapt resource value: 0x7F060030 public const int abc_panel_menu_list_width = 2131099696; - + // aapt resource value: 0x7F060031 public const int abc_progress_bar_height_material = 2131099697; - + // aapt resource value: 0x7F060032 public const int abc_search_view_preferred_height = 2131099698; - + // aapt resource value: 0x7F060033 public const int abc_search_view_preferred_width = 2131099699; - + // aapt resource value: 0x7F060034 public const int abc_seekbar_track_background_height_material = 2131099700; - + // aapt resource value: 0x7F060035 public const int abc_seekbar_track_progress_height_material = 2131099701; - + // aapt resource value: 0x7F060036 public const int abc_select_dialog_padding_start_material = 2131099702; - + // aapt resource value: 0x7F060037 public const int abc_switch_padding = 2131099703; - + // aapt resource value: 0x7F060038 public const int abc_text_size_body_1_material = 2131099704; - + // aapt resource value: 0x7F060039 public const int abc_text_size_body_2_material = 2131099705; - + // aapt resource value: 0x7F06003A public const int abc_text_size_button_material = 2131099706; - + // aapt resource value: 0x7F06003B public const int abc_text_size_caption_material = 2131099707; - + // aapt resource value: 0x7F06003C public const int abc_text_size_display_1_material = 2131099708; - + // aapt resource value: 0x7F06003D public const int abc_text_size_display_2_material = 2131099709; - + // aapt resource value: 0x7F06003E public const int abc_text_size_display_3_material = 2131099710; - + // aapt resource value: 0x7F06003F public const int abc_text_size_display_4_material = 2131099711; - + // aapt resource value: 0x7F060040 public const int abc_text_size_headline_material = 2131099712; - + // aapt resource value: 0x7F060041 public const int abc_text_size_large_material = 2131099713; - + // aapt resource value: 0x7F060042 public const int abc_text_size_medium_material = 2131099714; - + // aapt resource value: 0x7F060043 public const int abc_text_size_menu_header_material = 2131099715; - + // aapt resource value: 0x7F060044 public const int abc_text_size_menu_material = 2131099716; - + // aapt resource value: 0x7F060045 public const int abc_text_size_small_material = 2131099717; - + // aapt resource value: 0x7F060046 public const int abc_text_size_subhead_material = 2131099718; - + // aapt resource value: 0x7F060047 public const int abc_text_size_subtitle_material_toolbar = 2131099719; - + // aapt resource value: 0x7F060048 public const int abc_text_size_title_material = 2131099720; - + // aapt resource value: 0x7F060049 public const int abc_text_size_title_material_toolbar = 2131099721; - + // aapt resource value: 0x7F06004A public const int cardview_compat_inset_shadow = 2131099722; - + // aapt resource value: 0x7F06004B public const int cardview_default_elevation = 2131099723; - + // aapt resource value: 0x7F06004C public const int cardview_default_radius = 2131099724; - + // aapt resource value: 0x7F06004D public const int compat_button_inset_horizontal_material = 2131099725; - + // aapt resource value: 0x7F06004E public const int compat_button_inset_vertical_material = 2131099726; - + // aapt resource value: 0x7F06004F public const int compat_button_padding_horizontal_material = 2131099727; - + // aapt resource value: 0x7F060050 public const int compat_button_padding_vertical_material = 2131099728; - + // aapt resource value: 0x7F060051 public const int compat_control_corner_material = 2131099729; - + // aapt resource value: 0x7F060052 public const int design_appbar_elevation = 2131099730; - + // aapt resource value: 0x7F060053 public const int design_bottom_navigation_active_item_max_width = 2131099731; - + // aapt resource value: 0x7F060054 public const int design_bottom_navigation_active_text_size = 2131099732; - + // aapt resource value: 0x7F060055 public const int design_bottom_navigation_elevation = 2131099733; - + // aapt resource value: 0x7F060056 public const int design_bottom_navigation_height = 2131099734; - + // aapt resource value: 0x7F060057 public const int design_bottom_navigation_item_max_width = 2131099735; - + // aapt resource value: 0x7F060058 public const int design_bottom_navigation_item_min_width = 2131099736; - + // aapt resource value: 0x7F060059 public const int design_bottom_navigation_margin = 2131099737; - + // aapt resource value: 0x7F06005A public const int design_bottom_navigation_shadow_height = 2131099738; - + // aapt resource value: 0x7F06005B public const int design_bottom_navigation_text_size = 2131099739; - + // aapt resource value: 0x7F06005C public const int design_bottom_sheet_modal_elevation = 2131099740; - + // aapt resource value: 0x7F06005D public const int design_bottom_sheet_peek_height_min = 2131099741; - + // aapt resource value: 0x7F06005E public const int design_fab_border_width = 2131099742; - + // aapt resource value: 0x7F06005F public const int design_fab_elevation = 2131099743; - + // aapt resource value: 0x7F060060 public const int design_fab_image_size = 2131099744; - + // aapt resource value: 0x7F060061 public const int design_fab_size_mini = 2131099745; - + // aapt resource value: 0x7F060062 public const int design_fab_size_normal = 2131099746; - + // aapt resource value: 0x7F060063 public const int design_fab_translation_z_pressed = 2131099747; - + // aapt resource value: 0x7F060064 public const int design_navigation_elevation = 2131099748; - + // aapt resource value: 0x7F060065 public const int design_navigation_icon_padding = 2131099749; - + // aapt resource value: 0x7F060066 public const int design_navigation_icon_size = 2131099750; - + // aapt resource value: 0x7F060067 public const int design_navigation_max_width = 2131099751; - + // aapt resource value: 0x7F060068 public const int design_navigation_padding_bottom = 2131099752; - + // aapt resource value: 0x7F060069 public const int design_navigation_separator_vertical_padding = 2131099753; - + // aapt resource value: 0x7F06006A public const int design_snackbar_action_inline_max_width = 2131099754; - + // aapt resource value: 0x7F06006B public const int design_snackbar_background_corner_radius = 2131099755; - + // aapt resource value: 0x7F06006C public const int design_snackbar_elevation = 2131099756; - + // aapt resource value: 0x7F06006D public const int design_snackbar_extra_spacing_horizontal = 2131099757; - + // aapt resource value: 0x7F06006E public const int design_snackbar_max_width = 2131099758; - + // aapt resource value: 0x7F06006F public const int design_snackbar_min_width = 2131099759; - + // aapt resource value: 0x7F060070 public const int design_snackbar_padding_horizontal = 2131099760; - + // aapt resource value: 0x7F060071 public const int design_snackbar_padding_vertical = 2131099761; - + // aapt resource value: 0x7F060072 public const int design_snackbar_padding_vertical_2lines = 2131099762; - + // aapt resource value: 0x7F060073 public const int design_snackbar_text_size = 2131099763; - + // aapt resource value: 0x7F060074 public const int design_tab_max_width = 2131099764; - + // aapt resource value: 0x7F060075 public const int design_tab_scrollable_min_width = 2131099765; - + // aapt resource value: 0x7F060076 public const int design_tab_text_size = 2131099766; - + // aapt resource value: 0x7F060077 public const int design_tab_text_size_2line = 2131099767; - + // aapt resource value: 0x7F060078 public const int disabled_alpha_material_dark = 2131099768; - + // aapt resource value: 0x7F060079 public const int disabled_alpha_material_light = 2131099769; - + // aapt resource value: 0x7F06007A public const int fastscroll_default_thickness = 2131099770; - + // aapt resource value: 0x7F06007B public const int fastscroll_margin = 2131099771; - + // aapt resource value: 0x7F06007C public const int fastscroll_minimum_range = 2131099772; - + // aapt resource value: 0x7F06007D public const int highlight_alpha_material_colored = 2131099773; - + // aapt resource value: 0x7F06007E public const int highlight_alpha_material_dark = 2131099774; - + // aapt resource value: 0x7F06007F public const int highlight_alpha_material_light = 2131099775; - + // aapt resource value: 0x7F060080 public const int hint_alpha_material_dark = 2131099776; - + // aapt resource value: 0x7F060081 public const int hint_alpha_material_light = 2131099777; - + // aapt resource value: 0x7F060082 public const int hint_pressed_alpha_material_dark = 2131099778; - + // aapt resource value: 0x7F060083 public const int hint_pressed_alpha_material_light = 2131099779; - + // aapt resource value: 0x7F060084 public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - + // aapt resource value: 0x7F060085 public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - + // aapt resource value: 0x7F060086 public const int item_touch_helper_swipe_escape_velocity = 2131099782; - + // aapt resource value: 0x7F060087 public const int mr_controller_volume_group_list_item_height = 2131099783; - + // aapt resource value: 0x7F060088 public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - + // aapt resource value: 0x7F060089 public const int mr_controller_volume_group_list_max_height = 2131099785; - + // aapt resource value: 0x7F06008A public const int mr_controller_volume_group_list_padding_top = 2131099786; - + // aapt resource value: 0x7F06008B public const int mr_dialog_fixed_width_major = 2131099787; - + // aapt resource value: 0x7F06008C public const int mr_dialog_fixed_width_minor = 2131099788; - + // aapt resource value: 0x7F06008D public const int notification_action_icon_size = 2131099789; - + // aapt resource value: 0x7F06008E public const int notification_action_text_size = 2131099790; - + // aapt resource value: 0x7F06008F public const int notification_big_circle_margin = 2131099791; - + // aapt resource value: 0x7F060090 public const int notification_content_margin_start = 2131099792; - + // aapt resource value: 0x7F060091 public const int notification_large_icon_height = 2131099793; - + // aapt resource value: 0x7F060092 public const int notification_large_icon_width = 2131099794; - + // aapt resource value: 0x7F060093 public const int notification_main_column_padding_top = 2131099795; - + // aapt resource value: 0x7F060094 public const int notification_media_narrow_margin = 2131099796; - + // aapt resource value: 0x7F060095 public const int notification_right_icon_size = 2131099797; - + // aapt resource value: 0x7F060096 public const int notification_right_side_padding_top = 2131099798; - + // aapt resource value: 0x7F060097 public const int notification_small_icon_background_padding = 2131099799; - + // aapt resource value: 0x7F060098 public const int notification_small_icon_size_as_large = 2131099800; - + // aapt resource value: 0x7F060099 public const int notification_subtext_size = 2131099801; - + // aapt resource value: 0x7F06009A public const int notification_top_pad = 2131099802; - + // aapt resource value: 0x7F06009B public const int notification_top_pad_large_text = 2131099803; - + // aapt resource value: 0x7F06009C public const int tooltip_corner_radius = 2131099804; - + // aapt resource value: 0x7F06009D public const int tooltip_horizontal_padding = 2131099805; - + // aapt resource value: 0x7F06009E public const int tooltip_margin = 2131099806; - + // aapt resource value: 0x7F06009F public const int tooltip_precise_anchor_extra_offset = 2131099807; - + // aapt resource value: 0x7F0600A0 public const int tooltip_precise_anchor_threshold = 2131099808; - + // aapt resource value: 0x7F0600A1 public const int tooltip_vertical_padding = 2131099809; - + // aapt resource value: 0x7F0600A2 public const int tooltip_y_offset_non_touch = 2131099810; - + // aapt resource value: 0x7F0600A3 public const int tooltip_y_offset_touch = 2131099811; - + static Dimension() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Dimension() { } } - + public partial class Drawable { - + // aapt resource value: 0x7F070006 public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - + // aapt resource value: 0x7F070007 public const int abc_action_bar_item_background_material = 2131165191; - + // aapt resource value: 0x7F070008 public const int abc_btn_borderless_material = 2131165192; - + // aapt resource value: 0x7F070009 public const int abc_btn_check_material = 2131165193; - + // aapt resource value: 0x7F07000A public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - + // aapt resource value: 0x7F07000B public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - + // aapt resource value: 0x7F07000C public const int abc_btn_colored_material = 2131165196; - + // aapt resource value: 0x7F07000D public const int abc_btn_default_mtrl_shape = 2131165197; - + // aapt resource value: 0x7F07000E public const int abc_btn_radio_material = 2131165198; - + // aapt resource value: 0x7F07000F public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - + // aapt resource value: 0x7F070010 public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - + // aapt resource value: 0x7F070011 public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - + // aapt resource value: 0x7F070012 public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - + // aapt resource value: 0x7F070013 public const int abc_cab_background_internal_bg = 2131165203; - + // aapt resource value: 0x7F070014 public const int abc_cab_background_top_material = 2131165204; - + // aapt resource value: 0x7F070015 public const int abc_cab_background_top_mtrl_alpha = 2131165205; - + // aapt resource value: 0x7F070016 public const int abc_control_background_material = 2131165206; - + // aapt resource value: 0x7F070017 public const int abc_dialog_material_background = 2131165207; - + // aapt resource value: 0x7F070018 public const int abc_edit_text_material = 2131165208; - + // aapt resource value: 0x7F070019 public const int abc_ic_ab_back_material = 2131165209; - + // aapt resource value: 0x7F07001A public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - + // aapt resource value: 0x7F07001B public const int abc_ic_clear_material = 2131165211; - + // aapt resource value: 0x7F07001C public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - + // aapt resource value: 0x7F07001D public const int abc_ic_go_search_api_material = 2131165213; - + // aapt resource value: 0x7F07001E public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - + // aapt resource value: 0x7F07001F public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - + // aapt resource value: 0x7F070020 public const int abc_ic_menu_overflow_material = 2131165216; - + // aapt resource value: 0x7F070021 public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - + // aapt resource value: 0x7F070022 public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - + // aapt resource value: 0x7F070023 public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - + // aapt resource value: 0x7F070024 public const int abc_ic_search_api_material = 2131165220; - + // aapt resource value: 0x7F070025 public const int abc_ic_star_black_16dp = 2131165221; - + // aapt resource value: 0x7F070026 public const int abc_ic_star_black_36dp = 2131165222; - + // aapt resource value: 0x7F070027 public const int abc_ic_star_black_48dp = 2131165223; - + // aapt resource value: 0x7F070028 public const int abc_ic_star_half_black_16dp = 2131165224; - + // aapt resource value: 0x7F070029 public const int abc_ic_star_half_black_36dp = 2131165225; - + // aapt resource value: 0x7F07002A public const int abc_ic_star_half_black_48dp = 2131165226; - + // aapt resource value: 0x7F07002B public const int abc_ic_voice_search_api_material = 2131165227; - + // aapt resource value: 0x7F07002C public const int abc_item_background_holo_dark = 2131165228; - + // aapt resource value: 0x7F07002D public const int abc_item_background_holo_light = 2131165229; - + // aapt resource value: 0x7F07002E public const int abc_list_divider_mtrl_alpha = 2131165230; - + // aapt resource value: 0x7F07002F public const int abc_list_focused_holo = 2131165231; - + // aapt resource value: 0x7F070030 public const int abc_list_longpressed_holo = 2131165232; - + // aapt resource value: 0x7F070031 public const int abc_list_pressed_holo_dark = 2131165233; - + // aapt resource value: 0x7F070032 public const int abc_list_pressed_holo_light = 2131165234; - + // aapt resource value: 0x7F070033 public const int abc_list_selector_background_transition_holo_dark = 2131165235; - + // aapt resource value: 0x7F070034 public const int abc_list_selector_background_transition_holo_light = 2131165236; - + // aapt resource value: 0x7F070035 public const int abc_list_selector_disabled_holo_dark = 2131165237; - + // aapt resource value: 0x7F070036 public const int abc_list_selector_disabled_holo_light = 2131165238; - + // aapt resource value: 0x7F070037 public const int abc_list_selector_holo_dark = 2131165239; - + // aapt resource value: 0x7F070038 public const int abc_list_selector_holo_light = 2131165240; - + // aapt resource value: 0x7F070039 public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - + // aapt resource value: 0x7F07003A public const int abc_popup_background_mtrl_mult = 2131165242; - + // aapt resource value: 0x7F07003B public const int abc_ratingbar_indicator_material = 2131165243; - + // aapt resource value: 0x7F07003C public const int abc_ratingbar_material = 2131165244; - + // aapt resource value: 0x7F07003D public const int abc_ratingbar_small_material = 2131165245; - + // aapt resource value: 0x7F07003E public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - + // aapt resource value: 0x7F07003F public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - + // aapt resource value: 0x7F070040 public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - + // aapt resource value: 0x7F070041 public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - + // aapt resource value: 0x7F070042 public const int abc_scrubber_track_mtrl_alpha = 2131165250; - + // aapt resource value: 0x7F070043 public const int abc_seekbar_thumb_material = 2131165251; - + // aapt resource value: 0x7F070044 public const int abc_seekbar_tick_mark_material = 2131165252; - + // aapt resource value: 0x7F070045 public const int abc_seekbar_track_material = 2131165253; - + // aapt resource value: 0x7F070046 public const int abc_spinner_mtrl_am_alpha = 2131165254; - + // aapt resource value: 0x7F070047 public const int abc_spinner_textfield_background_material = 2131165255; - + // aapt resource value: 0x7F070048 public const int abc_switch_thumb_material = 2131165256; - + // aapt resource value: 0x7F070049 public const int abc_switch_track_mtrl_alpha = 2131165257; - + // aapt resource value: 0x7F07004A public const int abc_tab_indicator_material = 2131165258; - + // aapt resource value: 0x7F07004B public const int abc_tab_indicator_mtrl_alpha = 2131165259; - + // aapt resource value: 0x7F070053 public const int abc_textfield_activated_mtrl_alpha = 2131165267; - + // aapt resource value: 0x7F070054 public const int abc_textfield_default_mtrl_alpha = 2131165268; - + // aapt resource value: 0x7F070055 public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - + // aapt resource value: 0x7F070056 public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - + // aapt resource value: 0x7F070057 public const int abc_textfield_search_material = 2131165271; - + // aapt resource value: 0x7F07004C public const int abc_text_cursor_material = 2131165260; - + // aapt resource value: 0x7F07004D public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - + // aapt resource value: 0x7F07004E public const int abc_text_select_handle_left_mtrl_light = 2131165262; - + // aapt resource value: 0x7F07004F public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - + // aapt resource value: 0x7F070050 public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - + // aapt resource value: 0x7F070051 public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - + // aapt resource value: 0x7F070052 public const int abc_text_select_handle_right_mtrl_light = 2131165266; - + // aapt resource value: 0x7F070058 public const int abc_vector_test = 2131165272; - + // aapt resource value: 0x7F070059 public const int avd_hide_password = 2131165273; - + // aapt resource value: 0x7F07005A public const int avd_show_password = 2131165274; - + // aapt resource value: 0x7F07005B public const int design_bottom_navigation_item_background = 2131165275; - + // aapt resource value: 0x7F07005C public const int design_fab_background = 2131165276; - + // aapt resource value: 0x7F07005D public const int design_ic_visibility = 2131165277; - + // aapt resource value: 0x7F07005E public const int design_ic_visibility_off = 2131165278; - + // aapt resource value: 0x7F07005F public const int design_password_eye = 2131165279; - + // aapt resource value: 0x7F070060 public const int design_snackbar_background = 2131165280; - + // aapt resource value: 0x7F070061 public const int ic_audiotrack_dark = 2131165281; - + // aapt resource value: 0x7F070062 public const int ic_audiotrack_light = 2131165282; - + // aapt resource value: 0x7F070063 public const int ic_dialog_close_dark = 2131165283; - + // aapt resource value: 0x7F070064 public const int ic_dialog_close_light = 2131165284; - + // aapt resource value: 0x7F070065 public const int ic_group_collapse_00 = 2131165285; - + // aapt resource value: 0x7F070066 public const int ic_group_collapse_01 = 2131165286; - + // aapt resource value: 0x7F070067 public const int ic_group_collapse_02 = 2131165287; - + // aapt resource value: 0x7F070068 public const int ic_group_collapse_03 = 2131165288; - + // aapt resource value: 0x7F070069 public const int ic_group_collapse_04 = 2131165289; - + // aapt resource value: 0x7F07006A public const int ic_group_collapse_05 = 2131165290; - + // aapt resource value: 0x7F07006B public const int ic_group_collapse_06 = 2131165291; - + // aapt resource value: 0x7F07006C public const int ic_group_collapse_07 = 2131165292; - + // aapt resource value: 0x7F07006D public const int ic_group_collapse_08 = 2131165293; - + // aapt resource value: 0x7F07006E public const int ic_group_collapse_09 = 2131165294; - + // aapt resource value: 0x7F07006F public const int ic_group_collapse_10 = 2131165295; - + // aapt resource value: 0x7F070070 public const int ic_group_collapse_11 = 2131165296; - + // aapt resource value: 0x7F070071 public const int ic_group_collapse_12 = 2131165297; - + // aapt resource value: 0x7F070072 public const int ic_group_collapse_13 = 2131165298; - + // aapt resource value: 0x7F070073 public const int ic_group_collapse_14 = 2131165299; - + // aapt resource value: 0x7F070074 public const int ic_group_collapse_15 = 2131165300; - + // aapt resource value: 0x7F070075 public const int ic_group_expand_00 = 2131165301; - + // aapt resource value: 0x7F070076 public const int ic_group_expand_01 = 2131165302; - + // aapt resource value: 0x7F070077 public const int ic_group_expand_02 = 2131165303; - + // aapt resource value: 0x7F070078 public const int ic_group_expand_03 = 2131165304; - + // aapt resource value: 0x7F070079 public const int ic_group_expand_04 = 2131165305; - + // aapt resource value: 0x7F07007A public const int ic_group_expand_05 = 2131165306; - + // aapt resource value: 0x7F07007B public const int ic_group_expand_06 = 2131165307; - + // aapt resource value: 0x7F07007C public const int ic_group_expand_07 = 2131165308; - + // aapt resource value: 0x7F07007D public const int ic_group_expand_08 = 2131165309; - + // aapt resource value: 0x7F07007E public const int ic_group_expand_09 = 2131165310; - + // aapt resource value: 0x7F07007F public const int ic_group_expand_10 = 2131165311; - + // aapt resource value: 0x7F070080 public const int ic_group_expand_11 = 2131165312; - + // aapt resource value: 0x7F070081 public const int ic_group_expand_12 = 2131165313; - + // aapt resource value: 0x7F070082 public const int ic_group_expand_13 = 2131165314; - + // aapt resource value: 0x7F070083 public const int ic_group_expand_14 = 2131165315; - + // aapt resource value: 0x7F070084 public const int ic_group_expand_15 = 2131165316; - + // aapt resource value: 0x7F070085 public const int ic_media_pause_dark = 2131165317; - + // aapt resource value: 0x7F070086 public const int ic_media_pause_light = 2131165318; - + // aapt resource value: 0x7F070087 public const int ic_media_play_dark = 2131165319; - + // aapt resource value: 0x7F070088 public const int ic_media_play_light = 2131165320; - + // aapt resource value: 0x7F070089 public const int ic_media_stop_dark = 2131165321; - + // aapt resource value: 0x7F07008A public const int ic_media_stop_light = 2131165322; - + // aapt resource value: 0x7F07008B public const int ic_mr_button_connected_00_dark = 2131165323; - + // aapt resource value: 0x7F07008C public const int ic_mr_button_connected_00_light = 2131165324; - + // aapt resource value: 0x7F07008D public const int ic_mr_button_connected_01_dark = 2131165325; - + // aapt resource value: 0x7F07008E public const int ic_mr_button_connected_01_light = 2131165326; - + // aapt resource value: 0x7F07008F public const int ic_mr_button_connected_02_dark = 2131165327; - + // aapt resource value: 0x7F070090 public const int ic_mr_button_connected_02_light = 2131165328; - + // aapt resource value: 0x7F070091 public const int ic_mr_button_connected_03_dark = 2131165329; - + // aapt resource value: 0x7F070092 public const int ic_mr_button_connected_03_light = 2131165330; - + // aapt resource value: 0x7F070093 public const int ic_mr_button_connected_04_dark = 2131165331; - + // aapt resource value: 0x7F070094 public const int ic_mr_button_connected_04_light = 2131165332; - + // aapt resource value: 0x7F070095 public const int ic_mr_button_connected_05_dark = 2131165333; - + // aapt resource value: 0x7F070096 public const int ic_mr_button_connected_05_light = 2131165334; - + // aapt resource value: 0x7F070097 public const int ic_mr_button_connected_06_dark = 2131165335; - + // aapt resource value: 0x7F070098 public const int ic_mr_button_connected_06_light = 2131165336; - + // aapt resource value: 0x7F070099 public const int ic_mr_button_connected_07_dark = 2131165337; - + // aapt resource value: 0x7F07009A public const int ic_mr_button_connected_07_light = 2131165338; - + // aapt resource value: 0x7F07009B public const int ic_mr_button_connected_08_dark = 2131165339; - + // aapt resource value: 0x7F07009C public const int ic_mr_button_connected_08_light = 2131165340; - + // aapt resource value: 0x7F07009D public const int ic_mr_button_connected_09_dark = 2131165341; - + // aapt resource value: 0x7F07009E public const int ic_mr_button_connected_09_light = 2131165342; - + // aapt resource value: 0x7F07009F public const int ic_mr_button_connected_10_dark = 2131165343; - + // aapt resource value: 0x7F0700A0 public const int ic_mr_button_connected_10_light = 2131165344; - + // aapt resource value: 0x7F0700A1 public const int ic_mr_button_connected_11_dark = 2131165345; - + // aapt resource value: 0x7F0700A2 public const int ic_mr_button_connected_11_light = 2131165346; - + // aapt resource value: 0x7F0700A3 public const int ic_mr_button_connected_12_dark = 2131165347; - + // aapt resource value: 0x7F0700A4 public const int ic_mr_button_connected_12_light = 2131165348; - + // aapt resource value: 0x7F0700A5 public const int ic_mr_button_connected_13_dark = 2131165349; - + // aapt resource value: 0x7F0700A6 public const int ic_mr_button_connected_13_light = 2131165350; - + // aapt resource value: 0x7F0700A7 public const int ic_mr_button_connected_14_dark = 2131165351; - + // aapt resource value: 0x7F0700A8 public const int ic_mr_button_connected_14_light = 2131165352; - + // aapt resource value: 0x7F0700A9 public const int ic_mr_button_connected_15_dark = 2131165353; - + // aapt resource value: 0x7F0700AA public const int ic_mr_button_connected_15_light = 2131165354; - + // aapt resource value: 0x7F0700AB public const int ic_mr_button_connected_16_dark = 2131165355; - + // aapt resource value: 0x7F0700AC public const int ic_mr_button_connected_16_light = 2131165356; - + // aapt resource value: 0x7F0700AD public const int ic_mr_button_connected_17_dark = 2131165357; - + // aapt resource value: 0x7F0700AE public const int ic_mr_button_connected_17_light = 2131165358; - + // aapt resource value: 0x7F0700AF public const int ic_mr_button_connected_18_dark = 2131165359; - + // aapt resource value: 0x7F0700B0 public const int ic_mr_button_connected_18_light = 2131165360; - + // aapt resource value: 0x7F0700B1 public const int ic_mr_button_connected_19_dark = 2131165361; - + // aapt resource value: 0x7F0700B2 public const int ic_mr_button_connected_19_light = 2131165362; - + // aapt resource value: 0x7F0700B3 public const int ic_mr_button_connected_20_dark = 2131165363; - + // aapt resource value: 0x7F0700B4 public const int ic_mr_button_connected_20_light = 2131165364; - + // aapt resource value: 0x7F0700B5 public const int ic_mr_button_connected_21_dark = 2131165365; - + // aapt resource value: 0x7F0700B6 public const int ic_mr_button_connected_21_light = 2131165366; - + // aapt resource value: 0x7F0700B7 public const int ic_mr_button_connected_22_dark = 2131165367; - + // aapt resource value: 0x7F0700B8 public const int ic_mr_button_connected_22_light = 2131165368; - + // aapt resource value: 0x7F0700B9 public const int ic_mr_button_connected_23_dark = 2131165369; - + // aapt resource value: 0x7F0700BA public const int ic_mr_button_connected_23_light = 2131165370; - + // aapt resource value: 0x7F0700BB public const int ic_mr_button_connected_24_dark = 2131165371; - + // aapt resource value: 0x7F0700BC public const int ic_mr_button_connected_24_light = 2131165372; - + // aapt resource value: 0x7F0700BD public const int ic_mr_button_connected_25_dark = 2131165373; - + // aapt resource value: 0x7F0700BE public const int ic_mr_button_connected_25_light = 2131165374; - + // aapt resource value: 0x7F0700BF public const int ic_mr_button_connected_26_dark = 2131165375; - + // aapt resource value: 0x7F0700C0 public const int ic_mr_button_connected_26_light = 2131165376; - + // aapt resource value: 0x7F0700C1 public const int ic_mr_button_connected_27_dark = 2131165377; - + // aapt resource value: 0x7F0700C2 public const int ic_mr_button_connected_27_light = 2131165378; - + // aapt resource value: 0x7F0700C3 public const int ic_mr_button_connected_28_dark = 2131165379; - + // aapt resource value: 0x7F0700C4 public const int ic_mr_button_connected_28_light = 2131165380; - + // aapt resource value: 0x7F0700C5 public const int ic_mr_button_connected_29_dark = 2131165381; - + // aapt resource value: 0x7F0700C6 public const int ic_mr_button_connected_29_light = 2131165382; - + // aapt resource value: 0x7F0700C7 public const int ic_mr_button_connected_30_dark = 2131165383; - + // aapt resource value: 0x7F0700C8 public const int ic_mr_button_connected_30_light = 2131165384; - + // aapt resource value: 0x7F0700C9 public const int ic_mr_button_connecting_00_dark = 2131165385; - + // aapt resource value: 0x7F0700CA public const int ic_mr_button_connecting_00_light = 2131165386; - + // aapt resource value: 0x7F0700CB public const int ic_mr_button_connecting_01_dark = 2131165387; - + // aapt resource value: 0x7F0700CC public const int ic_mr_button_connecting_01_light = 2131165388; - + // aapt resource value: 0x7F0700CD public const int ic_mr_button_connecting_02_dark = 2131165389; - + // aapt resource value: 0x7F0700CE public const int ic_mr_button_connecting_02_light = 2131165390; - + // aapt resource value: 0x7F0700CF public const int ic_mr_button_connecting_03_dark = 2131165391; - + // aapt resource value: 0x7F0700D0 public const int ic_mr_button_connecting_03_light = 2131165392; - + // aapt resource value: 0x7F0700D1 public const int ic_mr_button_connecting_04_dark = 2131165393; - + // aapt resource value: 0x7F0700D2 public const int ic_mr_button_connecting_04_light = 2131165394; - + // aapt resource value: 0x7F0700D3 public const int ic_mr_button_connecting_05_dark = 2131165395; - + // aapt resource value: 0x7F0700D4 public const int ic_mr_button_connecting_05_light = 2131165396; - + // aapt resource value: 0x7F0700D5 public const int ic_mr_button_connecting_06_dark = 2131165397; - + // aapt resource value: 0x7F0700D6 public const int ic_mr_button_connecting_06_light = 2131165398; - + // aapt resource value: 0x7F0700D7 public const int ic_mr_button_connecting_07_dark = 2131165399; - + // aapt resource value: 0x7F0700D8 public const int ic_mr_button_connecting_07_light = 2131165400; - + // aapt resource value: 0x7F0700D9 public const int ic_mr_button_connecting_08_dark = 2131165401; - + // aapt resource value: 0x7F0700DA public const int ic_mr_button_connecting_08_light = 2131165402; - + // aapt resource value: 0x7F0700DB public const int ic_mr_button_connecting_09_dark = 2131165403; - + // aapt resource value: 0x7F0700DC public const int ic_mr_button_connecting_09_light = 2131165404; - + // aapt resource value: 0x7F0700DD public const int ic_mr_button_connecting_10_dark = 2131165405; - + // aapt resource value: 0x7F0700DE public const int ic_mr_button_connecting_10_light = 2131165406; - + // aapt resource value: 0x7F0700DF public const int ic_mr_button_connecting_11_dark = 2131165407; - + // aapt resource value: 0x7F0700E0 public const int ic_mr_button_connecting_11_light = 2131165408; - + // aapt resource value: 0x7F0700E1 public const int ic_mr_button_connecting_12_dark = 2131165409; - + // aapt resource value: 0x7F0700E2 public const int ic_mr_button_connecting_12_light = 2131165410; - + // aapt resource value: 0x7F0700E3 public const int ic_mr_button_connecting_13_dark = 2131165411; - + // aapt resource value: 0x7F0700E4 public const int ic_mr_button_connecting_13_light = 2131165412; - + // aapt resource value: 0x7F0700E5 public const int ic_mr_button_connecting_14_dark = 2131165413; - + // aapt resource value: 0x7F0700E6 public const int ic_mr_button_connecting_14_light = 2131165414; - + // aapt resource value: 0x7F0700E7 public const int ic_mr_button_connecting_15_dark = 2131165415; - + // aapt resource value: 0x7F0700E8 public const int ic_mr_button_connecting_15_light = 2131165416; - + // aapt resource value: 0x7F0700E9 public const int ic_mr_button_connecting_16_dark = 2131165417; - + // aapt resource value: 0x7F0700EA public const int ic_mr_button_connecting_16_light = 2131165418; - + // aapt resource value: 0x7F0700EB public const int ic_mr_button_connecting_17_dark = 2131165419; - + // aapt resource value: 0x7F0700EC public const int ic_mr_button_connecting_17_light = 2131165420; - + // aapt resource value: 0x7F0700ED public const int ic_mr_button_connecting_18_dark = 2131165421; - + // aapt resource value: 0x7F0700EE public const int ic_mr_button_connecting_18_light = 2131165422; - + // aapt resource value: 0x7F0700EF public const int ic_mr_button_connecting_19_dark = 2131165423; - + // aapt resource value: 0x7F0700F0 public const int ic_mr_button_connecting_19_light = 2131165424; - + // aapt resource value: 0x7F0700F1 public const int ic_mr_button_connecting_20_dark = 2131165425; - + // aapt resource value: 0x7F0700F2 public const int ic_mr_button_connecting_20_light = 2131165426; - + // aapt resource value: 0x7F0700F3 public const int ic_mr_button_connecting_21_dark = 2131165427; - + // aapt resource value: 0x7F0700F4 public const int ic_mr_button_connecting_21_light = 2131165428; - + // aapt resource value: 0x7F0700F5 public const int ic_mr_button_connecting_22_dark = 2131165429; - + // aapt resource value: 0x7F0700F6 public const int ic_mr_button_connecting_22_light = 2131165430; - + // aapt resource value: 0x7F0700F7 public const int ic_mr_button_connecting_23_dark = 2131165431; - + // aapt resource value: 0x7F0700F8 public const int ic_mr_button_connecting_23_light = 2131165432; - + // aapt resource value: 0x7F0700F9 public const int ic_mr_button_connecting_24_dark = 2131165433; - + // aapt resource value: 0x7F0700FA public const int ic_mr_button_connecting_24_light = 2131165434; - + // aapt resource value: 0x7F0700FB public const int ic_mr_button_connecting_25_dark = 2131165435; - + // aapt resource value: 0x7F0700FC public const int ic_mr_button_connecting_25_light = 2131165436; - + // aapt resource value: 0x7F0700FD public const int ic_mr_button_connecting_26_dark = 2131165437; - + // aapt resource value: 0x7F0700FE public const int ic_mr_button_connecting_26_light = 2131165438; - + // aapt resource value: 0x7F0700FF public const int ic_mr_button_connecting_27_dark = 2131165439; - + // aapt resource value: 0x7F070100 public const int ic_mr_button_connecting_27_light = 2131165440; - + // aapt resource value: 0x7F070101 public const int ic_mr_button_connecting_28_dark = 2131165441; - + // aapt resource value: 0x7F070102 public const int ic_mr_button_connecting_28_light = 2131165442; - + // aapt resource value: 0x7F070103 public const int ic_mr_button_connecting_29_dark = 2131165443; - + // aapt resource value: 0x7F070104 public const int ic_mr_button_connecting_29_light = 2131165444; - + // aapt resource value: 0x7F070105 public const int ic_mr_button_connecting_30_dark = 2131165445; - + // aapt resource value: 0x7F070106 public const int ic_mr_button_connecting_30_light = 2131165446; - + // aapt resource value: 0x7F070107 public const int ic_mr_button_disabled_dark = 2131165447; - + // aapt resource value: 0x7F070108 public const int ic_mr_button_disabled_light = 2131165448; - + // aapt resource value: 0x7F070109 public const int ic_mr_button_disconnected_dark = 2131165449; - + // aapt resource value: 0x7F07010A public const int ic_mr_button_disconnected_light = 2131165450; - + // aapt resource value: 0x7F07010B public const int ic_mr_button_grey = 2131165451; - + // aapt resource value: 0x7F07010C public const int ic_vol_type_speaker_dark = 2131165452; - + // aapt resource value: 0x7F07010D public const int ic_vol_type_speaker_group_dark = 2131165453; - + // aapt resource value: 0x7F07010E public const int ic_vol_type_speaker_group_light = 2131165454; - + // aapt resource value: 0x7F07010F public const int ic_vol_type_speaker_light = 2131165455; - + // aapt resource value: 0x7F070110 public const int ic_vol_type_tv_dark = 2131165456; - + // aapt resource value: 0x7F070111 public const int ic_vol_type_tv_light = 2131165457; - + // aapt resource value: 0x7F070112 public const int mr_button_connected_dark = 2131165458; - + // aapt resource value: 0x7F070113 public const int mr_button_connected_light = 2131165459; - + // aapt resource value: 0x7F070114 public const int mr_button_connecting_dark = 2131165460; - + // aapt resource value: 0x7F070115 public const int mr_button_connecting_light = 2131165461; - + // aapt resource value: 0x7F070116 public const int mr_button_dark = 2131165462; - + // aapt resource value: 0x7F070117 public const int mr_button_light = 2131165463; - + // aapt resource value: 0x7F070118 public const int mr_dialog_close_dark = 2131165464; - + // aapt resource value: 0x7F070119 public const int mr_dialog_close_light = 2131165465; - + // aapt resource value: 0x7F07011A public const int mr_dialog_material_background_dark = 2131165466; - + // aapt resource value: 0x7F07011B public const int mr_dialog_material_background_light = 2131165467; - + // aapt resource value: 0x7F07011C public const int mr_group_collapse = 2131165468; - + // aapt resource value: 0x7F07011D public const int mr_group_expand = 2131165469; - + // aapt resource value: 0x7F07011E public const int mr_media_pause_dark = 2131165470; - + // aapt resource value: 0x7F07011F public const int mr_media_pause_light = 2131165471; - + // aapt resource value: 0x7F070120 public const int mr_media_play_dark = 2131165472; - + // aapt resource value: 0x7F070121 public const int mr_media_play_light = 2131165473; - + // aapt resource value: 0x7F070122 public const int mr_media_stop_dark = 2131165474; - + // aapt resource value: 0x7F070123 public const int mr_media_stop_light = 2131165475; - + // aapt resource value: 0x7F070124 public const int mr_vol_type_audiotrack_dark = 2131165476; - + // aapt resource value: 0x7F070125 public const int mr_vol_type_audiotrack_light = 2131165477; - + // aapt resource value: 0x7F070126 public const int navigation_empty_icon = 2131165478; - + // aapt resource value: 0x7F070127 public const int notification_action_background = 2131165479; - + // aapt resource value: 0x7F070128 public const int notification_bg = 2131165480; - + // aapt resource value: 0x7F070129 public const int notification_bg_low = 2131165481; - + // aapt resource value: 0x7F07012A public const int notification_bg_low_normal = 2131165482; - + // aapt resource value: 0x7F07012B public const int notification_bg_low_pressed = 2131165483; - + // aapt resource value: 0x7F07012C public const int notification_bg_normal = 2131165484; - + // aapt resource value: 0x7F07012D public const int notification_bg_normal_pressed = 2131165485; - + // aapt resource value: 0x7F07012E public const int notification_icon_background = 2131165486; - + // aapt resource value: 0x7F07012F public const int notification_template_icon_bg = 2131165487; - + // aapt resource value: 0x7F070130 public const int notification_template_icon_low_bg = 2131165488; - + // aapt resource value: 0x7F070131 public const int notification_tile_bg = 2131165489; - + // aapt resource value: 0x7F070132 public const int notify_panel_notification_icon_bg = 2131165490; - + // aapt resource value: 0x7F070133 public const int tooltip_frame_dark = 2131165491; - + // aapt resource value: 0x7F070134 public const int tooltip_frame_light = 2131165492; - + static Drawable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Drawable() { } } - + public partial class Id { - + // aapt resource value: 0x7F080006 public const int action0 = 2131230726; - + // aapt resource value: 0x7F080018 public const int actions = 2131230744; - + // aapt resource value: 0x7F080007 public const int action_bar = 2131230727; - + // aapt resource value: 0x7F080008 public const int action_bar_activity_content = 2131230728; - + // aapt resource value: 0x7F080009 public const int action_bar_container = 2131230729; - + // aapt resource value: 0x7F08000A public const int action_bar_root = 2131230730; - + // aapt resource value: 0x7F08000B public const int action_bar_spinner = 2131230731; - + // aapt resource value: 0x7F08000C public const int action_bar_subtitle = 2131230732; - + // aapt resource value: 0x7F08000D public const int action_bar_title = 2131230733; - + // aapt resource value: 0x7F08000E public const int action_container = 2131230734; - + // aapt resource value: 0x7F08000F public const int action_context_bar = 2131230735; - + // aapt resource value: 0x7F080010 public const int action_divider = 2131230736; - + // aapt resource value: 0x7F080011 public const int action_image = 2131230737; - + // aapt resource value: 0x7F080012 public const int action_menu_divider = 2131230738; - + // aapt resource value: 0x7F080013 public const int action_menu_presenter = 2131230739; - + // aapt resource value: 0x7F080014 public const int action_mode_bar = 2131230740; - + // aapt resource value: 0x7F080015 public const int action_mode_bar_stub = 2131230741; - + // aapt resource value: 0x7F080016 public const int action_mode_close_button = 2131230742; - + // aapt resource value: 0x7F080017 public const int action_text = 2131230743; - + // aapt resource value: 0x7F080019 public const int activity_chooser_view_content = 2131230745; - + // aapt resource value: 0x7F08001A public const int add = 2131230746; - + // aapt resource value: 0x7F08001B public const int alertTitle = 2131230747; - + // aapt resource value: 0x7F08001C public const int all = 2131230748; - + // aapt resource value: 0x7F080000 public const int ALT = 2131230720; - + // aapt resource value: 0x7F08001D public const int always = 2131230749; - + // aapt resource value: 0x7F08001E public const int async = 2131230750; - + // aapt resource value: 0x7F08001F public const int auto = 2131230751; - + // aapt resource value: 0x7F080020 public const int beginning = 2131230752; - + // aapt resource value: 0x7F080021 public const int blocking = 2131230753; - + // aapt resource value: 0x7F080022 public const int bottom = 2131230754; - + // aapt resource value: 0x7F080023 public const int bottomtab_navarea = 2131230755; - + // aapt resource value: 0x7F080024 public const int bottomtab_tabbar = 2131230756; - + // aapt resource value: 0x7F080025 public const int buttonPanel = 2131230757; - + // aapt resource value: 0x7F080026 public const int cancel_action = 2131230758; - + // aapt resource value: 0x7F080027 public const int center = 2131230759; - + // aapt resource value: 0x7F080028 public const int center_horizontal = 2131230760; - + // aapt resource value: 0x7F080029 public const int center_vertical = 2131230761; - + // aapt resource value: 0x7F08002A public const int checkbox = 2131230762; - + // aapt resource value: 0x7F08002B public const int chronometer = 2131230763; - + // aapt resource value: 0x7F08002C public const int clip_horizontal = 2131230764; - + // aapt resource value: 0x7F08002D public const int clip_vertical = 2131230765; - + // aapt resource value: 0x7F08002E public const int collapseActionView = 2131230766; - + // aapt resource value: 0x7F08002F public const int container = 2131230767; - + // aapt resource value: 0x7F080030 public const int contentPanel = 2131230768; - + // aapt resource value: 0x7F080031 public const int coordinator = 2131230769; - + // aapt resource value: 0x7F080001 public const int CTRL = 2131230721; - + // aapt resource value: 0x7F080032 public const int custom = 2131230770; - + // aapt resource value: 0x7F080033 public const int customPanel = 2131230771; - + // aapt resource value: 0x7F080034 public const int decor_content_parent = 2131230772; - + // aapt resource value: 0x7F080035 public const int default_activity_button = 2131230773; - + // aapt resource value: 0x7F080036 public const int design_bottom_sheet = 2131230774; - + // aapt resource value: 0x7F080037 public const int design_menu_item_action_area = 2131230775; - + // aapt resource value: 0x7F080038 public const int design_menu_item_action_area_stub = 2131230776; - + // aapt resource value: 0x7F080039 public const int design_menu_item_text = 2131230777; - + // aapt resource value: 0x7F08003A public const int design_navigation_view = 2131230778; - + // aapt resource value: 0x7F08003B public const int disableHome = 2131230779; - + // aapt resource value: 0x7F08003C public const int edit_query = 2131230780; - + // aapt resource value: 0x7F08003D public const int end = 2131230781; - + // aapt resource value: 0x7F08003E public const int end_padder = 2131230782; - + // aapt resource value: 0x7F08003F public const int enterAlways = 2131230783; - + // aapt resource value: 0x7F080040 public const int enterAlwaysCollapsed = 2131230784; - + // aapt resource value: 0x7F080041 public const int exitUntilCollapsed = 2131230785; - + // aapt resource value: 0x7F080043 public const int expanded_menu = 2131230787; - + // aapt resource value: 0x7F080042 public const int expand_activities_button = 2131230786; - + // aapt resource value: 0x7F080044 public const int fill = 2131230788; - + // aapt resource value: 0x7F080045 public const int fill_horizontal = 2131230789; - + // aapt resource value: 0x7F080046 public const int fill_vertical = 2131230790; - + // aapt resource value: 0x7F080047 public const int @fixed = 2131230791; - + // aapt resource value: 0x7F080048 public const int flyoutcontent_appbar = 2131230792; - + // aapt resource value: 0x7F080049 public const int flyoutcontent_recycler = 2131230793; - + // aapt resource value: 0x7F08004A public const int forever = 2131230794; - + // aapt resource value: 0x7F080002 public const int FUNCTION = 2131230722; - + // aapt resource value: 0x7F08004B public const int ghost_view = 2131230795; - + // aapt resource value: 0x7F08004C public const int home = 2131230796; - + // aapt resource value: 0x7F08004D public const int homeAsUp = 2131230797; - + // aapt resource value: 0x7F08004E public const int icon = 2131230798; - + // aapt resource value: 0x7F08004F public const int icon_group = 2131230799; - + // aapt resource value: 0x7F080050 public const int ifRoom = 2131230800; - + // aapt resource value: 0x7F080051 public const int image = 2131230801; - + // aapt resource value: 0x7F080052 public const int info = 2131230802; - + // aapt resource value: 0x7F080053 public const int italic = 2131230803; - + // aapt resource value: 0x7F080054 public const int item_touch_helper_previous_elevation = 2131230804; - + // aapt resource value: 0x7F080055 public const int largeLabel = 2131230805; - + // aapt resource value: 0x7F080056 public const int left = 2131230806; - + // aapt resource value: 0x7F080057 public const int line1 = 2131230807; - + // aapt resource value: 0x7F080058 public const int line3 = 2131230808; - + // aapt resource value: 0x7F080059 public const int listMode = 2131230809; - + // aapt resource value: 0x7F08005A public const int list_item = 2131230810; - + // aapt resource value: 0x7F08005B public const int main_appbar = 2131230811; - + // aapt resource value: 0x7F08005C public const int main_scrollview = 2131230812; - + // aapt resource value: 0x7F08005D public const int main_tablayout = 2131230813; - + // aapt resource value: 0x7F08005E public const int main_toolbar = 2131230814; - + // aapt resource value: 0x7F08005F public const int masked = 2131230815; - + // aapt resource value: 0x7F080060 public const int media_actions = 2131230816; - + // aapt resource value: 0x7F080061 public const int message = 2131230817; - + // aapt resource value: 0x7F080003 public const int META = 2131230723; - + // aapt resource value: 0x7F080062 public const int middle = 2131230818; - + // aapt resource value: 0x7F080063 public const int mini = 2131230819; - + // aapt resource value: 0x7F080064 public const int mr_art = 2131230820; - + // aapt resource value: 0x7F080065 public const int mr_chooser_list = 2131230821; - + // aapt resource value: 0x7F080066 public const int mr_chooser_route_desc = 2131230822; - + // aapt resource value: 0x7F080067 public const int mr_chooser_route_icon = 2131230823; - + // aapt resource value: 0x7F080068 public const int mr_chooser_route_name = 2131230824; - + // aapt resource value: 0x7F080069 public const int mr_chooser_title = 2131230825; - + // aapt resource value: 0x7F08006A public const int mr_close = 2131230826; - + // aapt resource value: 0x7F08006B public const int mr_control_divider = 2131230827; - + // aapt resource value: 0x7F08006C public const int mr_control_playback_ctrl = 2131230828; - + // aapt resource value: 0x7F08006D public const int mr_control_subtitle = 2131230829; - + // aapt resource value: 0x7F08006E public const int mr_control_title = 2131230830; - + // aapt resource value: 0x7F08006F public const int mr_control_title_container = 2131230831; - + // aapt resource value: 0x7F080070 public const int mr_custom_control = 2131230832; - + // aapt resource value: 0x7F080071 public const int mr_default_control = 2131230833; - + // aapt resource value: 0x7F080072 public const int mr_dialog_area = 2131230834; - + // aapt resource value: 0x7F080073 public const int mr_expandable_area = 2131230835; - + // aapt resource value: 0x7F080074 public const int mr_group_expand_collapse = 2131230836; - + // aapt resource value: 0x7F080075 public const int mr_media_main_control = 2131230837; - + // aapt resource value: 0x7F080076 public const int mr_name = 2131230838; - + // aapt resource value: 0x7F080077 public const int mr_playback_control = 2131230839; - + // aapt resource value: 0x7F080078 public const int mr_title_bar = 2131230840; - + // aapt resource value: 0x7F080079 public const int mr_volume_control = 2131230841; - + // aapt resource value: 0x7F08007A public const int mr_volume_group_list = 2131230842; - + // aapt resource value: 0x7F08007B public const int mr_volume_item_icon = 2131230843; - + // aapt resource value: 0x7F08007C public const int mr_volume_slider = 2131230844; - + // aapt resource value: 0x7F08007D public const int multiply = 2131230845; - + // aapt resource value: 0x7F08007E public const int navigation_header_container = 2131230846; - + // aapt resource value: 0x7F08007F public const int never = 2131230847; - + // aapt resource value: 0x7F080080 public const int none = 2131230848; - + // aapt resource value: 0x7F080081 public const int normal = 2131230849; - + // aapt resource value: 0x7F080082 public const int notification_background = 2131230850; - + // aapt resource value: 0x7F080083 public const int notification_main_column = 2131230851; - + // aapt resource value: 0x7F080084 public const int notification_main_column_container = 2131230852; - + // aapt resource value: 0x7F080085 public const int parallax = 2131230853; - + // aapt resource value: 0x7F080086 public const int parentPanel = 2131230854; - + // aapt resource value: 0x7F080087 public const int parent_matrix = 2131230855; - + // aapt resource value: 0x7F080088 public const int pin = 2131230856; - + // aapt resource value: 0x7F080089 public const int progress_circular = 2131230857; - + // aapt resource value: 0x7F08008A public const int progress_horizontal = 2131230858; - + // aapt resource value: 0x7F08008B public const int radio = 2131230859; - + // aapt resource value: 0x7F08008C public const int right = 2131230860; - + // aapt resource value: 0x7F08008D public const int right_icon = 2131230861; - + // aapt resource value: 0x7F08008E public const int right_side = 2131230862; - + // aapt resource value: 0x7F08008F public const int save_image_matrix = 2131230863; - + // aapt resource value: 0x7F080090 public const int save_non_transition_alpha = 2131230864; - + // aapt resource value: 0x7F080091 public const int save_scale_type = 2131230865; - + // aapt resource value: 0x7F080092 public const int screen = 2131230866; - + // aapt resource value: 0x7F080093 public const int scroll = 2131230867; - + // aapt resource value: 0x7F080097 public const int scrollable = 2131230871; - + // aapt resource value: 0x7F080094 public const int scrollIndicatorDown = 2131230868; - + // aapt resource value: 0x7F080095 public const int scrollIndicatorUp = 2131230869; - + // aapt resource value: 0x7F080096 public const int scrollView = 2131230870; - + // aapt resource value: 0x7F080098 public const int search_badge = 2131230872; - + // aapt resource value: 0x7F080099 public const int search_bar = 2131230873; - + // aapt resource value: 0x7F08009A public const int search_button = 2131230874; - + // aapt resource value: 0x7F08009B public const int search_close_btn = 2131230875; - + // aapt resource value: 0x7F08009C public const int search_edit_frame = 2131230876; - + // aapt resource value: 0x7F08009D public const int search_go_btn = 2131230877; - + // aapt resource value: 0x7F08009E public const int search_mag_icon = 2131230878; - + // aapt resource value: 0x7F08009F public const int search_plate = 2131230879; - + // aapt resource value: 0x7F0800A0 public const int search_src_text = 2131230880; - + // aapt resource value: 0x7F0800A1 public const int search_voice_btn = 2131230881; - + // aapt resource value: 0x7F0800A2 public const int select_dialog_listview = 2131230882; - + // aapt resource value: 0x7F0800A3 public const int shellcontent_appbar = 2131230883; - + // aapt resource value: 0x7F0800A4 public const int shellcontent_scrollview = 2131230884; - + // aapt resource value: 0x7F0800A5 public const int shellcontent_toolbar = 2131230885; - + // aapt resource value: 0x7F080004 public const int SHIFT = 2131230724; - + // aapt resource value: 0x7F0800A6 public const int shortcut = 2131230886; - + // aapt resource value: 0x7F0800A7 public const int showCustom = 2131230887; - + // aapt resource value: 0x7F0800A8 public const int showHome = 2131230888; - + // aapt resource value: 0x7F0800A9 public const int showTitle = 2131230889; - + // aapt resource value: 0x7F0800AA public const int smallLabel = 2131230890; - + // aapt resource value: 0x7F0800AB public const int snackbar_action = 2131230891; - + // aapt resource value: 0x7F0800AC public const int snackbar_text = 2131230892; - + // aapt resource value: 0x7F0800AD public const int snap = 2131230893; - + // aapt resource value: 0x7F0800AE public const int spacer = 2131230894; - + // aapt resource value: 0x7F0800AF public const int split_action_bar = 2131230895; - + // aapt resource value: 0x7F0800B0 public const int src_atop = 2131230896; - + // aapt resource value: 0x7F0800B1 public const int src_in = 2131230897; - + // aapt resource value: 0x7F0800B2 public const int src_over = 2131230898; - + // aapt resource value: 0x7F0800B3 public const int start = 2131230899; - + // aapt resource value: 0x7F0800B4 public const int status_bar_latest_event_content = 2131230900; - + // aapt resource value: 0x7F0800B5 public const int submenuarrow = 2131230901; - + // aapt resource value: 0x7F0800B6 public const int submit_area = 2131230902; - + // aapt resource value: 0x7F080005 public const int SYM = 2131230725; - + // aapt resource value: 0x7F0800B7 public const int tabMode = 2131230903; - + // aapt resource value: 0x7F0800B8 public const int tag_transition_group = 2131230904; - + // aapt resource value: 0x7F0800B9 public const int text = 2131230905; - + // aapt resource value: 0x7F0800BA public const int text2 = 2131230906; - + // aapt resource value: 0x7F0800BE public const int textinput_counter = 2131230910; - + // aapt resource value: 0x7F0800BF public const int textinput_error = 2131230911; - + // aapt resource value: 0x7F0800BB public const int textSpacerNoButtons = 2131230907; - + // aapt resource value: 0x7F0800BC public const int textSpacerNoTitle = 2131230908; - + // aapt resource value: 0x7F0800BD public const int text_input_password_toggle = 2131230909; - + // aapt resource value: 0x7F0800C0 public const int time = 2131230912; - + // aapt resource value: 0x7F0800C1 public const int title = 2131230913; - + // aapt resource value: 0x7F0800C2 public const int titleDividerNoCustom = 2131230914; - + // aapt resource value: 0x7F0800C3 public const int title_template = 2131230915; - + // aapt resource value: 0x7F0800C4 public const int top = 2131230916; - + // aapt resource value: 0x7F0800C5 public const int topPanel = 2131230917; - + // aapt resource value: 0x7F0800C6 public const int touch_outside = 2131230918; - + // aapt resource value: 0x7F0800C7 public const int transition_current_scene = 2131230919; - + // aapt resource value: 0x7F0800C8 public const int transition_layout_save = 2131230920; - + // aapt resource value: 0x7F0800C9 public const int transition_position = 2131230921; - + // aapt resource value: 0x7F0800CA public const int transition_scene_layoutid_cache = 2131230922; - + // aapt resource value: 0x7F0800CB public const int transition_transform = 2131230923; - + // aapt resource value: 0x7F0800CC public const int uniform = 2131230924; - + // aapt resource value: 0x7F0800CD public const int up = 2131230925; - + // aapt resource value: 0x7F0800CE public const int useLogo = 2131230926; - + // aapt resource value: 0x7F0800CF public const int view_offset_helper = 2131230927; - + // aapt resource value: 0x7F0800D0 public const int visible = 2131230928; - + // aapt resource value: 0x7F0800D1 public const int volume_item_container = 2131230929; - + // aapt resource value: 0x7F0800D2 public const int withText = 2131230930; - + // aapt resource value: 0x7F0800D3 public const int wrap_content = 2131230931; - + static Id() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Id() { } } - + public partial class Integer { - + // aapt resource value: 0x7F090000 public const int abc_config_activityDefaultDur = 2131296256; - + // aapt resource value: 0x7F090001 public const int abc_config_activityShortDur = 2131296257; - + // aapt resource value: 0x7F090002 public const int app_bar_elevation_anim_duration = 2131296258; - + // aapt resource value: 0x7F090003 public const int bottom_sheet_slide_duration = 2131296259; - + // aapt resource value: 0x7F090004 public const int cancel_button_image_alpha = 2131296260; - + // aapt resource value: 0x7F090005 public const int config_tooltipAnimTime = 2131296261; - + // aapt resource value: 0x7F090006 public const int design_snackbar_text_max_lines = 2131296262; - + // aapt resource value: 0x7F090007 public const int hide_password_duration = 2131296263; - + // aapt resource value: 0x7F090008 public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - + // aapt resource value: 0x7F090009 public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - + // aapt resource value: 0x7F09000A public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - + // aapt resource value: 0x7F09000B public const int show_password_duration = 2131296267; - + // aapt resource value: 0x7F09000C public const int status_bar_notification_info_maxnum = 2131296268; - + static Integer() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Integer() { } } - + public partial class Interpolator { - + // aapt resource value: 0x7F0A0000 public const int mr_fast_out_slow_in = 2131361792; - + // aapt resource value: 0x7F0A0001 public const int mr_linear_out_slow_in = 2131361793; - + static Interpolator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Interpolator() { } } - + public partial class Layout { - + // aapt resource value: 0x7F0B0000 public const int abc_action_bar_title_item = 2131427328; - + // aapt resource value: 0x7F0B0001 public const int abc_action_bar_up_container = 2131427329; - + // aapt resource value: 0x7F0B0002 public const int abc_action_menu_item_layout = 2131427330; - + // aapt resource value: 0x7F0B0003 public const int abc_action_menu_layout = 2131427331; - + // aapt resource value: 0x7F0B0004 public const int abc_action_mode_bar = 2131427332; - + // aapt resource value: 0x7F0B0005 public const int abc_action_mode_close_item_material = 2131427333; - + // aapt resource value: 0x7F0B0006 public const int abc_activity_chooser_view = 2131427334; - + // aapt resource value: 0x7F0B0007 public const int abc_activity_chooser_view_list_item = 2131427335; - + // aapt resource value: 0x7F0B0008 public const int abc_alert_dialog_button_bar_material = 2131427336; - + // aapt resource value: 0x7F0B0009 public const int abc_alert_dialog_material = 2131427337; - + // aapt resource value: 0x7F0B000A public const int abc_alert_dialog_title_material = 2131427338; - + // aapt resource value: 0x7F0B000B public const int abc_dialog_title_material = 2131427339; - + // aapt resource value: 0x7F0B000C public const int abc_expanded_menu_layout = 2131427340; - + // aapt resource value: 0x7F0B000D public const int abc_list_menu_item_checkbox = 2131427341; - + // aapt resource value: 0x7F0B000E public const int abc_list_menu_item_icon = 2131427342; - + // aapt resource value: 0x7F0B000F public const int abc_list_menu_item_layout = 2131427343; - + // aapt resource value: 0x7F0B0010 public const int abc_list_menu_item_radio = 2131427344; - + // aapt resource value: 0x7F0B0011 public const int abc_popup_menu_header_item_layout = 2131427345; - + // aapt resource value: 0x7F0B0012 public const int abc_popup_menu_item_layout = 2131427346; - + // aapt resource value: 0x7F0B0013 public const int abc_screen_content_include = 2131427347; - + // aapt resource value: 0x7F0B0014 public const int abc_screen_simple = 2131427348; - + // aapt resource value: 0x7F0B0015 public const int abc_screen_simple_overlay_action_mode = 2131427349; - + // aapt resource value: 0x7F0B0016 public const int abc_screen_toolbar = 2131427350; - + // aapt resource value: 0x7F0B0017 public const int abc_search_dropdown_item_icons_2line = 2131427351; - + // aapt resource value: 0x7F0B0018 public const int abc_search_view = 2131427352; - + // aapt resource value: 0x7F0B0019 public const int abc_select_dialog_material = 2131427353; - + // aapt resource value: 0x7F0B001A public const int activity_main = 2131427354; - + // aapt resource value: 0x7F0B001B public const int BottomTabLayout = 2131427355; - + // aapt resource value: 0x7F0B001C public const int design_bottom_navigation_item = 2131427356; - + // aapt resource value: 0x7F0B001D public const int design_bottom_sheet_dialog = 2131427357; - + // aapt resource value: 0x7F0B001E public const int design_layout_snackbar = 2131427358; - + // aapt resource value: 0x7F0B001F public const int design_layout_snackbar_include = 2131427359; - + // aapt resource value: 0x7F0B0020 public const int design_layout_tab_icon = 2131427360; - + // aapt resource value: 0x7F0B0021 public const int design_layout_tab_text = 2131427361; - + // aapt resource value: 0x7F0B0022 public const int design_menu_item_action_area = 2131427362; - + // aapt resource value: 0x7F0B0023 public const int design_navigation_item = 2131427363; - + // aapt resource value: 0x7F0B0024 public const int design_navigation_item_header = 2131427364; - + // aapt resource value: 0x7F0B0025 public const int design_navigation_item_separator = 2131427365; - + // aapt resource value: 0x7F0B0026 public const int design_navigation_item_subheader = 2131427366; - + // aapt resource value: 0x7F0B0027 public const int design_navigation_menu = 2131427367; - + // aapt resource value: 0x7F0B0028 public const int design_navigation_menu_item = 2131427368; - + // aapt resource value: 0x7F0B0029 public const int design_text_input_password_icon = 2131427369; - + // aapt resource value: 0x7F0B002A public const int FlyoutContent = 2131427370; - + // aapt resource value: 0x7F0B002B public const int mr_chooser_dialog = 2131427371; - + // aapt resource value: 0x7F0B002C public const int mr_chooser_list_item = 2131427372; - + // aapt resource value: 0x7F0B002D public const int mr_controller_material_dialog_b = 2131427373; - + // aapt resource value: 0x7F0B002E public const int mr_controller_volume_item = 2131427374; - + // aapt resource value: 0x7F0B002F public const int mr_playback_control = 2131427375; - + // aapt resource value: 0x7F0B0030 public const int mr_volume_control = 2131427376; - + // aapt resource value: 0x7F0B0031 public const int notification_action = 2131427377; - + // aapt resource value: 0x7F0B0032 public const int notification_action_tombstone = 2131427378; - + // aapt resource value: 0x7F0B0033 public const int notification_media_action = 2131427379; - + // aapt resource value: 0x7F0B0034 public const int notification_media_cancel_action = 2131427380; - + // aapt resource value: 0x7F0B0035 public const int notification_template_big_media = 2131427381; - + // aapt resource value: 0x7F0B0036 public const int notification_template_big_media_custom = 2131427382; - + // aapt resource value: 0x7F0B0037 public const int notification_template_big_media_narrow = 2131427383; - + // aapt resource value: 0x7F0B0038 public const int notification_template_big_media_narrow_custom = 2131427384; - + // aapt resource value: 0x7F0B0039 public const int notification_template_custom_big = 2131427385; - + // aapt resource value: 0x7F0B003A public const int notification_template_icon_group = 2131427386; - + // aapt resource value: 0x7F0B003B public const int notification_template_lines_media = 2131427387; - + // aapt resource value: 0x7F0B003C public const int notification_template_media = 2131427388; - + // aapt resource value: 0x7F0B003D public const int notification_template_media_custom = 2131427389; - + // aapt resource value: 0x7F0B003E public const int notification_template_part_chronometer = 2131427390; - + // aapt resource value: 0x7F0B003F public const int notification_template_part_time = 2131427391; - + // aapt resource value: 0x7F0B0040 public const int RootLayout = 2131427392; - + // aapt resource value: 0x7F0B0041 public const int select_dialog_item_material = 2131427393; - + // aapt resource value: 0x7F0B0042 public const int select_dialog_multichoice_material = 2131427394; - + // aapt resource value: 0x7F0B0043 public const int select_dialog_singlechoice_material = 2131427395; - + // aapt resource value: 0x7F0B0044 public const int ShellContent = 2131427396; - + // aapt resource value: 0x7F0B0045 public const int support_simple_spinner_dropdown_item = 2131427397; - + // aapt resource value: 0x7F0B0046 public const int tooltip = 2131427398; - + static Layout() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Layout() { } } - + public partial class Mipmap { - + // aapt resource value: 0x7F0C0000 public const int ic_launcher = 2131492864; - + // aapt resource value: 0x7F0C0001 public const int ic_launcher_foreground = 2131492865; - + // aapt resource value: 0x7F0C0002 public const int ic_launcher_round = 2131492866; - + static Mipmap() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Mipmap() { } } - + public partial class String { - + // aapt resource value: 0x7F0D0000 public const int abc_action_bar_home_description = 2131558400; - + // aapt resource value: 0x7F0D0001 public const int abc_action_bar_up_description = 2131558401; - + // aapt resource value: 0x7F0D0002 public const int abc_action_menu_overflow_description = 2131558402; - + // aapt resource value: 0x7F0D0003 public const int abc_action_mode_done = 2131558403; - + // aapt resource value: 0x7F0D0005 public const int abc_activitychooserview_choose_application = 2131558405; - + // aapt resource value: 0x7F0D0004 public const int abc_activity_chooser_view_see_all = 2131558404; - + // aapt resource value: 0x7F0D0006 public const int abc_capital_off = 2131558406; - + // aapt resource value: 0x7F0D0007 public const int abc_capital_on = 2131558407; - + // aapt resource value: 0x7F0D0008 public const int abc_font_family_body_1_material = 2131558408; - + // aapt resource value: 0x7F0D0009 public const int abc_font_family_body_2_material = 2131558409; - + // aapt resource value: 0x7F0D000A public const int abc_font_family_button_material = 2131558410; - + // aapt resource value: 0x7F0D000B public const int abc_font_family_caption_material = 2131558411; - + // aapt resource value: 0x7F0D000C public const int abc_font_family_display_1_material = 2131558412; - + // aapt resource value: 0x7F0D000D public const int abc_font_family_display_2_material = 2131558413; - + // aapt resource value: 0x7F0D000E public const int abc_font_family_display_3_material = 2131558414; - + // aapt resource value: 0x7F0D000F public const int abc_font_family_display_4_material = 2131558415; - + // aapt resource value: 0x7F0D0010 public const int abc_font_family_headline_material = 2131558416; - + // aapt resource value: 0x7F0D0011 public const int abc_font_family_menu_material = 2131558417; - + // aapt resource value: 0x7F0D0012 public const int abc_font_family_subhead_material = 2131558418; - + // aapt resource value: 0x7F0D0013 public const int abc_font_family_title_material = 2131558419; - + // aapt resource value: 0x7F0D0015 public const int abc_searchview_description_clear = 2131558421; - + // aapt resource value: 0x7F0D0016 public const int abc_searchview_description_query = 2131558422; - + // aapt resource value: 0x7F0D0017 public const int abc_searchview_description_search = 2131558423; - + // aapt resource value: 0x7F0D0018 public const int abc_searchview_description_submit = 2131558424; - + // aapt resource value: 0x7F0D0019 public const int abc_searchview_description_voice = 2131558425; - + // aapt resource value: 0x7F0D0014 public const int abc_search_hint = 2131558420; - + // aapt resource value: 0x7F0D001A public const int abc_shareactionprovider_share_with = 2131558426; - + // aapt resource value: 0x7F0D001B public const int abc_shareactionprovider_share_with_application = 2131558427; - + // aapt resource value: 0x7F0D001C public const int abc_toolbar_collapse_description = 2131558428; - + // aapt resource value: 0x7F0D001D public const int action_settings = 2131558429; - + // aapt resource value: 0x7F0D001F public const int appbar_scrolling_view_behavior = 2131558431; - + // aapt resource value: 0x7F0D001E public const int app_name = 2131558430; - + // aapt resource value: 0x7F0D0020 public const int bottom_sheet_behavior = 2131558432; - + // aapt resource value: 0x7F0D0021 public const int character_counter_pattern = 2131558433; - + // aapt resource value: 0x7F0D0022 public const int mr_button_content_description = 2131558434; - + // aapt resource value: 0x7F0D0023 public const int mr_cast_button_connected = 2131558435; - + // aapt resource value: 0x7F0D0024 public const int mr_cast_button_connecting = 2131558436; - + // aapt resource value: 0x7F0D0025 public const int mr_cast_button_disconnected = 2131558437; - + // aapt resource value: 0x7F0D0026 public const int mr_chooser_searching = 2131558438; - + // aapt resource value: 0x7F0D0027 public const int mr_chooser_title = 2131558439; - + // aapt resource value: 0x7F0D0028 public const int mr_controller_album_art = 2131558440; - + // aapt resource value: 0x7F0D0029 public const int mr_controller_casting_screen = 2131558441; - + // aapt resource value: 0x7F0D002A public const int mr_controller_close_description = 2131558442; - + // aapt resource value: 0x7F0D002B public const int mr_controller_collapse_group = 2131558443; - + // aapt resource value: 0x7F0D002C public const int mr_controller_disconnect = 2131558444; - + // aapt resource value: 0x7F0D002D public const int mr_controller_expand_group = 2131558445; - + // aapt resource value: 0x7F0D002E public const int mr_controller_no_info_available = 2131558446; - + // aapt resource value: 0x7F0D002F public const int mr_controller_no_media_selected = 2131558447; - + // aapt resource value: 0x7F0D0030 public const int mr_controller_pause = 2131558448; - + // aapt resource value: 0x7F0D0031 public const int mr_controller_play = 2131558449; - + // aapt resource value: 0x7F0D0032 public const int mr_controller_stop = 2131558450; - + // aapt resource value: 0x7F0D0033 public const int mr_controller_stop_casting = 2131558451; - + // aapt resource value: 0x7F0D0034 public const int mr_controller_volume_slider = 2131558452; - + // aapt resource value: 0x7F0D0035 public const int mr_system_route_name = 2131558453; - + // aapt resource value: 0x7F0D0036 public const int mr_user_route_category_name = 2131558454; - + // aapt resource value: 0x7F0D0037 public const int password_toggle_content_description = 2131558455; - + // aapt resource value: 0x7F0D0038 public const int path_password_eye = 2131558456; - + // aapt resource value: 0x7F0D0039 public const int path_password_eye_mask_strike_through = 2131558457; - + // aapt resource value: 0x7F0D003A public const int path_password_eye_mask_visible = 2131558458; - + // aapt resource value: 0x7F0D003B public const int path_password_strike_through = 2131558459; - + // aapt resource value: 0x7F0D003C public const int search_menu_title = 2131558460; - + // aapt resource value: 0x7F0D003D public const int status_bar_notification_info_overflow = 2131558461; - + static String() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private String() { } } - + public partial class Style { - + // aapt resource value: 0x7F0E0000 public const int AlertDialog_AppCompat = 2131623936; - + // aapt resource value: 0x7F0E0001 public const int AlertDialog_AppCompat_Light = 2131623937; - + // aapt resource value: 0x7F0E0002 public const int Animation_AppCompat_Dialog = 2131623938; - + // aapt resource value: 0x7F0E0003 public const int Animation_AppCompat_DropDownUp = 2131623939; - + // aapt resource value: 0x7F0E0004 public const int Animation_AppCompat_Tooltip = 2131623940; - + // aapt resource value: 0x7F0E0005 public const int Animation_Design_BottomSheetDialog = 2131623941; - + // aapt resource value: 0x7F0E0006 public const int Base_AlertDialog_AppCompat = 2131623942; - + // aapt resource value: 0x7F0E0007 public const int Base_AlertDialog_AppCompat_Light = 2131623943; - + // aapt resource value: 0x7F0E0008 public const int Base_Animation_AppCompat_Dialog = 2131623944; - + // aapt resource value: 0x7F0E0009 public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - + // aapt resource value: 0x7F0E000A public const int Base_Animation_AppCompat_Tooltip = 2131623946; - + // aapt resource value: 0x7F0E000B public const int Base_CardView = 2131623947; - + // aapt resource value: 0x7F0E000D public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - + // aapt resource value: 0x7F0E000C public const int Base_DialogWindowTitle_AppCompat = 2131623948; - + // aapt resource value: 0x7F0E000E public const int Base_TextAppearance_AppCompat = 2131623950; - + // aapt resource value: 0x7F0E000F public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - + // aapt resource value: 0x7F0E0010 public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - + // aapt resource value: 0x7F0E0011 public const int Base_TextAppearance_AppCompat_Button = 2131623953; - + // aapt resource value: 0x7F0E0012 public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - + // aapt resource value: 0x7F0E0013 public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - + // aapt resource value: 0x7F0E0014 public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - + // aapt resource value: 0x7F0E0015 public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - + // aapt resource value: 0x7F0E0016 public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - + // aapt resource value: 0x7F0E0017 public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - + // aapt resource value: 0x7F0E0018 public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - + // aapt resource value: 0x7F0E0019 public const int Base_TextAppearance_AppCompat_Large = 2131623961; - + // aapt resource value: 0x7F0E001A public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - + // aapt resource value: 0x7F0E001B public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - + // aapt resource value: 0x7F0E001C public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - + // aapt resource value: 0x7F0E001D public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - + // aapt resource value: 0x7F0E001E public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - + // aapt resource value: 0x7F0E001F public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - + // aapt resource value: 0x7F0E0020 public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - + // aapt resource value: 0x7F0E0021 public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - + // aapt resource value: 0x7F0E0022 public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - + // aapt resource value: 0x7F0E0023 public const int Base_TextAppearance_AppCompat_Small = 2131623971; - + // aapt resource value: 0x7F0E0024 public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - + // aapt resource value: 0x7F0E0025 public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - + // aapt resource value: 0x7F0E0026 public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - + // aapt resource value: 0x7F0E0027 public const int Base_TextAppearance_AppCompat_Title = 2131623975; - + // aapt resource value: 0x7F0E0028 public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - + // aapt resource value: 0x7F0E0029 public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - + // aapt resource value: 0x7F0E002A public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - + // aapt resource value: 0x7F0E002B public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - + // aapt resource value: 0x7F0E002C public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - + // aapt resource value: 0x7F0E002D public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - + // aapt resource value: 0x7F0E002E public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - + // aapt resource value: 0x7F0E002F public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - + // aapt resource value: 0x7F0E0030 public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - + // aapt resource value: 0x7F0E0031 public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - + // aapt resource value: 0x7F0E0032 public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - + // aapt resource value: 0x7F0E0033 public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - + // aapt resource value: 0x7F0E0034 public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - + // aapt resource value: 0x7F0E0035 public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - + // aapt resource value: 0x7F0E0036 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - + // aapt resource value: 0x7F0E0037 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - + // aapt resource value: 0x7F0E0038 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - + // aapt resource value: 0x7F0E0039 public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - + // aapt resource value: 0x7F0E003A public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - + // aapt resource value: 0x7F0E003B public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - + // aapt resource value: 0x7F0E003C public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - + // aapt resource value: 0x7F0E003D public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - + // aapt resource value: 0x7F0E004C public const int Base_ThemeOverlay_AppCompat = 2131624012; - + // aapt resource value: 0x7F0E004D public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - + // aapt resource value: 0x7F0E004E public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - + // aapt resource value: 0x7F0E004F public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - + // aapt resource value: 0x7F0E0050 public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - + // aapt resource value: 0x7F0E0051 public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - + // aapt resource value: 0x7F0E0052 public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - + // aapt resource value: 0x7F0E003E public const int Base_Theme_AppCompat = 2131623998; - + // aapt resource value: 0x7F0E003F public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - + // aapt resource value: 0x7F0E0040 public const int Base_Theme_AppCompat_Dialog = 2131624000; - + // aapt resource value: 0x7F0E0044 public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - + // aapt resource value: 0x7F0E0041 public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - + // aapt resource value: 0x7F0E0042 public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - + // aapt resource value: 0x7F0E0043 public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - + // aapt resource value: 0x7F0E0045 public const int Base_Theme_AppCompat_Light = 2131624005; - + // aapt resource value: 0x7F0E0046 public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - + // aapt resource value: 0x7F0E0047 public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - + // aapt resource value: 0x7F0E004B public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - + // aapt resource value: 0x7F0E0048 public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - + // aapt resource value: 0x7F0E0049 public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - + // aapt resource value: 0x7F0E004A public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - + // aapt resource value: 0x7F0E0055 public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - + // aapt resource value: 0x7F0E0053 public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - + // aapt resource value: 0x7F0E0054 public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - + // aapt resource value: 0x7F0E0056 public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - + // aapt resource value: 0x7F0E0057 public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - + // aapt resource value: 0x7F0E0058 public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - + // aapt resource value: 0x7F0E005D public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - + // aapt resource value: 0x7F0E0059 public const int Base_V21_Theme_AppCompat = 2131624025; - + // aapt resource value: 0x7F0E005A public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - + // aapt resource value: 0x7F0E005B public const int Base_V21_Theme_AppCompat_Light = 2131624027; - + // aapt resource value: 0x7F0E005C public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - + // aapt resource value: 0x7F0E005E public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - + // aapt resource value: 0x7F0E005F public const int Base_V22_Theme_AppCompat = 2131624031; - + // aapt resource value: 0x7F0E0060 public const int Base_V22_Theme_AppCompat_Light = 2131624032; - + // aapt resource value: 0x7F0E0061 public const int Base_V23_Theme_AppCompat = 2131624033; - + // aapt resource value: 0x7F0E0062 public const int Base_V23_Theme_AppCompat_Light = 2131624034; - + // aapt resource value: 0x7F0E0063 public const int Base_V26_Theme_AppCompat = 2131624035; - + // aapt resource value: 0x7F0E0064 public const int Base_V26_Theme_AppCompat_Light = 2131624036; - + // aapt resource value: 0x7F0E0065 public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - + // aapt resource value: 0x7F0E0066 public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - + // aapt resource value: 0x7F0E006B public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - + // aapt resource value: 0x7F0E0067 public const int Base_V7_Theme_AppCompat = 2131624039; - + // aapt resource value: 0x7F0E0068 public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - + // aapt resource value: 0x7F0E0069 public const int Base_V7_Theme_AppCompat_Light = 2131624041; - + // aapt resource value: 0x7F0E006A public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - + // aapt resource value: 0x7F0E006C public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - + // aapt resource value: 0x7F0E006D public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - + // aapt resource value: 0x7F0E006E public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - + // aapt resource value: 0x7F0E006F public const int Base_Widget_AppCompat_ActionBar = 2131624047; - + // aapt resource value: 0x7F0E0070 public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - + // aapt resource value: 0x7F0E0071 public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - + // aapt resource value: 0x7F0E0072 public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - + // aapt resource value: 0x7F0E0073 public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - + // aapt resource value: 0x7F0E0074 public const int Base_Widget_AppCompat_ActionButton = 2131624052; - + // aapt resource value: 0x7F0E0075 public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - + // aapt resource value: 0x7F0E0076 public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - + // aapt resource value: 0x7F0E0077 public const int Base_Widget_AppCompat_ActionMode = 2131624055; - + // aapt resource value: 0x7F0E0078 public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - + // aapt resource value: 0x7F0E0079 public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - + // aapt resource value: 0x7F0E007A public const int Base_Widget_AppCompat_Button = 2131624058; - + // aapt resource value: 0x7F0E0080 public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - + // aapt resource value: 0x7F0E0081 public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - + // aapt resource value: 0x7F0E007B public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - + // aapt resource value: 0x7F0E007C public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - + // aapt resource value: 0x7F0E007D public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - + // aapt resource value: 0x7F0E007E public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - + // aapt resource value: 0x7F0E007F public const int Base_Widget_AppCompat_Button_Small = 2131624063; - + // aapt resource value: 0x7F0E0082 public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - + // aapt resource value: 0x7F0E0083 public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - + // aapt resource value: 0x7F0E0084 public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - + // aapt resource value: 0x7F0E0085 public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - + // aapt resource value: 0x7F0E0086 public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - + // aapt resource value: 0x7F0E0087 public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - + // aapt resource value: 0x7F0E0088 public const int Base_Widget_AppCompat_EditText = 2131624072; - + // aapt resource value: 0x7F0E0089 public const int Base_Widget_AppCompat_ImageButton = 2131624073; - + // aapt resource value: 0x7F0E008A public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - + // aapt resource value: 0x7F0E008B public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - + // aapt resource value: 0x7F0E008C public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - + // aapt resource value: 0x7F0E008D public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - + // aapt resource value: 0x7F0E008E public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - + // aapt resource value: 0x7F0E008F public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - + // aapt resource value: 0x7F0E0090 public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - + // aapt resource value: 0x7F0E0091 public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - + // aapt resource value: 0x7F0E0092 public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - + // aapt resource value: 0x7F0E0093 public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - + // aapt resource value: 0x7F0E0094 public const int Base_Widget_AppCompat_ListView = 2131624084; - + // aapt resource value: 0x7F0E0095 public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - + // aapt resource value: 0x7F0E0096 public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - + // aapt resource value: 0x7F0E0097 public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - + // aapt resource value: 0x7F0E0098 public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - + // aapt resource value: 0x7F0E0099 public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - + // aapt resource value: 0x7F0E009A public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - + // aapt resource value: 0x7F0E009B public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - + // aapt resource value: 0x7F0E009C public const int Base_Widget_AppCompat_RatingBar = 2131624092; - + // aapt resource value: 0x7F0E009D public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - + // aapt resource value: 0x7F0E009E public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - + // aapt resource value: 0x7F0E009F public const int Base_Widget_AppCompat_SearchView = 2131624095; - + // aapt resource value: 0x7F0E00A0 public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - + // aapt resource value: 0x7F0E00A1 public const int Base_Widget_AppCompat_SeekBar = 2131624097; - + // aapt resource value: 0x7F0E00A2 public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - + // aapt resource value: 0x7F0E00A3 public const int Base_Widget_AppCompat_Spinner = 2131624099; - + // aapt resource value: 0x7F0E00A4 public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - + // aapt resource value: 0x7F0E00A5 public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - + // aapt resource value: 0x7F0E00A6 public const int Base_Widget_AppCompat_Toolbar = 2131624102; - + // aapt resource value: 0x7F0E00A7 public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - + // aapt resource value: 0x7F0E00A8 public const int Base_Widget_Design_AppBarLayout = 2131624104; - + // aapt resource value: 0x7F0E00A9 public const int Base_Widget_Design_TabLayout = 2131624105; - + // aapt resource value: 0x7F0E00AA public const int CardView = 2131624106; - + // aapt resource value: 0x7F0E00AB public const int CardView_Dark = 2131624107; - + // aapt resource value: 0x7F0E00AC public const int CardView_Light = 2131624108; - + // aapt resource value: 0x7F0E00AD public const int Platform_AppCompat = 2131624109; - + // aapt resource value: 0x7F0E00AE public const int Platform_AppCompat_Light = 2131624110; - + // aapt resource value: 0x7F0E00AF public const int Platform_ThemeOverlay_AppCompat = 2131624111; - + // aapt resource value: 0x7F0E00B0 public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - + // aapt resource value: 0x7F0E00B1 public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - + // aapt resource value: 0x7F0E00B2 public const int Platform_V11_AppCompat = 2131624114; - + // aapt resource value: 0x7F0E00B3 public const int Platform_V11_AppCompat_Light = 2131624115; - + // aapt resource value: 0x7F0E00B4 public const int Platform_V14_AppCompat = 2131624116; - + // aapt resource value: 0x7F0E00B5 public const int Platform_V14_AppCompat_Light = 2131624117; - + // aapt resource value: 0x7F0E00B6 public const int Platform_V21_AppCompat = 2131624118; - + // aapt resource value: 0x7F0E00B7 public const int Platform_V21_AppCompat_Light = 2131624119; - + // aapt resource value: 0x7F0E00B8 public const int Platform_V25_AppCompat = 2131624120; - + // aapt resource value: 0x7F0E00B9 public const int Platform_V25_AppCompat_Light = 2131624121; - + // aapt resource value: 0x7F0E00BA public const int Platform_Widget_AppCompat_Spinner = 2131624122; - + // aapt resource value: 0x7F0E00BB public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - + // aapt resource value: 0x7F0E00BC public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - + // aapt resource value: 0x7F0E00BD public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - + // aapt resource value: 0x7F0E00BE public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - + // aapt resource value: 0x7F0E00BF public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - + // aapt resource value: 0x7F0E00C0 public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - + // aapt resource value: 0x7F0E00C6 public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - + // aapt resource value: 0x7F0E00C1 public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - + // aapt resource value: 0x7F0E00C2 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - + // aapt resource value: 0x7F0E00C3 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - + // aapt resource value: 0x7F0E00C4 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - + // aapt resource value: 0x7F0E00C5 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - + // aapt resource value: 0x7F0E00C7 public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - + // aapt resource value: 0x7F0E00C8 public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - + // aapt resource value: 0x7F0E00C9 public const int TextAppearance_AppCompat = 2131624137; - + // aapt resource value: 0x7F0E00CA public const int TextAppearance_AppCompat_Body1 = 2131624138; - + // aapt resource value: 0x7F0E00CB public const int TextAppearance_AppCompat_Body2 = 2131624139; - + // aapt resource value: 0x7F0E00CC public const int TextAppearance_AppCompat_Button = 2131624140; - + // aapt resource value: 0x7F0E00CD public const int TextAppearance_AppCompat_Caption = 2131624141; - + // aapt resource value: 0x7F0E00CE public const int TextAppearance_AppCompat_Display1 = 2131624142; - + // aapt resource value: 0x7F0E00CF public const int TextAppearance_AppCompat_Display2 = 2131624143; - + // aapt resource value: 0x7F0E00D0 public const int TextAppearance_AppCompat_Display3 = 2131624144; - + // aapt resource value: 0x7F0E00D1 public const int TextAppearance_AppCompat_Display4 = 2131624145; - + // aapt resource value: 0x7F0E00D2 public const int TextAppearance_AppCompat_Headline = 2131624146; - + // aapt resource value: 0x7F0E00D3 public const int TextAppearance_AppCompat_Inverse = 2131624147; - + // aapt resource value: 0x7F0E00D4 public const int TextAppearance_AppCompat_Large = 2131624148; - + // aapt resource value: 0x7F0E00D5 public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - + // aapt resource value: 0x7F0E00D6 public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - + // aapt resource value: 0x7F0E00D7 public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - + // aapt resource value: 0x7F0E00D8 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - + // aapt resource value: 0x7F0E00D9 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - + // aapt resource value: 0x7F0E00DA public const int TextAppearance_AppCompat_Medium = 2131624154; - + // aapt resource value: 0x7F0E00DB public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - + // aapt resource value: 0x7F0E00DC public const int TextAppearance_AppCompat_Menu = 2131624156; - + // aapt resource value: 0x7F0E00DD public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - + // aapt resource value: 0x7F0E00DE public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - + // aapt resource value: 0x7F0E00DF public const int TextAppearance_AppCompat_Small = 2131624159; - + // aapt resource value: 0x7F0E00E0 public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - + // aapt resource value: 0x7F0E00E1 public const int TextAppearance_AppCompat_Subhead = 2131624161; - + // aapt resource value: 0x7F0E00E2 public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - + // aapt resource value: 0x7F0E00E3 public const int TextAppearance_AppCompat_Title = 2131624163; - + // aapt resource value: 0x7F0E00E4 public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - + // aapt resource value: 0x7F0E00E5 public const int TextAppearance_AppCompat_Tooltip = 2131624165; - + // aapt resource value: 0x7F0E00E6 public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - + // aapt resource value: 0x7F0E00E7 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - + // aapt resource value: 0x7F0E00E8 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - + // aapt resource value: 0x7F0E00E9 public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - + // aapt resource value: 0x7F0E00EA public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - + // aapt resource value: 0x7F0E00EB public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - + // aapt resource value: 0x7F0E00EC public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - + // aapt resource value: 0x7F0E00ED public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - + // aapt resource value: 0x7F0E00EE public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - + // aapt resource value: 0x7F0E00EF public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - + // aapt resource value: 0x7F0E00F0 public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - + // aapt resource value: 0x7F0E00F1 public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - + // aapt resource value: 0x7F0E00F2 public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - + // aapt resource value: 0x7F0E00F3 public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - + // aapt resource value: 0x7F0E00F4 public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - + // aapt resource value: 0x7F0E00F5 public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - + // aapt resource value: 0x7F0E00F6 public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - + // aapt resource value: 0x7F0E00F7 public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - + // aapt resource value: 0x7F0E00F8 public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - + // aapt resource value: 0x7F0E00F9 public const int TextAppearance_Compat_Notification = 2131624185; - + // aapt resource value: 0x7F0E00FA public const int TextAppearance_Compat_Notification_Info = 2131624186; - + // aapt resource value: 0x7F0E00FB public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - + // aapt resource value: 0x7F0E00FC public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - + // aapt resource value: 0x7F0E00FD public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - + // aapt resource value: 0x7F0E00FE public const int TextAppearance_Compat_Notification_Media = 2131624190; - + // aapt resource value: 0x7F0E00FF public const int TextAppearance_Compat_Notification_Time = 2131624191; - + // aapt resource value: 0x7F0E0100 public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - + // aapt resource value: 0x7F0E0101 public const int TextAppearance_Compat_Notification_Title = 2131624193; - + // aapt resource value: 0x7F0E0102 public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - + // aapt resource value: 0x7F0E0103 public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - + // aapt resource value: 0x7F0E0104 public const int TextAppearance_Design_Counter = 2131624196; - + // aapt resource value: 0x7F0E0105 public const int TextAppearance_Design_Counter_Overflow = 2131624197; - + // aapt resource value: 0x7F0E0106 public const int TextAppearance_Design_Error = 2131624198; - + // aapt resource value: 0x7F0E0107 public const int TextAppearance_Design_Hint = 2131624199; - + // aapt resource value: 0x7F0E0108 public const int TextAppearance_Design_Snackbar_Message = 2131624200; - + // aapt resource value: 0x7F0E0109 public const int TextAppearance_Design_Tab = 2131624201; - + // aapt resource value: 0x7F0E010A public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - + // aapt resource value: 0x7F0E010B public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - + // aapt resource value: 0x7F0E010C public const int TextAppearance_MediaRouter_Title = 2131624204; - + // aapt resource value: 0x7F0E010D public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - + // aapt resource value: 0x7F0E010E public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - + // aapt resource value: 0x7F0E010F public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - + // aapt resource value: 0x7F0E012F public const int ThemeOverlay_AppCompat = 2131624239; - + // aapt resource value: 0x7F0E0130 public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - + // aapt resource value: 0x7F0E0131 public const int ThemeOverlay_AppCompat_Dark = 2131624241; - + // aapt resource value: 0x7F0E0132 public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - + // aapt resource value: 0x7F0E0133 public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - + // aapt resource value: 0x7F0E0134 public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - + // aapt resource value: 0x7F0E0135 public const int ThemeOverlay_AppCompat_Light = 2131624245; - + // aapt resource value: 0x7F0E0136 public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - + // aapt resource value: 0x7F0E0137 public const int ThemeOverlay_MediaRouter_Light = 2131624247; - + // aapt resource value: 0x7F0E0110 public const int Theme_AppCompat = 2131624208; - + // aapt resource value: 0x7F0E0111 public const int Theme_AppCompat_CompactMenu = 2131624209; - + // aapt resource value: 0x7F0E0112 public const int Theme_AppCompat_DayNight = 2131624210; - + // aapt resource value: 0x7F0E0113 public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - + // aapt resource value: 0x7F0E0114 public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - + // aapt resource value: 0x7F0E0117 public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - + // aapt resource value: 0x7F0E0115 public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - + // aapt resource value: 0x7F0E0116 public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - + // aapt resource value: 0x7F0E0118 public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - + // aapt resource value: 0x7F0E0119 public const int Theme_AppCompat_Dialog = 2131624217; - + // aapt resource value: 0x7F0E011C public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - + // aapt resource value: 0x7F0E011A public const int Theme_AppCompat_Dialog_Alert = 2131624218; - + // aapt resource value: 0x7F0E011B public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - + // aapt resource value: 0x7F0E011D public const int Theme_AppCompat_Light = 2131624221; - + // aapt resource value: 0x7F0E011E public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - + // aapt resource value: 0x7F0E011F public const int Theme_AppCompat_Light_Dialog = 2131624223; - + // aapt resource value: 0x7F0E0122 public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - + // aapt resource value: 0x7F0E0120 public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - + // aapt resource value: 0x7F0E0121 public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - + // aapt resource value: 0x7F0E0123 public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - + // aapt resource value: 0x7F0E0124 public const int Theme_AppCompat_NoActionBar = 2131624228; - + // aapt resource value: 0x7F0E0125 public const int Theme_Design = 2131624229; - + // aapt resource value: 0x7F0E0126 public const int Theme_Design_BottomSheetDialog = 2131624230; - + // aapt resource value: 0x7F0E0127 public const int Theme_Design_Light = 2131624231; - + // aapt resource value: 0x7F0E0128 public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - + // aapt resource value: 0x7F0E0129 public const int Theme_Design_Light_NoActionBar = 2131624233; - + // aapt resource value: 0x7F0E012A public const int Theme_Design_NoActionBar = 2131624234; - + // aapt resource value: 0x7F0E012B public const int Theme_MediaRouter = 2131624235; - + // aapt resource value: 0x7F0E012C public const int Theme_MediaRouter_Light = 2131624236; - + // aapt resource value: 0x7F0E012E public const int Theme_MediaRouter_LightControlPanel = 2131624238; - + // aapt resource value: 0x7F0E012D public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - + // aapt resource value: 0x7F0E0138 public const int Widget_AppCompat_ActionBar = 2131624248; - + // aapt resource value: 0x7F0E0139 public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - + // aapt resource value: 0x7F0E013A public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - + // aapt resource value: 0x7F0E013B public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - + // aapt resource value: 0x7F0E013C public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - + // aapt resource value: 0x7F0E013D public const int Widget_AppCompat_ActionButton = 2131624253; - + // aapt resource value: 0x7F0E013E public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - + // aapt resource value: 0x7F0E013F public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - + // aapt resource value: 0x7F0E0140 public const int Widget_AppCompat_ActionMode = 2131624256; - + // aapt resource value: 0x7F0E0141 public const int Widget_AppCompat_ActivityChooserView = 2131624257; - + // aapt resource value: 0x7F0E0142 public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - + // aapt resource value: 0x7F0E0143 public const int Widget_AppCompat_Button = 2131624259; - + // aapt resource value: 0x7F0E0149 public const int Widget_AppCompat_ButtonBar = 2131624265; - + // aapt resource value: 0x7F0E014A public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - + // aapt resource value: 0x7F0E0144 public const int Widget_AppCompat_Button_Borderless = 2131624260; - + // aapt resource value: 0x7F0E0145 public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - + // aapt resource value: 0x7F0E0146 public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - + // aapt resource value: 0x7F0E0147 public const int Widget_AppCompat_Button_Colored = 2131624263; - + // aapt resource value: 0x7F0E0148 public const int Widget_AppCompat_Button_Small = 2131624264; - + // aapt resource value: 0x7F0E014B public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - + // aapt resource value: 0x7F0E014C public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - + // aapt resource value: 0x7F0E014D public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - + // aapt resource value: 0x7F0E014E public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - + // aapt resource value: 0x7F0E014F public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - + // aapt resource value: 0x7F0E0150 public const int Widget_AppCompat_EditText = 2131624272; - + // aapt resource value: 0x7F0E0151 public const int Widget_AppCompat_ImageButton = 2131624273; - + // aapt resource value: 0x7F0E0152 public const int Widget_AppCompat_Light_ActionBar = 2131624274; - + // aapt resource value: 0x7F0E0153 public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - + // aapt resource value: 0x7F0E0154 public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - + // aapt resource value: 0x7F0E0155 public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - + // aapt resource value: 0x7F0E0156 public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - + // aapt resource value: 0x7F0E0157 public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - + // aapt resource value: 0x7F0E0158 public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - + // aapt resource value: 0x7F0E0159 public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - + // aapt resource value: 0x7F0E015A public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - + // aapt resource value: 0x7F0E015B public const int Widget_AppCompat_Light_ActionButton = 2131624283; - + // aapt resource value: 0x7F0E015C public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - + // aapt resource value: 0x7F0E015D public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - + // aapt resource value: 0x7F0E015E public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - + // aapt resource value: 0x7F0E015F public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - + // aapt resource value: 0x7F0E0160 public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - + // aapt resource value: 0x7F0E0161 public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - + // aapt resource value: 0x7F0E0162 public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - + // aapt resource value: 0x7F0E0163 public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - + // aapt resource value: 0x7F0E0164 public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - + // aapt resource value: 0x7F0E0165 public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - + // aapt resource value: 0x7F0E0166 public const int Widget_AppCompat_Light_SearchView = 2131624294; - + // aapt resource value: 0x7F0E0167 public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - + // aapt resource value: 0x7F0E0168 public const int Widget_AppCompat_ListMenuView = 2131624296; - + // aapt resource value: 0x7F0E0169 public const int Widget_AppCompat_ListPopupWindow = 2131624297; - + // aapt resource value: 0x7F0E016A public const int Widget_AppCompat_ListView = 2131624298; - + // aapt resource value: 0x7F0E016B public const int Widget_AppCompat_ListView_DropDown = 2131624299; - + // aapt resource value: 0x7F0E016C public const int Widget_AppCompat_ListView_Menu = 2131624300; - + // aapt resource value: 0x7F0E016D public const int Widget_AppCompat_PopupMenu = 2131624301; - + // aapt resource value: 0x7F0E016E public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - + // aapt resource value: 0x7F0E016F public const int Widget_AppCompat_PopupWindow = 2131624303; - + // aapt resource value: 0x7F0E0170 public const int Widget_AppCompat_ProgressBar = 2131624304; - + // aapt resource value: 0x7F0E0171 public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - + // aapt resource value: 0x7F0E0172 public const int Widget_AppCompat_RatingBar = 2131624306; - + // aapt resource value: 0x7F0E0173 public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - + // aapt resource value: 0x7F0E0174 public const int Widget_AppCompat_RatingBar_Small = 2131624308; - + // aapt resource value: 0x7F0E0175 public const int Widget_AppCompat_SearchView = 2131624309; - + // aapt resource value: 0x7F0E0176 public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - + // aapt resource value: 0x7F0E0177 public const int Widget_AppCompat_SeekBar = 2131624311; - + // aapt resource value: 0x7F0E0178 public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - + // aapt resource value: 0x7F0E0179 public const int Widget_AppCompat_Spinner = 2131624313; - + // aapt resource value: 0x7F0E017A public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - + // aapt resource value: 0x7F0E017B public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - + // aapt resource value: 0x7F0E017C public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - + // aapt resource value: 0x7F0E017D public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - + // aapt resource value: 0x7F0E017E public const int Widget_AppCompat_Toolbar = 2131624318; - + // aapt resource value: 0x7F0E017F public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - + // aapt resource value: 0x7F0E0180 public const int Widget_Compat_NotificationActionContainer = 2131624320; - + // aapt resource value: 0x7F0E0181 public const int Widget_Compat_NotificationActionText = 2131624321; - + // aapt resource value: 0x7F0E0182 public const int Widget_Design_AppBarLayout = 2131624322; - + // aapt resource value: 0x7F0E0183 public const int Widget_Design_BottomNavigationView = 2131624323; - + // aapt resource value: 0x7F0E0184 public const int Widget_Design_BottomSheet_Modal = 2131624324; - + // aapt resource value: 0x7F0E0185 public const int Widget_Design_CollapsingToolbar = 2131624325; - + // aapt resource value: 0x7F0E0186 public const int Widget_Design_CoordinatorLayout = 2131624326; - + // aapt resource value: 0x7F0E0187 public const int Widget_Design_FloatingActionButton = 2131624327; - + // aapt resource value: 0x7F0E0188 public const int Widget_Design_NavigationView = 2131624328; - + // aapt resource value: 0x7F0E0189 public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - + // aapt resource value: 0x7F0E018A public const int Widget_Design_Snackbar = 2131624330; - + // aapt resource value: 0x7F0E018B public const int Widget_Design_TabLayout = 2131624331; - + // aapt resource value: 0x7F0E018C public const int Widget_Design_TextInputLayout = 2131624332; - + // aapt resource value: 0x7F0E018D public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - + // aapt resource value: 0x7F0E018E public const int Widget_MediaRouter_MediaRouteButton = 2131624334; - + static Style() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Style() { } } - + public partial class Styleable { - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } public static int[] ActionBar = new int[] { 2130903089, @@ -7540,112 +7417,112 @@ public partial class Styleable 2130903335, 2130903379, 2130903389}; - + // aapt resource value: { 0x10100B3 } public static int[] ActionBarLayout = new int[] { 16842931}; - + // aapt resource value: 0 public const int ActionBarLayout_android_layout_gravity = 0; - + // aapt resource value: 0 public const int ActionBar_background = 0; - + // aapt resource value: 1 public const int ActionBar_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionBar_backgroundStacked = 2; - + // aapt resource value: 3 public const int ActionBar_contentInsetEnd = 3; - + // aapt resource value: 4 public const int ActionBar_contentInsetEndWithActions = 4; - + // aapt resource value: 5 public const int ActionBar_contentInsetLeft = 5; - + // aapt resource value: 6 public const int ActionBar_contentInsetRight = 6; - + // aapt resource value: 7 public const int ActionBar_contentInsetStart = 7; - + // aapt resource value: 8 public const int ActionBar_contentInsetStartWithNavigation = 8; - + // aapt resource value: 9 public const int ActionBar_customNavigationLayout = 9; - + // aapt resource value: 10 public const int ActionBar_displayOptions = 10; - + // aapt resource value: 11 public const int ActionBar_divider = 11; - + // aapt resource value: 12 public const int ActionBar_elevation = 12; - + // aapt resource value: 13 public const int ActionBar_height = 13; - + // aapt resource value: 14 public const int ActionBar_hideOnContentScroll = 14; - + // aapt resource value: 15 public const int ActionBar_homeAsUpIndicator = 15; - + // aapt resource value: 16 public const int ActionBar_homeLayout = 16; - + // aapt resource value: 17 public const int ActionBar_icon = 17; - + // aapt resource value: 18 public const int ActionBar_indeterminateProgressStyle = 18; - + // aapt resource value: 19 public const int ActionBar_itemPadding = 19; - + // aapt resource value: 20 public const int ActionBar_logo = 20; - + // aapt resource value: 21 public const int ActionBar_navigationMode = 21; - + // aapt resource value: 22 public const int ActionBar_popupTheme = 22; - + // aapt resource value: 23 public const int ActionBar_progressBarPadding = 23; - + // aapt resource value: 24 public const int ActionBar_progressBarStyle = 24; - + // aapt resource value: 25 public const int ActionBar_subtitle = 25; - + // aapt resource value: 26 public const int ActionBar_subtitleTextStyle = 26; - + // aapt resource value: 27 public const int ActionBar_title = 27; - + // aapt resource value: 28 public const int ActionBar_titleTextStyle = 28; - + // aapt resource value: { 0x101013F } public static int[] ActionMenuItemView = new int[] { 16843071}; - + // aapt resource value: 0 public const int ActionMenuItemView_android_minWidth = 0; - + // aapt resource value: { 0xFFFFFFFF } public static int[] ActionMenuView = new int[] { -1}; - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } public static int[] ActionMode = new int[] { 2130903089, @@ -7654,36 +7531,36 @@ public partial class Styleable 2130903208, 2130903335, 2130903389}; - + // aapt resource value: 0 public const int ActionMode_background = 0; - + // aapt resource value: 1 public const int ActionMode_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionMode_closeItemLayout = 2; - + // aapt resource value: 3 public const int ActionMode_height = 3; - + // aapt resource value: 4 public const int ActionMode_subtitleTextStyle = 4; - + // aapt resource value: 5 public const int ActionMode_titleTextStyle = 5; - + // aapt resource value: { 0x7F03008A,0x7F0300B5 } public static int[] ActivityChooserView = new int[] { 2130903178, 2130903221}; - + // aapt resource value: 0 public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; - + // aapt resource value: 1 public const int ActivityChooserView_initialActivityCount = 1; - + // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } public static int[] AlertDialog = new int[] { 16842994, @@ -7693,28 +7570,28 @@ public partial class Styleable 2130903272, 2130903316, 2130903317}; - + // aapt resource value: 0 public const int AlertDialog_android_layout = 0; - + // aapt resource value: 1 public const int AlertDialog_buttonPanelSideLayout = 1; - + // aapt resource value: 2 public const int AlertDialog_listItemLayout = 2; - + // aapt resource value: 3 public const int AlertDialog_listLayout = 3; - + // aapt resource value: 4 public const int AlertDialog_multiChoiceItemLayout = 4; - + // aapt resource value: 5 public const int AlertDialog_showTitle = 5; - + // aapt resource value: 6 public const int AlertDialog_singleChoiceItemLayout = 6; - + // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } public static int[] AppBarLayout = new int[] { 16842964, @@ -7722,82 +7599,82 @@ public partial class Styleable 16844096, 2130903175, 2130903179}; - + // aapt resource value: { 0x7F03011E,0x7F03011F } public static int[] AppBarLayoutStates = new int[] { 2130903326, 2130903327}; - + // aapt resource value: 0 public const int AppBarLayoutStates_state_collapsed = 0; - + // aapt resource value: 1 public const int AppBarLayoutStates_state_collapsible = 1; - + // aapt resource value: 0 public const int AppBarLayout_android_background = 0; - + // aapt resource value: 2 public const int AppBarLayout_android_keyboardNavigationCluster = 2; - + // aapt resource value: 1 public const int AppBarLayout_android_touchscreenBlocksFocus = 1; - + // aapt resource value: 3 public const int AppBarLayout_elevation = 3; - + // aapt resource value: 4 public const int AppBarLayout_expanded = 4; - + // aapt resource value: { 0x7F0300C8,0x7F0300C9 } public static int[] AppBarLayout_Layout = new int[] { 2130903240, 2130903241}; - + // aapt resource value: 0 public const int AppBarLayout_Layout_layout_scrollFlags = 0; - + // aapt resource value: 1 public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; - + // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } public static int[] AppCompatImageView = new int[] { 16843033, 2130903323, 2130903377, 2130903378}; - + // aapt resource value: 0 public const int AppCompatImageView_android_src = 0; - + // aapt resource value: 1 public const int AppCompatImageView_srcCompat = 1; - + // aapt resource value: 2 public const int AppCompatImageView_tint = 2; - + // aapt resource value: 3 public const int AppCompatImageView_tintMode = 3; - + // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } public static int[] AppCompatSeekBar = new int[] { 16843074, 2130903374, 2130903375, 2130903376}; - + // aapt resource value: 0 public const int AppCompatSeekBar_android_thumb = 0; - + // aapt resource value: 1 public const int AppCompatSeekBar_tickMark = 1; - + // aapt resource value: 2 public const int AppCompatSeekBar_tickMarkTint = 2; - + // aapt resource value: 3 public const int AppCompatSeekBar_tickMarkTintMode = 3; - + // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } public static int[] AppCompatTextHelper = new int[] { 16842804, @@ -7807,28 +7684,28 @@ public partial class Styleable 16843120, 16843666, 16843667}; - + // aapt resource value: 2 public const int AppCompatTextHelper_android_drawableBottom = 2; - + // aapt resource value: 6 public const int AppCompatTextHelper_android_drawableEnd = 6; - + // aapt resource value: 3 public const int AppCompatTextHelper_android_drawableLeft = 3; - + // aapt resource value: 4 public const int AppCompatTextHelper_android_drawableRight = 4; - + // aapt resource value: 5 public const int AppCompatTextHelper_android_drawableStart = 5; - + // aapt resource value: 1 public const int AppCompatTextHelper_android_drawableTop = 1; - + // aapt resource value: 0 public const int AppCompatTextHelper_android_textAppearance = 0; - + // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } public static int[] AppCompatTextView = new int[] { 16842804, @@ -7839,31 +7716,31 @@ public partial class Styleable 2130903088, 2130903195, 2130903357}; - + // aapt resource value: 0 public const int AppCompatTextView_android_textAppearance = 0; - + // aapt resource value: 1 public const int AppCompatTextView_autoSizeMaxTextSize = 1; - + // aapt resource value: 2 public const int AppCompatTextView_autoSizeMinTextSize = 2; - + // aapt resource value: 3 public const int AppCompatTextView_autoSizePresetSizes = 3; - + // aapt resource value: 4 public const int AppCompatTextView_autoSizeStepGranularity = 4; - + // aapt resource value: 5 public const int AppCompatTextView_autoSizeTextType = 5; - + // aapt resource value: 6 public const int AppCompatTextView_fontFamily = 6; - + // aapt resource value: 7 public const int AppCompatTextView_textAllCaps = 7; - + // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } public static int[] AppCompatTheme = new int[] { 16842839, @@ -7985,364 +7862,364 @@ public partial class Styleable 2130903408, 2130903409, 2130903410}; - + // aapt resource value: 2 public const int AppCompatTheme_actionBarDivider = 2; - + // aapt resource value: 3 public const int AppCompatTheme_actionBarItemBackground = 3; - + // aapt resource value: 4 public const int AppCompatTheme_actionBarPopupTheme = 4; - + // aapt resource value: 5 public const int AppCompatTheme_actionBarSize = 5; - + // aapt resource value: 6 public const int AppCompatTheme_actionBarSplitStyle = 6; - + // aapt resource value: 7 public const int AppCompatTheme_actionBarStyle = 7; - + // aapt resource value: 8 public const int AppCompatTheme_actionBarTabBarStyle = 8; - + // aapt resource value: 9 public const int AppCompatTheme_actionBarTabStyle = 9; - + // aapt resource value: 10 public const int AppCompatTheme_actionBarTabTextStyle = 10; - + // aapt resource value: 11 public const int AppCompatTheme_actionBarTheme = 11; - + // aapt resource value: 12 public const int AppCompatTheme_actionBarWidgetTheme = 12; - + // aapt resource value: 13 public const int AppCompatTheme_actionButtonStyle = 13; - + // aapt resource value: 14 public const int AppCompatTheme_actionDropDownStyle = 14; - + // aapt resource value: 15 public const int AppCompatTheme_actionMenuTextAppearance = 15; - + // aapt resource value: 16 public const int AppCompatTheme_actionMenuTextColor = 16; - + // aapt resource value: 17 public const int AppCompatTheme_actionModeBackground = 17; - + // aapt resource value: 18 public const int AppCompatTheme_actionModeCloseButtonStyle = 18; - + // aapt resource value: 19 public const int AppCompatTheme_actionModeCloseDrawable = 19; - + // aapt resource value: 20 public const int AppCompatTheme_actionModeCopyDrawable = 20; - + // aapt resource value: 21 public const int AppCompatTheme_actionModeCutDrawable = 21; - + // aapt resource value: 22 public const int AppCompatTheme_actionModeFindDrawable = 22; - + // aapt resource value: 23 public const int AppCompatTheme_actionModePasteDrawable = 23; - + // aapt resource value: 24 public const int AppCompatTheme_actionModePopupWindowStyle = 24; - + // aapt resource value: 25 public const int AppCompatTheme_actionModeSelectAllDrawable = 25; - + // aapt resource value: 26 public const int AppCompatTheme_actionModeShareDrawable = 26; - + // aapt resource value: 27 public const int AppCompatTheme_actionModeSplitBackground = 27; - + // aapt resource value: 28 public const int AppCompatTheme_actionModeStyle = 28; - + // aapt resource value: 29 public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - + // aapt resource value: 30 public const int AppCompatTheme_actionOverflowButtonStyle = 30; - + // aapt resource value: 31 public const int AppCompatTheme_actionOverflowMenuStyle = 31; - + // aapt resource value: 32 public const int AppCompatTheme_activityChooserViewStyle = 32; - + // aapt resource value: 33 public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; - + // aapt resource value: 34 public const int AppCompatTheme_alertDialogCenterButtons = 34; - + // aapt resource value: 35 public const int AppCompatTheme_alertDialogStyle = 35; - + // aapt resource value: 36 public const int AppCompatTheme_alertDialogTheme = 36; - + // aapt resource value: 1 public const int AppCompatTheme_android_windowAnimationStyle = 1; - + // aapt resource value: 0 public const int AppCompatTheme_android_windowIsFloating = 0; - + // aapt resource value: 37 public const int AppCompatTheme_autoCompleteTextViewStyle = 37; - + // aapt resource value: 38 public const int AppCompatTheme_borderlessButtonStyle = 38; - + // aapt resource value: 39 public const int AppCompatTheme_buttonBarButtonStyle = 39; - + // aapt resource value: 40 public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - + // aapt resource value: 41 public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - + // aapt resource value: 42 public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - + // aapt resource value: 43 public const int AppCompatTheme_buttonBarStyle = 43; - + // aapt resource value: 44 public const int AppCompatTheme_buttonStyle = 44; - + // aapt resource value: 45 public const int AppCompatTheme_buttonStyleSmall = 45; - + // aapt resource value: 46 public const int AppCompatTheme_checkboxStyle = 46; - + // aapt resource value: 47 public const int AppCompatTheme_checkedTextViewStyle = 47; - + // aapt resource value: 48 public const int AppCompatTheme_colorAccent = 48; - + // aapt resource value: 49 public const int AppCompatTheme_colorBackgroundFloating = 49; - + // aapt resource value: 50 public const int AppCompatTheme_colorButtonNormal = 50; - + // aapt resource value: 51 public const int AppCompatTheme_colorControlActivated = 51; - + // aapt resource value: 52 public const int AppCompatTheme_colorControlHighlight = 52; - + // aapt resource value: 53 public const int AppCompatTheme_colorControlNormal = 53; - + // aapt resource value: 54 public const int AppCompatTheme_colorError = 54; - + // aapt resource value: 55 public const int AppCompatTheme_colorPrimary = 55; - + // aapt resource value: 56 public const int AppCompatTheme_colorPrimaryDark = 56; - + // aapt resource value: 57 public const int AppCompatTheme_colorSwitchThumbNormal = 57; - + // aapt resource value: 58 public const int AppCompatTheme_controlBackground = 58; - + // aapt resource value: 59 public const int AppCompatTheme_dialogPreferredPadding = 59; - + // aapt resource value: 60 public const int AppCompatTheme_dialogTheme = 60; - + // aapt resource value: 61 public const int AppCompatTheme_dividerHorizontal = 61; - + // aapt resource value: 62 public const int AppCompatTheme_dividerVertical = 62; - + // aapt resource value: 64 public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - + // aapt resource value: 63 public const int AppCompatTheme_dropDownListViewStyle = 63; - + // aapt resource value: 65 public const int AppCompatTheme_editTextBackground = 65; - + // aapt resource value: 66 public const int AppCompatTheme_editTextColor = 66; - + // aapt resource value: 67 public const int AppCompatTheme_editTextStyle = 67; - + // aapt resource value: 68 public const int AppCompatTheme_homeAsUpIndicator = 68; - + // aapt resource value: 69 public const int AppCompatTheme_imageButtonStyle = 69; - + // aapt resource value: 70 public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - + // aapt resource value: 71 public const int AppCompatTheme_listDividerAlertDialog = 71; - + // aapt resource value: 72 public const int AppCompatTheme_listMenuViewStyle = 72; - + // aapt resource value: 73 public const int AppCompatTheme_listPopupWindowStyle = 73; - + // aapt resource value: 74 public const int AppCompatTheme_listPreferredItemHeight = 74; - + // aapt resource value: 75 public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - + // aapt resource value: 76 public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - + // aapt resource value: 77 public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - + // aapt resource value: 78 public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - + // aapt resource value: 79 public const int AppCompatTheme_panelBackground = 79; - + // aapt resource value: 80 public const int AppCompatTheme_panelMenuListTheme = 80; - + // aapt resource value: 81 public const int AppCompatTheme_panelMenuListWidth = 81; - + // aapt resource value: 82 public const int AppCompatTheme_popupMenuStyle = 82; - + // aapt resource value: 83 public const int AppCompatTheme_popupWindowStyle = 83; - + // aapt resource value: 84 public const int AppCompatTheme_radioButtonStyle = 84; - + // aapt resource value: 85 public const int AppCompatTheme_ratingBarStyle = 85; - + // aapt resource value: 86 public const int AppCompatTheme_ratingBarStyleIndicator = 86; - + // aapt resource value: 87 public const int AppCompatTheme_ratingBarStyleSmall = 87; - + // aapt resource value: 88 public const int AppCompatTheme_searchViewStyle = 88; - + // aapt resource value: 89 public const int AppCompatTheme_seekBarStyle = 89; - + // aapt resource value: 90 public const int AppCompatTheme_selectableItemBackground = 90; - + // aapt resource value: 91 public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - + // aapt resource value: 92 public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - + // aapt resource value: 93 public const int AppCompatTheme_spinnerStyle = 93; - + // aapt resource value: 94 public const int AppCompatTheme_switchStyle = 94; - + // aapt resource value: 95 public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - + // aapt resource value: 96 public const int AppCompatTheme_textAppearanceListItem = 96; - + // aapt resource value: 97 public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - + // aapt resource value: 98 public const int AppCompatTheme_textAppearanceListItemSmall = 98; - + // aapt resource value: 99 public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - + // aapt resource value: 100 public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - + // aapt resource value: 101 public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - + // aapt resource value: 102 public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - + // aapt resource value: 103 public const int AppCompatTheme_textColorAlertDialogListItem = 103; - + // aapt resource value: 104 public const int AppCompatTheme_textColorSearchUrl = 104; - + // aapt resource value: 105 public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - + // aapt resource value: 106 public const int AppCompatTheme_toolbarStyle = 106; - + // aapt resource value: 107 public const int AppCompatTheme_tooltipForegroundColor = 107; - + // aapt resource value: 108 public const int AppCompatTheme_tooltipFrameBackground = 108; - + // aapt resource value: 109 public const int AppCompatTheme_windowActionBar = 109; - + // aapt resource value: 110 public const int AppCompatTheme_windowActionBarOverlay = 110; - + // aapt resource value: 111 public const int AppCompatTheme_windowActionModeOverlay = 111; - + // aapt resource value: 112 public const int AppCompatTheme_windowFixedHeightMajor = 112; - + // aapt resource value: 113 public const int AppCompatTheme_windowFixedHeightMinor = 113; - + // aapt resource value: 114 public const int AppCompatTheme_windowFixedWidthMajor = 114; - + // aapt resource value: 115 public const int AppCompatTheme_windowFixedWidthMinor = 115; - + // aapt resource value: 116 public const int AppCompatTheme_windowMinWidthMajor = 116; - + // aapt resource value: 117 public const int AppCompatTheme_windowMinWidthMinor = 117; - + // aapt resource value: 118 public const int AppCompatTheme_windowNoTitle = 118; - + // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } public static int[] BottomNavigationView = new int[] { 2130903175, @@ -8350,44 +8227,44 @@ public partial class Styleable 2130903225, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int BottomNavigationView_elevation = 0; - + // aapt resource value: 1 public const int BottomNavigationView_itemBackground = 1; - + // aapt resource value: 2 public const int BottomNavigationView_itemIconTint = 2; - + // aapt resource value: 3 public const int BottomNavigationView_itemTextColor = 3; - + // aapt resource value: 4 public const int BottomNavigationView_menu = 4; - + // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } public static int[] BottomSheetBehavior_Layout = new int[] { 2130903096, 2130903098, 2130903099}; - + // aapt resource value: 0 public const int BottomSheetBehavior_Layout_behavior_hideable = 0; - + // aapt resource value: 1 public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; - + // aapt resource value: 2 public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; - + // aapt resource value: { 0x7F030026 } public static int[] ButtonBarLayout = new int[] { 2130903078}; - + // aapt resource value: 0 public const int ButtonBarLayout_allowStacking = 0; - + // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } public static int[] CardView = new int[] { 16843071, @@ -8403,46 +8280,46 @@ public partial class Styleable 2130903150, 2130903151, 2130903152}; - + // aapt resource value: 1 public const int CardView_android_minHeight = 1; - + // aapt resource value: 0 public const int CardView_android_minWidth = 0; - + // aapt resource value: 2 public const int CardView_cardBackgroundColor = 2; - + // aapt resource value: 3 public const int CardView_cardCornerRadius = 3; - + // aapt resource value: 4 public const int CardView_cardElevation = 4; - + // aapt resource value: 5 public const int CardView_cardMaxElevation = 5; - + // aapt resource value: 6 public const int CardView_cardPreventCornerOverlap = 6; - + // aapt resource value: 7 public const int CardView_cardUseCompatPadding = 7; - + // aapt resource value: 8 public const int CardView_contentPadding = 8; - + // aapt resource value: 9 public const int CardView_contentPaddingBottom = 9; - + // aapt resource value: 10 public const int CardView_contentPaddingLeft = 10; - + // aapt resource value: 11 public const int CardView_contentPaddingRight = 11; - + // aapt resource value: 12 public const int CardView_contentPaddingTop = 12; - + // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } public static int[] CollapsingToolbarLayout = new int[] { 2130903127, @@ -8461,104 +8338,104 @@ public partial class Styleable 2130903379, 2130903380, 2130903390}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - + // aapt resource value: 2 public const int CollapsingToolbarLayout_contentScrim = 2; - + // aapt resource value: 3 public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - + // aapt resource value: 4 public const int CollapsingToolbarLayout_expandedTitleMargin = 4; - + // aapt resource value: 5 public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - + // aapt resource value: 6 public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - + // aapt resource value: 7 public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - + // aapt resource value: 8 public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; - + // aapt resource value: 9 public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; - + // aapt resource value: { 0x7F0300C3,0x7F0300C4 } public static int[] CollapsingToolbarLayout_Layout = new int[] { 2130903235, 2130903236}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; - + // aapt resource value: 10 public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; - + // aapt resource value: 11 public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; - + // aapt resource value: 12 public const int CollapsingToolbarLayout_statusBarScrim = 12; - + // aapt resource value: 13 public const int CollapsingToolbarLayout_title = 13; - + // aapt resource value: 14 public const int CollapsingToolbarLayout_titleEnabled = 14; - + // aapt resource value: 15 public const int CollapsingToolbarLayout_toolbarId = 15; - + // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } public static int[] ColorStateListItem = new int[] { 16843173, 16843551, 2130903079}; - + // aapt resource value: 2 public const int ColorStateListItem_alpha = 2; - + // aapt resource value: 1 public const int ColorStateListItem_android_alpha = 1; - + // aapt resource value: 0 public const int ColorStateListItem_android_color = 0; - + // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } public static int[] CompoundButton = new int[] { 16843015, 2130903113, 2130903114}; - + // aapt resource value: 0 public const int CompoundButton_android_button = 0; - + // aapt resource value: 1 public const int CompoundButton_buttonTint = 1; - + // aapt resource value: 2 public const int CompoundButton_buttonTintMode = 2; - + // aapt resource value: { 0x7F0300BD,0x7F030120 } public static int[] CoordinatorLayout = new int[] { 2130903229, 2130903328}; - + // aapt resource value: 0 public const int CoordinatorLayout_keylines = 0; - + // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } public static int[] CoordinatorLayout_Layout = new int[] { 16842931, @@ -8568,46 +8445,46 @@ public partial class Styleable 2130903237, 2130903238, 2130903239}; - + // aapt resource value: 0 public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - + // aapt resource value: 1 public const int CoordinatorLayout_Layout_layout_anchor = 1; - + // aapt resource value: 2 public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - + // aapt resource value: 3 public const int CoordinatorLayout_Layout_layout_behavior = 3; - + // aapt resource value: 4 public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; - + // aapt resource value: 5 public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - + // aapt resource value: 6 public const int CoordinatorLayout_Layout_layout_keyline = 6; - + // aapt resource value: 1 public const int CoordinatorLayout_statusBarBackground = 1; - + // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } public static int[] DesignTheme = new int[] { 2130903102, 2130903103, 2130903367}; - + // aapt resource value: 0 public const int DesignTheme_bottomSheetDialogTheme = 0; - + // aapt resource value: 1 public const int DesignTheme_bottomSheetStyle = 1; - + // aapt resource value: 2 public const int DesignTheme_textColorError = 2; - + // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } public static int[] DrawerArrowToggle = new int[] { 2130903081, @@ -8618,31 +8495,31 @@ public partial class Styleable 2130903205, 2130903319, 2130903370}; - + // aapt resource value: 0 public const int DrawerArrowToggle_arrowHeadLength = 0; - + // aapt resource value: 1 public const int DrawerArrowToggle_arrowShaftLength = 1; - + // aapt resource value: 2 public const int DrawerArrowToggle_barLength = 2; - + // aapt resource value: 3 public const int DrawerArrowToggle_color = 3; - + // aapt resource value: 4 public const int DrawerArrowToggle_drawableSize = 4; - + // aapt resource value: 5 public const int DrawerArrowToggle_gapBetweenBars = 5; - + // aapt resource value: 6 public const int DrawerArrowToggle_spinBars = 6; - + // aapt resource value: 7 public const int DrawerArrowToggle_thickness = 7; - + // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } public static int[] FloatingActionButton = new int[] { 2130903092, @@ -8653,38 +8530,38 @@ public partial class Styleable 2130903294, 2130903304, 2130903399}; - + // aapt resource value: 0 public const int FloatingActionButton_backgroundTint = 0; - + // aapt resource value: 1 public const int FloatingActionButton_backgroundTintMode = 1; - + // aapt resource value: { 0x7F030037 } public static int[] FloatingActionButton_Behavior_Layout = new int[] { 2130903095}; - + // aapt resource value: 0 public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - + // aapt resource value: 2 public const int FloatingActionButton_borderWidth = 2; - + // aapt resource value: 3 public const int FloatingActionButton_elevation = 3; - + // aapt resource value: 4 public const int FloatingActionButton_fabSize = 4; - + // aapt resource value: 5 public const int FloatingActionButton_pressedTranslationZ = 5; - + // aapt resource value: 6 public const int FloatingActionButton_rippleColor = 6; - + // aapt resource value: 7 public const int FloatingActionButton_useCompatPadding = 7; - + // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } public static int[] FontFamily = new int[] { 2130903196, @@ -8693,7 +8570,7 @@ public partial class Styleable 2130903199, 2130903200, 2130903201}; - + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } public static int[] FontFamilyFont = new int[] { 16844082, @@ -8702,58 +8579,58 @@ public partial class Styleable 2130903194, 2130903202, 2130903203}; - + // aapt resource value: 0 public const int FontFamilyFont_android_font = 0; - + // aapt resource value: 2 public const int FontFamilyFont_android_fontStyle = 2; - + // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; - + // aapt resource value: 3 public const int FontFamilyFont_font = 3; - + // aapt resource value: 4 public const int FontFamilyFont_fontStyle = 4; - + // aapt resource value: 5 public const int FontFamilyFont_fontWeight = 5; - + // aapt resource value: 0 public const int FontFamily_fontProviderAuthority = 0; - + // aapt resource value: 1 public const int FontFamily_fontProviderCerts = 1; - + // aapt resource value: 2 public const int FontFamily_fontProviderFetchStrategy = 2; - + // aapt resource value: 3 public const int FontFamily_fontProviderFetchTimeout = 3; - + // aapt resource value: 4 public const int FontFamily_fontProviderPackage = 4; - + // aapt resource value: 5 public const int FontFamily_fontProviderQuery = 5; - + // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } public static int[] ForegroundLinearLayout = new int[] { 16843017, 16843264, 2130903204}; - + // aapt resource value: 0 public const int ForegroundLinearLayout_android_foreground = 0; - + // aapt resource value: 1 public const int ForegroundLinearLayout_android_foregroundGravity = 1; - + // aapt resource value: 2 public const int ForegroundLinearLayout_foregroundInsidePadding = 2; - + // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } public static int[] LinearLayoutCompat = new int[] { 16842927, @@ -8765,83 +8642,83 @@ public partial class Styleable 2130903166, 2130903257, 2130903314}; - + // aapt resource value: 2 public const int LinearLayoutCompat_android_baselineAligned = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; - + // aapt resource value: 0 public const int LinearLayoutCompat_android_gravity = 0; - + // aapt resource value: 1 public const int LinearLayoutCompat_android_orientation = 1; - + // aapt resource value: 4 public const int LinearLayoutCompat_android_weightSum = 4; - + // aapt resource value: 5 public const int LinearLayoutCompat_divider = 5; - + // aapt resource value: 6 public const int LinearLayoutCompat_dividerPadding = 6; - + // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } public static int[] LinearLayoutCompat_Layout = new int[] { 16842931, 16842996, 16842997, 16843137}; - + // aapt resource value: 0 public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; - + // aapt resource value: 2 public const int LinearLayoutCompat_Layout_android_layout_height = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_Layout_android_layout_weight = 3; - + // aapt resource value: 1 public const int LinearLayoutCompat_Layout_android_layout_width = 1; - + // aapt resource value: 7 public const int LinearLayoutCompat_measureWithLargestChild = 7; - + // aapt resource value: 8 public const int LinearLayoutCompat_showDividers = 8; - + // aapt resource value: { 0x10102AC,0x10102AD } public static int[] ListPopupWindow = new int[] { 16843436, 16843437}; - + // aapt resource value: 0 public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; - + // aapt resource value: 1 public const int ListPopupWindow_android_dropDownVerticalOffset = 1; - + // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } public static int[] MediaRouteButton = new int[] { 16843071, 16843072, 2130903187, 2130903260}; - + // aapt resource value: 1 public const int MediaRouteButton_android_minHeight = 1; - + // aapt resource value: 0 public const int MediaRouteButton_android_minWidth = 0; - + // aapt resource value: 2 public const int MediaRouteButton_externalRouteEnabledDrawable = 2; - + // aapt resource value: 3 public const int MediaRouteButton_mediaRouteButtonTint = 3; - + // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } public static int[] MenuGroup = new int[] { 16842766, @@ -8850,25 +8727,25 @@ public partial class Styleable 16843230, 16843231, 16843232}; - + // aapt resource value: 5 public const int MenuGroup_android_checkableBehavior = 5; - + // aapt resource value: 0 public const int MenuGroup_android_enabled = 0; - + // aapt resource value: 1 public const int MenuGroup_android_id = 1; - + // aapt resource value: 3 public const int MenuGroup_android_menuCategory = 3; - + // aapt resource value: 4 public const int MenuGroup_android_orderInCategory = 4; - + // aapt resource value: 2 public const int MenuGroup_android_visible = 2; - + // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } public static int[] MenuItem = new int[] { 16842754, @@ -8894,76 +8771,76 @@ public partial class Styleable 2130903276, 2130903313, 2130903395}; - + // aapt resource value: 13 public const int MenuItem_actionLayout = 13; - + // aapt resource value: 14 public const int MenuItem_actionProviderClass = 14; - + // aapt resource value: 15 public const int MenuItem_actionViewClass = 15; - + // aapt resource value: 16 public const int MenuItem_alphabeticModifiers = 16; - + // aapt resource value: 9 public const int MenuItem_android_alphabeticShortcut = 9; - + // aapt resource value: 11 public const int MenuItem_android_checkable = 11; - + // aapt resource value: 3 public const int MenuItem_android_checked = 3; - + // aapt resource value: 1 public const int MenuItem_android_enabled = 1; - + // aapt resource value: 0 public const int MenuItem_android_icon = 0; - + // aapt resource value: 2 public const int MenuItem_android_id = 2; - + // aapt resource value: 5 public const int MenuItem_android_menuCategory = 5; - + // aapt resource value: 10 public const int MenuItem_android_numericShortcut = 10; - + // aapt resource value: 12 public const int MenuItem_android_onClick = 12; - + // aapt resource value: 6 public const int MenuItem_android_orderInCategory = 6; - + // aapt resource value: 7 public const int MenuItem_android_title = 7; - + // aapt resource value: 8 public const int MenuItem_android_titleCondensed = 8; - + // aapt resource value: 4 public const int MenuItem_android_visible = 4; - + // aapt resource value: 17 public const int MenuItem_contentDescription = 17; - + // aapt resource value: 18 public const int MenuItem_iconTint = 18; - + // aapt resource value: 19 public const int MenuItem_iconTintMode = 19; - + // aapt resource value: 20 public const int MenuItem_numericModifiers = 20; - + // aapt resource value: 21 public const int MenuItem_showAsAction = 21; - + // aapt resource value: 22 public const int MenuItem_tooltipText = 22; - + // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } public static int[] MenuView = new int[] { 16842926, @@ -8975,34 +8852,34 @@ public partial class Styleable 16843057, 2130903293, 2130903330}; - + // aapt resource value: 4 public const int MenuView_android_headerBackground = 4; - + // aapt resource value: 2 public const int MenuView_android_horizontalDivider = 2; - + // aapt resource value: 5 public const int MenuView_android_itemBackground = 5; - + // aapt resource value: 6 public const int MenuView_android_itemIconDisabledAlpha = 6; - + // aapt resource value: 1 public const int MenuView_android_itemTextAppearance = 1; - + // aapt resource value: 3 public const int MenuView_android_verticalDivider = 3; - + // aapt resource value: 0 public const int MenuView_android_windowAnimationStyle = 0; - + // aapt resource value: 7 public const int MenuView_preserveIconSpacing = 7; - + // aapt resource value: 8 public const int MenuView_subMenuArrow = 8; - + // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } public static int[] NavigationView = new int[] { 16842964, @@ -9015,70 +8892,70 @@ public partial class Styleable 2130903227, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int NavigationView_android_background = 0; - + // aapt resource value: 1 public const int NavigationView_android_fitsSystemWindows = 1; - + // aapt resource value: 2 public const int NavigationView_android_maxWidth = 2; - + // aapt resource value: 3 public const int NavigationView_elevation = 3; - + // aapt resource value: 4 public const int NavigationView_headerLayout = 4; - + // aapt resource value: 5 public const int NavigationView_itemBackground = 5; - + // aapt resource value: 6 public const int NavigationView_itemIconTint = 6; - + // aapt resource value: 7 public const int NavigationView_itemTextAppearance = 7; - + // aapt resource value: 8 public const int NavigationView_itemTextColor = 8; - + // aapt resource value: 9 public const int NavigationView_menu = 9; - + // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } public static int[] PopupWindow = new int[] { 16843126, 16843465, 2130903277}; - + // aapt resource value: { 0x7F03011D } public static int[] PopupWindowBackgroundState = new int[] { 2130903325}; - + // aapt resource value: 0 public const int PopupWindowBackgroundState_state_above_anchor = 0; - + // aapt resource value: 1 public const int PopupWindow_android_popupAnimationStyle = 1; - + // aapt resource value: 0 public const int PopupWindow_android_popupBackground = 0; - + // aapt resource value: 2 public const int PopupWindow_overlapAnchor = 2; - + // aapt resource value: { 0x7F0300EE,0x7F0300F1 } public static int[] RecycleListView = new int[] { 2130903278, 2130903281}; - + // aapt resource value: 0 public const int RecycleListView_paddingBottomNoButtons = 0; - + // aapt resource value: 1 public const int RecycleListView_paddingTopNoTitle = 1; - + // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } public static int[] RecyclerView = new int[] { 16842948, @@ -9092,54 +8969,54 @@ public partial class Styleable 2130903303, 2130903318, 2130903324}; - + // aapt resource value: 1 public const int RecyclerView_android_descendantFocusability = 1; - + // aapt resource value: 0 public const int RecyclerView_android_orientation = 0; - + // aapt resource value: 2 public const int RecyclerView_fastScrollEnabled = 2; - + // aapt resource value: 3 public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - + // aapt resource value: 4 public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - + // aapt resource value: 5 public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - + // aapt resource value: 6 public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - + // aapt resource value: 7 public const int RecyclerView_layoutManager = 7; - + // aapt resource value: 8 public const int RecyclerView_reverseLayout = 8; - + // aapt resource value: 9 public const int RecyclerView_spanCount = 9; - + // aapt resource value: 10 public const int RecyclerView_stackFromEnd = 10; - + // aapt resource value: { 0x7F0300B6 } public static int[] ScrimInsetsFrameLayout = new int[] { 2130903222}; - + // aapt resource value: 0 public const int ScrimInsetsFrameLayout_insetForeground = 0; - + // aapt resource value: { 0x7F030039 } public static int[] ScrollingViewBehavior_Layout = new int[] { 2130903097}; - + // aapt resource value: 0 public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; - + // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } public static int[] SearchView = new int[] { 16842970, @@ -9159,73 +9036,73 @@ public partial class Styleable 2130903331, 2130903336, 2130903400}; - + // aapt resource value: 0 public const int SearchView_android_focusable = 0; - + // aapt resource value: 3 public const int SearchView_android_imeOptions = 3; - + // aapt resource value: 2 public const int SearchView_android_inputType = 2; - + // aapt resource value: 1 public const int SearchView_android_maxWidth = 1; - + // aapt resource value: 4 public const int SearchView_closeIcon = 4; - + // aapt resource value: 5 public const int SearchView_commitIcon = 5; - + // aapt resource value: 6 public const int SearchView_defaultQueryHint = 6; - + // aapt resource value: 7 public const int SearchView_goIcon = 7; - + // aapt resource value: 8 public const int SearchView_iconifiedByDefault = 8; - + // aapt resource value: 9 public const int SearchView_layout = 9; - + // aapt resource value: 10 public const int SearchView_queryBackground = 10; - + // aapt resource value: 11 public const int SearchView_queryHint = 11; - + // aapt resource value: 12 public const int SearchView_searchHintIcon = 12; - + // aapt resource value: 13 public const int SearchView_searchIcon = 13; - + // aapt resource value: 14 public const int SearchView_submitBackground = 14; - + // aapt resource value: 15 public const int SearchView_suggestionRowLayout = 15; - + // aapt resource value: 16 public const int SearchView_voiceIcon = 16; - + // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } public static int[] SnackbarLayout = new int[] { 16843039, 2130903175, 2130903255}; - + // aapt resource value: 0 public const int SnackbarLayout_android_maxWidth = 0; - + // aapt resource value: 1 public const int SnackbarLayout_elevation = 1; - + // aapt resource value: 2 public const int SnackbarLayout_maxActionInlineWidth = 2; - + // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } public static int[] Spinner = new int[] { 16842930, @@ -9233,22 +9110,22 @@ public partial class Styleable 16843131, 16843362, 2130903291}; - + // aapt resource value: 3 public const int Spinner_android_dropDownWidth = 3; - + // aapt resource value: 0 public const int Spinner_android_entries = 0; - + // aapt resource value: 1 public const int Spinner_android_popupBackground = 1; - + // aapt resource value: 2 public const int Spinner_android_prompt = 2; - + // aapt resource value: 4 public const int Spinner_popupTheme = 4; - + // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } public static int[] SwitchCompat = new int[] { 16843044, @@ -9265,64 +9142,64 @@ public partial class Styleable 2130903396, 2130903397, 2130903398}; - + // aapt resource value: 1 public const int SwitchCompat_android_textOff = 1; - + // aapt resource value: 0 public const int SwitchCompat_android_textOn = 0; - + // aapt resource value: 2 public const int SwitchCompat_android_thumb = 2; - + // aapt resource value: 3 public const int SwitchCompat_showText = 3; - + // aapt resource value: 4 public const int SwitchCompat_splitTrack = 4; - + // aapt resource value: 5 public const int SwitchCompat_switchMinWidth = 5; - + // aapt resource value: 6 public const int SwitchCompat_switchPadding = 6; - + // aapt resource value: 7 public const int SwitchCompat_switchTextAppearance = 7; - + // aapt resource value: 8 public const int SwitchCompat_thumbTextPadding = 8; - + // aapt resource value: 9 public const int SwitchCompat_thumbTint = 9; - + // aapt resource value: 10 public const int SwitchCompat_thumbTintMode = 10; - + // aapt resource value: 11 public const int SwitchCompat_track = 11; - + // aapt resource value: 12 public const int SwitchCompat_trackTint = 12; - + // aapt resource value: 13 public const int SwitchCompat_trackTintMode = 13; - + // aapt resource value: { 0x1010002,0x10100F2,0x101014F } public static int[] TabItem = new int[] { 16842754, 16842994, 16843087}; - + // aapt resource value: 0 public const int TabItem_android_icon = 0; - + // aapt resource value: 1 public const int TabItem_android_layout = 1; - + // aapt resource value: 2 public const int TabItem_android_text = 2; - + // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } public static int[] TabLayout = new int[] { 2130903341, @@ -9341,55 +9218,55 @@ public partial class Styleable 2130903354, 2130903355, 2130903356}; - + // aapt resource value: 0 public const int TabLayout_tabBackground = 0; - + // aapt resource value: 1 public const int TabLayout_tabContentStart = 1; - + // aapt resource value: 2 public const int TabLayout_tabGravity = 2; - + // aapt resource value: 3 public const int TabLayout_tabIndicatorColor = 3; - + // aapt resource value: 4 public const int TabLayout_tabIndicatorHeight = 4; - + // aapt resource value: 5 public const int TabLayout_tabMaxWidth = 5; - + // aapt resource value: 6 public const int TabLayout_tabMinWidth = 6; - + // aapt resource value: 7 public const int TabLayout_tabMode = 7; - + // aapt resource value: 8 public const int TabLayout_tabPadding = 8; - + // aapt resource value: 9 public const int TabLayout_tabPaddingBottom = 9; - + // aapt resource value: 10 public const int TabLayout_tabPaddingEnd = 10; - + // aapt resource value: 11 public const int TabLayout_tabPaddingStart = 11; - + // aapt resource value: 12 public const int TabLayout_tabPaddingTop = 12; - + // aapt resource value: 13 public const int TabLayout_tabSelectedTextColor = 13; - + // aapt resource value: 14 public const int TabLayout_tabTextAppearance = 14; - + // aapt resource value: 15 public const int TabLayout_tabTextColor = 15; - + // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } public static int[] TextAppearance = new int[] { 16842901, @@ -9405,46 +9282,46 @@ public partial class Styleable 16843692, 2130903195, 2130903357}; - + // aapt resource value: 10 public const int TextAppearance_android_fontFamily = 10; - + // aapt resource value: 6 public const int TextAppearance_android_shadowColor = 6; - + // aapt resource value: 7 public const int TextAppearance_android_shadowDx = 7; - + // aapt resource value: 8 public const int TextAppearance_android_shadowDy = 8; - + // aapt resource value: 9 public const int TextAppearance_android_shadowRadius = 9; - + // aapt resource value: 3 public const int TextAppearance_android_textColor = 3; - + // aapt resource value: 4 public const int TextAppearance_android_textColorHint = 4; - + // aapt resource value: 5 public const int TextAppearance_android_textColorLink = 5; - + // aapt resource value: 0 public const int TextAppearance_android_textSize = 0; - + // aapt resource value: 2 public const int TextAppearance_android_textStyle = 2; - + // aapt resource value: 1 public const int TextAppearance_android_typeface = 1; - + // aapt resource value: 11 public const int TextAppearance_fontFamily = 11; - + // aapt resource value: 12 public const int TextAppearance_textAllCaps = 12; - + // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } public static int[] TextInputLayout = new int[] { 16842906, @@ -9463,55 +9340,55 @@ public partial class Styleable 2130903287, 2130903288, 2130903289}; - + // aapt resource value: 1 public const int TextInputLayout_android_hint = 1; - + // aapt resource value: 0 public const int TextInputLayout_android_textColorHint = 0; - + // aapt resource value: 2 public const int TextInputLayout_counterEnabled = 2; - + // aapt resource value: 3 public const int TextInputLayout_counterMaxLength = 3; - + // aapt resource value: 4 public const int TextInputLayout_counterOverflowTextAppearance = 4; - + // aapt resource value: 5 public const int TextInputLayout_counterTextAppearance = 5; - + // aapt resource value: 6 public const int TextInputLayout_errorEnabled = 6; - + // aapt resource value: 7 public const int TextInputLayout_errorTextAppearance = 7; - + // aapt resource value: 8 public const int TextInputLayout_hintAnimationEnabled = 8; - + // aapt resource value: 9 public const int TextInputLayout_hintEnabled = 9; - + // aapt resource value: 10 public const int TextInputLayout_hintTextAppearance = 10; - + // aapt resource value: 11 public const int TextInputLayout_passwordToggleContentDescription = 11; - + // aapt resource value: 12 public const int TextInputLayout_passwordToggleDrawable = 12; - + // aapt resource value: 13 public const int TextInputLayout_passwordToggleEnabled = 13; - + // aapt resource value: 14 public const int TextInputLayout_passwordToggleTint = 14; - + // aapt resource value: 15 public const int TextInputLayout_passwordToggleTintMode = 15; - + // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } public static int[] Toolbar = new int[] { 16842927, @@ -9543,94 +9420,94 @@ public partial class Styleable 2130903386, 2130903387, 2130903388}; - + // aapt resource value: 0 public const int Toolbar_android_gravity = 0; - + // aapt resource value: 1 public const int Toolbar_android_minHeight = 1; - + // aapt resource value: 2 public const int Toolbar_buttonGravity = 2; - + // aapt resource value: 3 public const int Toolbar_collapseContentDescription = 3; - + // aapt resource value: 4 public const int Toolbar_collapseIcon = 4; - + // aapt resource value: 5 public const int Toolbar_contentInsetEnd = 5; - + // aapt resource value: 6 public const int Toolbar_contentInsetEndWithActions = 6; - + // aapt resource value: 7 public const int Toolbar_contentInsetLeft = 7; - + // aapt resource value: 8 public const int Toolbar_contentInsetRight = 8; - + // aapt resource value: 9 public const int Toolbar_contentInsetStart = 9; - + // aapt resource value: 10 public const int Toolbar_contentInsetStartWithNavigation = 10; - + // aapt resource value: 11 public const int Toolbar_logo = 11; - + // aapt resource value: 12 public const int Toolbar_logoDescription = 12; - + // aapt resource value: 13 public const int Toolbar_maxButtonHeight = 13; - + // aapt resource value: 14 public const int Toolbar_navigationContentDescription = 14; - + // aapt resource value: 15 public const int Toolbar_navigationIcon = 15; - + // aapt resource value: 16 public const int Toolbar_popupTheme = 16; - + // aapt resource value: 17 public const int Toolbar_subtitle = 17; - + // aapt resource value: 18 public const int Toolbar_subtitleTextAppearance = 18; - + // aapt resource value: 19 public const int Toolbar_subtitleTextColor = 19; - + // aapt resource value: 20 public const int Toolbar_title = 20; - + // aapt resource value: 21 public const int Toolbar_titleMargin = 21; - + // aapt resource value: 22 public const int Toolbar_titleMarginBottom = 22; - + // aapt resource value: 23 public const int Toolbar_titleMarginEnd = 23; - + // aapt resource value: 26 public const int Toolbar_titleMargins = 26; - + // aapt resource value: 24 public const int Toolbar_titleMarginStart = 24; - + // aapt resource value: 25 public const int Toolbar_titleMarginTop = 25; - + // aapt resource value: 27 public const int Toolbar_titleTextAppearance = 27; - + // aapt resource value: 28 public const int Toolbar_titleTextColor = 28; - + // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } public static int[] View = new int[] { 16842752, @@ -9638,57 +9515,57 @@ public partial class Styleable 2130903279, 2130903280, 2130903369}; - + // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } public static int[] ViewBackgroundHelper = new int[] { 16842964, 2130903092, 2130903093}; - + // aapt resource value: 0 public const int ViewBackgroundHelper_android_background = 0; - + // aapt resource value: 1 public const int ViewBackgroundHelper_backgroundTint = 1; - + // aapt resource value: 2 public const int ViewBackgroundHelper_backgroundTintMode = 2; - + // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } public static int[] ViewStubCompat = new int[] { 16842960, 16842994, 16842995}; - + // aapt resource value: 0 public const int ViewStubCompat_android_id = 0; - + // aapt resource value: 2 public const int ViewStubCompat_android_inflatedId = 2; - + // aapt resource value: 1 public const int ViewStubCompat_android_layout = 1; - + // aapt resource value: 1 public const int View_android_focusable = 1; - + // aapt resource value: 0 public const int View_android_theme = 0; - + // aapt resource value: 2 public const int View_paddingEnd = 2; - + // aapt resource value: 3 public const int View_paddingStart = 3; - + // aapt resource value: 4 public const int View_theme = 4; - + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Styleable() { } From 0651046d2beca3be6518ce515172a1743bfd42fa Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 09:00:46 -0700 Subject: [PATCH 474/499] remove Platform.ios.cs --- .../PlatformSpecific/Platform.ios.cs | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs deleted file mode 100644 index df8f1b40..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.ios.cs +++ /dev/null @@ -1,62 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Runtime.InteropServices; -using ObjCRuntime; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class Platform - { -#if __IOS__ - [DllImport(Constants.SystemLibrary, EntryPoint = "sysctlbyname")] -#else - [DllImport(Constants.libSystemLibrary, EntryPoint = "sysctlbyname")] -#endif - private static extern int SysctlByName([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output, IntPtr oldLen, IntPtr newp, uint newlen); - - internal static string GetSystemLibraryProperty(string property) - { - var lengthPtr = Marshal.AllocHGlobal(sizeof(int)); - SysctlByName(property, IntPtr.Zero, lengthPtr, IntPtr.Zero, 0); - - var propertyLength = Marshal.ReadInt32(lengthPtr); - - if (propertyLength == 0) - { - Marshal.FreeHGlobal(lengthPtr); - throw new InvalidOperationException("Unable to read length of property."); - } - - var valuePtr = Marshal.AllocHGlobal(propertyLength); - SysctlByName(property, valuePtr, lengthPtr, IntPtr.Zero, 0); - - var returnValue = Marshal.PtrToStringAnsi(valuePtr); - - Marshal.FreeHGlobal(lengthPtr); - Marshal.FreeHGlobal(valuePtr); - - return returnValue; - } - } -} From ad006757d900326f34d2e1027936cf225e402e92 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 09:02:23 -0700 Subject: [PATCH 475/499] revert changes to Android/Resource.designer.cs to main branch --- .../Resources/Resource.designer.cs | 4672 ++++++++--------- 1 file changed, 2336 insertions(+), 2336 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs index 046c4004..7f4e9317 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs @@ -13,17 +13,17 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { - - + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] public partial class Resource { - + static Resource() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + public static void UpdateIdValues() { global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; @@ -2013,5379 +2013,5379 @@ public static void UpdateIdValues() global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; } - + public partial class Animation { - + // aapt resource value: 0x7F010000 public const int abc_fade_in = 2130771968; - + // aapt resource value: 0x7F010001 public const int abc_fade_out = 2130771969; - + // aapt resource value: 0x7F010002 public const int abc_grow_fade_in_from_bottom = 2130771970; - + // aapt resource value: 0x7F010003 public const int abc_popup_enter = 2130771971; - + // aapt resource value: 0x7F010004 public const int abc_popup_exit = 2130771972; - + // aapt resource value: 0x7F010005 public const int abc_shrink_fade_out_from_bottom = 2130771973; - + // aapt resource value: 0x7F010006 public const int abc_slide_in_bottom = 2130771974; - + // aapt resource value: 0x7F010007 public const int abc_slide_in_top = 2130771975; - + // aapt resource value: 0x7F010008 public const int abc_slide_out_bottom = 2130771976; - + // aapt resource value: 0x7F010009 public const int abc_slide_out_top = 2130771977; - + // aapt resource value: 0x7F01000A public const int design_bottom_sheet_slide_in = 2130771978; - + // aapt resource value: 0x7F01000B public const int design_bottom_sheet_slide_out = 2130771979; - + // aapt resource value: 0x7F01000C public const int design_snackbar_in = 2130771980; - + // aapt resource value: 0x7F01000D public const int design_snackbar_out = 2130771981; - + // aapt resource value: 0x7F01000E public const int EnterFromLeft = 2130771982; - + // aapt resource value: 0x7F01000F public const int EnterFromRight = 2130771983; - + // aapt resource value: 0x7F010010 public const int ExitToLeft = 2130771984; - + // aapt resource value: 0x7F010011 public const int ExitToRight = 2130771985; - + // aapt resource value: 0x7F010012 public const int tooltip_enter = 2130771986; - + // aapt resource value: 0x7F010013 public const int tooltip_exit = 2130771987; - + static Animation() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animation() { } } - + public partial class Animator { - + // aapt resource value: 0x7F020000 public const int design_appbar_state_list_animator = 2130837504; - + static Animator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Animator() { } } - + public partial class Attribute { - + // aapt resource value: 0x7F030000 public const int actionBarDivider = 2130903040; - + // aapt resource value: 0x7F030001 public const int actionBarItemBackground = 2130903041; - + // aapt resource value: 0x7F030002 public const int actionBarPopupTheme = 2130903042; - + // aapt resource value: 0x7F030003 public const int actionBarSize = 2130903043; - + // aapt resource value: 0x7F030004 public const int actionBarSplitStyle = 2130903044; - + // aapt resource value: 0x7F030005 public const int actionBarStyle = 2130903045; - + // aapt resource value: 0x7F030006 public const int actionBarTabBarStyle = 2130903046; - + // aapt resource value: 0x7F030007 public const int actionBarTabStyle = 2130903047; - + // aapt resource value: 0x7F030008 public const int actionBarTabTextStyle = 2130903048; - + // aapt resource value: 0x7F030009 public const int actionBarTheme = 2130903049; - + // aapt resource value: 0x7F03000A public const int actionBarWidgetTheme = 2130903050; - + // aapt resource value: 0x7F03000B public const int actionButtonStyle = 2130903051; - + // aapt resource value: 0x7F03000C public const int actionDropDownStyle = 2130903052; - + // aapt resource value: 0x7F03000D public const int actionLayout = 2130903053; - + // aapt resource value: 0x7F03000E public const int actionMenuTextAppearance = 2130903054; - + // aapt resource value: 0x7F03000F public const int actionMenuTextColor = 2130903055; - + // aapt resource value: 0x7F030010 public const int actionModeBackground = 2130903056; - + // aapt resource value: 0x7F030011 public const int actionModeCloseButtonStyle = 2130903057; - + // aapt resource value: 0x7F030012 public const int actionModeCloseDrawable = 2130903058; - + // aapt resource value: 0x7F030013 public const int actionModeCopyDrawable = 2130903059; - + // aapt resource value: 0x7F030014 public const int actionModeCutDrawable = 2130903060; - + // aapt resource value: 0x7F030015 public const int actionModeFindDrawable = 2130903061; - + // aapt resource value: 0x7F030016 public const int actionModePasteDrawable = 2130903062; - + // aapt resource value: 0x7F030017 public const int actionModePopupWindowStyle = 2130903063; - + // aapt resource value: 0x7F030018 public const int actionModeSelectAllDrawable = 2130903064; - + // aapt resource value: 0x7F030019 public const int actionModeShareDrawable = 2130903065; - + // aapt resource value: 0x7F03001A public const int actionModeSplitBackground = 2130903066; - + // aapt resource value: 0x7F03001B public const int actionModeStyle = 2130903067; - + // aapt resource value: 0x7F03001C public const int actionModeWebSearchDrawable = 2130903068; - + // aapt resource value: 0x7F03001D public const int actionOverflowButtonStyle = 2130903069; - + // aapt resource value: 0x7F03001E public const int actionOverflowMenuStyle = 2130903070; - + // aapt resource value: 0x7F03001F public const int actionProviderClass = 2130903071; - + // aapt resource value: 0x7F030020 public const int actionViewClass = 2130903072; - + // aapt resource value: 0x7F030021 public const int activityChooserViewStyle = 2130903073; - + // aapt resource value: 0x7F030022 public const int alertDialogButtonGroupStyle = 2130903074; - + // aapt resource value: 0x7F030023 public const int alertDialogCenterButtons = 2130903075; - + // aapt resource value: 0x7F030024 public const int alertDialogStyle = 2130903076; - + // aapt resource value: 0x7F030025 public const int alertDialogTheme = 2130903077; - + // aapt resource value: 0x7F030026 public const int allowStacking = 2130903078; - + // aapt resource value: 0x7F030027 public const int alpha = 2130903079; - + // aapt resource value: 0x7F030028 public const int alphabeticModifiers = 2130903080; - + // aapt resource value: 0x7F030029 public const int arrowHeadLength = 2130903081; - + // aapt resource value: 0x7F03002A public const int arrowShaftLength = 2130903082; - + // aapt resource value: 0x7F03002B public const int autoCompleteTextViewStyle = 2130903083; - + // aapt resource value: 0x7F03002C public const int autoSizeMaxTextSize = 2130903084; - + // aapt resource value: 0x7F03002D public const int autoSizeMinTextSize = 2130903085; - + // aapt resource value: 0x7F03002E public const int autoSizePresetSizes = 2130903086; - + // aapt resource value: 0x7F03002F public const int autoSizeStepGranularity = 2130903087; - + // aapt resource value: 0x7F030030 public const int autoSizeTextType = 2130903088; - + // aapt resource value: 0x7F030031 public const int background = 2130903089; - + // aapt resource value: 0x7F030032 public const int backgroundSplit = 2130903090; - + // aapt resource value: 0x7F030033 public const int backgroundStacked = 2130903091; - + // aapt resource value: 0x7F030034 public const int backgroundTint = 2130903092; - + // aapt resource value: 0x7F030035 public const int backgroundTintMode = 2130903093; - + // aapt resource value: 0x7F030036 public const int barLength = 2130903094; - + // aapt resource value: 0x7F030037 public const int behavior_autoHide = 2130903095; - + // aapt resource value: 0x7F030038 public const int behavior_hideable = 2130903096; - + // aapt resource value: 0x7F030039 public const int behavior_overlapTop = 2130903097; - + // aapt resource value: 0x7F03003A public const int behavior_peekHeight = 2130903098; - + // aapt resource value: 0x7F03003B public const int behavior_skipCollapsed = 2130903099; - + // aapt resource value: 0x7F03003D public const int borderlessButtonStyle = 2130903101; - + // aapt resource value: 0x7F03003C public const int borderWidth = 2130903100; - + // aapt resource value: 0x7F03003E public const int bottomSheetDialogTheme = 2130903102; - + // aapt resource value: 0x7F03003F public const int bottomSheetStyle = 2130903103; - + // aapt resource value: 0x7F030040 public const int buttonBarButtonStyle = 2130903104; - + // aapt resource value: 0x7F030041 public const int buttonBarNegativeButtonStyle = 2130903105; - + // aapt resource value: 0x7F030042 public const int buttonBarNeutralButtonStyle = 2130903106; - + // aapt resource value: 0x7F030043 public const int buttonBarPositiveButtonStyle = 2130903107; - + // aapt resource value: 0x7F030044 public const int buttonBarStyle = 2130903108; - + // aapt resource value: 0x7F030045 public const int buttonGravity = 2130903109; - + // aapt resource value: 0x7F030046 public const int buttonPanelSideLayout = 2130903110; - + // aapt resource value: 0x7F030047 public const int buttonStyle = 2130903111; - + // aapt resource value: 0x7F030048 public const int buttonStyleSmall = 2130903112; - + // aapt resource value: 0x7F030049 public const int buttonTint = 2130903113; - + // aapt resource value: 0x7F03004A public const int buttonTintMode = 2130903114; - + // aapt resource value: 0x7F03004B public const int cardBackgroundColor = 2130903115; - + // aapt resource value: 0x7F03004C public const int cardCornerRadius = 2130903116; - + // aapt resource value: 0x7F03004D public const int cardElevation = 2130903117; - + // aapt resource value: 0x7F03004E public const int cardMaxElevation = 2130903118; - + // aapt resource value: 0x7F03004F public const int cardPreventCornerOverlap = 2130903119; - + // aapt resource value: 0x7F030050 public const int cardUseCompatPadding = 2130903120; - + // aapt resource value: 0x7F030051 public const int checkboxStyle = 2130903121; - + // aapt resource value: 0x7F030052 public const int checkedTextViewStyle = 2130903122; - + // aapt resource value: 0x7F030053 public const int closeIcon = 2130903123; - + // aapt resource value: 0x7F030054 public const int closeItemLayout = 2130903124; - + // aapt resource value: 0x7F030055 public const int collapseContentDescription = 2130903125; - + // aapt resource value: 0x7F030057 public const int collapsedTitleGravity = 2130903127; - + // aapt resource value: 0x7F030058 public const int collapsedTitleTextAppearance = 2130903128; - + // aapt resource value: 0x7F030056 public const int collapseIcon = 2130903126; - + // aapt resource value: 0x7F030059 public const int color = 2130903129; - + // aapt resource value: 0x7F03005A public const int colorAccent = 2130903130; - + // aapt resource value: 0x7F03005B public const int colorBackgroundFloating = 2130903131; - + // aapt resource value: 0x7F03005C public const int colorButtonNormal = 2130903132; - + // aapt resource value: 0x7F03005D public const int colorControlActivated = 2130903133; - + // aapt resource value: 0x7F03005E public const int colorControlHighlight = 2130903134; - + // aapt resource value: 0x7F03005F public const int colorControlNormal = 2130903135; - + // aapt resource value: 0x7F030060 public const int colorError = 2130903136; - + // aapt resource value: 0x7F030061 public const int colorPrimary = 2130903137; - + // aapt resource value: 0x7F030062 public const int colorPrimaryDark = 2130903138; - + // aapt resource value: 0x7F030063 public const int colorSwitchThumbNormal = 2130903139; - + // aapt resource value: 0x7F030064 public const int commitIcon = 2130903140; - + // aapt resource value: 0x7F030065 public const int contentDescription = 2130903141; - + // aapt resource value: 0x7F030066 public const int contentInsetEnd = 2130903142; - + // aapt resource value: 0x7F030067 public const int contentInsetEndWithActions = 2130903143; - + // aapt resource value: 0x7F030068 public const int contentInsetLeft = 2130903144; - + // aapt resource value: 0x7F030069 public const int contentInsetRight = 2130903145; - + // aapt resource value: 0x7F03006A public const int contentInsetStart = 2130903146; - + // aapt resource value: 0x7F03006B public const int contentInsetStartWithNavigation = 2130903147; - + // aapt resource value: 0x7F03006C public const int contentPadding = 2130903148; - + // aapt resource value: 0x7F03006D public const int contentPaddingBottom = 2130903149; - + // aapt resource value: 0x7F03006E public const int contentPaddingLeft = 2130903150; - + // aapt resource value: 0x7F03006F public const int contentPaddingRight = 2130903151; - + // aapt resource value: 0x7F030070 public const int contentPaddingTop = 2130903152; - + // aapt resource value: 0x7F030071 public const int contentScrim = 2130903153; - + // aapt resource value: 0x7F030072 public const int controlBackground = 2130903154; - + // aapt resource value: 0x7F030073 public const int counterEnabled = 2130903155; - + // aapt resource value: 0x7F030074 public const int counterMaxLength = 2130903156; - + // aapt resource value: 0x7F030075 public const int counterOverflowTextAppearance = 2130903157; - + // aapt resource value: 0x7F030076 public const int counterTextAppearance = 2130903158; - + // aapt resource value: 0x7F030077 public const int customNavigationLayout = 2130903159; - + // aapt resource value: 0x7F030078 public const int defaultQueryHint = 2130903160; - + // aapt resource value: 0x7F030079 public const int dialogPreferredPadding = 2130903161; - + // aapt resource value: 0x7F03007A public const int dialogTheme = 2130903162; - + // aapt resource value: 0x7F03007B public const int displayOptions = 2130903163; - + // aapt resource value: 0x7F03007C public const int divider = 2130903164; - + // aapt resource value: 0x7F03007D public const int dividerHorizontal = 2130903165; - + // aapt resource value: 0x7F03007E public const int dividerPadding = 2130903166; - + // aapt resource value: 0x7F03007F public const int dividerVertical = 2130903167; - + // aapt resource value: 0x7F030080 public const int drawableSize = 2130903168; - + // aapt resource value: 0x7F030081 public const int drawerArrowStyle = 2130903169; - + // aapt resource value: 0x7F030083 public const int dropdownListPreferredItemHeight = 2130903171; - + // aapt resource value: 0x7F030082 public const int dropDownListViewStyle = 2130903170; - + // aapt resource value: 0x7F030084 public const int editTextBackground = 2130903172; - + // aapt resource value: 0x7F030085 public const int editTextColor = 2130903173; - + // aapt resource value: 0x7F030086 public const int editTextStyle = 2130903174; - + // aapt resource value: 0x7F030087 public const int elevation = 2130903175; - + // aapt resource value: 0x7F030088 public const int errorEnabled = 2130903176; - + // aapt resource value: 0x7F030089 public const int errorTextAppearance = 2130903177; - + // aapt resource value: 0x7F03008A public const int expandActivityOverflowButtonDrawable = 2130903178; - + // aapt resource value: 0x7F03008B public const int expanded = 2130903179; - + // aapt resource value: 0x7F03008C public const int expandedTitleGravity = 2130903180; - + // aapt resource value: 0x7F03008D public const int expandedTitleMargin = 2130903181; - + // aapt resource value: 0x7F03008E public const int expandedTitleMarginBottom = 2130903182; - + // aapt resource value: 0x7F03008F public const int expandedTitleMarginEnd = 2130903183; - + // aapt resource value: 0x7F030090 public const int expandedTitleMarginStart = 2130903184; - + // aapt resource value: 0x7F030091 public const int expandedTitleMarginTop = 2130903185; - + // aapt resource value: 0x7F030092 public const int expandedTitleTextAppearance = 2130903186; - + // aapt resource value: 0x7F030093 public const int externalRouteEnabledDrawable = 2130903187; - + // aapt resource value: 0x7F030094 public const int fabSize = 2130903188; - + // aapt resource value: 0x7F030095 public const int fastScrollEnabled = 2130903189; - + // aapt resource value: 0x7F030096 public const int fastScrollHorizontalThumbDrawable = 2130903190; - + // aapt resource value: 0x7F030097 public const int fastScrollHorizontalTrackDrawable = 2130903191; - + // aapt resource value: 0x7F030098 public const int fastScrollVerticalThumbDrawable = 2130903192; - + // aapt resource value: 0x7F030099 public const int fastScrollVerticalTrackDrawable = 2130903193; - + // aapt resource value: 0x7F03009A public const int font = 2130903194; - + // aapt resource value: 0x7F03009B public const int fontFamily = 2130903195; - + // aapt resource value: 0x7F03009C public const int fontProviderAuthority = 2130903196; - + // aapt resource value: 0x7F03009D public const int fontProviderCerts = 2130903197; - + // aapt resource value: 0x7F03009E public const int fontProviderFetchStrategy = 2130903198; - + // aapt resource value: 0x7F03009F public const int fontProviderFetchTimeout = 2130903199; - + // aapt resource value: 0x7F0300A0 public const int fontProviderPackage = 2130903200; - + // aapt resource value: 0x7F0300A1 public const int fontProviderQuery = 2130903201; - + // aapt resource value: 0x7F0300A2 public const int fontStyle = 2130903202; - + // aapt resource value: 0x7F0300A3 public const int fontWeight = 2130903203; - + // aapt resource value: 0x7F0300A4 public const int foregroundInsidePadding = 2130903204; - + // aapt resource value: 0x7F0300A5 public const int gapBetweenBars = 2130903205; - + // aapt resource value: 0x7F0300A6 public const int goIcon = 2130903206; - + // aapt resource value: 0x7F0300A7 public const int headerLayout = 2130903207; - + // aapt resource value: 0x7F0300A8 public const int height = 2130903208; - + // aapt resource value: 0x7F0300A9 public const int hideOnContentScroll = 2130903209; - + // aapt resource value: 0x7F0300AA public const int hintAnimationEnabled = 2130903210; - + // aapt resource value: 0x7F0300AB public const int hintEnabled = 2130903211; - + // aapt resource value: 0x7F0300AC public const int hintTextAppearance = 2130903212; - + // aapt resource value: 0x7F0300AD public const int homeAsUpIndicator = 2130903213; - + // aapt resource value: 0x7F0300AE public const int homeLayout = 2130903214; - + // aapt resource value: 0x7F0300AF public const int icon = 2130903215; - + // aapt resource value: 0x7F0300B2 public const int iconifiedByDefault = 2130903218; - + // aapt resource value: 0x7F0300B0 public const int iconTint = 2130903216; - + // aapt resource value: 0x7F0300B1 public const int iconTintMode = 2130903217; - + // aapt resource value: 0x7F0300B3 public const int imageButtonStyle = 2130903219; - + // aapt resource value: 0x7F0300B4 public const int indeterminateProgressStyle = 2130903220; - + // aapt resource value: 0x7F0300B5 public const int initialActivityCount = 2130903221; - + // aapt resource value: 0x7F0300B6 public const int insetForeground = 2130903222; - + // aapt resource value: 0x7F0300B7 public const int isLightTheme = 2130903223; - + // aapt resource value: 0x7F0300B8 public const int itemBackground = 2130903224; - + // aapt resource value: 0x7F0300B9 public const int itemIconTint = 2130903225; - + // aapt resource value: 0x7F0300BA public const int itemPadding = 2130903226; - + // aapt resource value: 0x7F0300BB public const int itemTextAppearance = 2130903227; - + // aapt resource value: 0x7F0300BC public const int itemTextColor = 2130903228; - + // aapt resource value: 0x7F0300BD public const int keylines = 2130903229; - + // aapt resource value: 0x7F0300BE public const int layout = 2130903230; - + // aapt resource value: 0x7F0300BF public const int layoutManager = 2130903231; - + // aapt resource value: 0x7F0300C0 public const int layout_anchor = 2130903232; - + // aapt resource value: 0x7F0300C1 public const int layout_anchorGravity = 2130903233; - + // aapt resource value: 0x7F0300C2 public const int layout_behavior = 2130903234; - + // aapt resource value: 0x7F0300C3 public const int layout_collapseMode = 2130903235; - + // aapt resource value: 0x7F0300C4 public const int layout_collapseParallaxMultiplier = 2130903236; - + // aapt resource value: 0x7F0300C5 public const int layout_dodgeInsetEdges = 2130903237; - + // aapt resource value: 0x7F0300C6 public const int layout_insetEdge = 2130903238; - + // aapt resource value: 0x7F0300C7 public const int layout_keyline = 2130903239; - + // aapt resource value: 0x7F0300C8 public const int layout_scrollFlags = 2130903240; - + // aapt resource value: 0x7F0300C9 public const int layout_scrollInterpolator = 2130903241; - + // aapt resource value: 0x7F0300CA public const int listChoiceBackgroundIndicator = 2130903242; - + // aapt resource value: 0x7F0300CB public const int listDividerAlertDialog = 2130903243; - + // aapt resource value: 0x7F0300CC public const int listItemLayout = 2130903244; - + // aapt resource value: 0x7F0300CD public const int listLayout = 2130903245; - + // aapt resource value: 0x7F0300CE public const int listMenuViewStyle = 2130903246; - + // aapt resource value: 0x7F0300CF public const int listPopupWindowStyle = 2130903247; - + // aapt resource value: 0x7F0300D0 public const int listPreferredItemHeight = 2130903248; - + // aapt resource value: 0x7F0300D1 public const int listPreferredItemHeightLarge = 2130903249; - + // aapt resource value: 0x7F0300D2 public const int listPreferredItemHeightSmall = 2130903250; - + // aapt resource value: 0x7F0300D3 public const int listPreferredItemPaddingLeft = 2130903251; - + // aapt resource value: 0x7F0300D4 public const int listPreferredItemPaddingRight = 2130903252; - + // aapt resource value: 0x7F0300D5 public const int logo = 2130903253; - + // aapt resource value: 0x7F0300D6 public const int logoDescription = 2130903254; - + // aapt resource value: 0x7F0300D7 public const int maxActionInlineWidth = 2130903255; - + // aapt resource value: 0x7F0300D8 public const int maxButtonHeight = 2130903256; - + // aapt resource value: 0x7F0300D9 public const int measureWithLargestChild = 2130903257; - + // aapt resource value: 0x7F0300DA public const int mediaRouteAudioTrackDrawable = 2130903258; - + // aapt resource value: 0x7F0300DB public const int mediaRouteButtonStyle = 2130903259; - + // aapt resource value: 0x7F0300DC public const int mediaRouteButtonTint = 2130903260; - + // aapt resource value: 0x7F0300DD public const int mediaRouteCloseDrawable = 2130903261; - + // aapt resource value: 0x7F0300DE public const int mediaRouteControlPanelThemeOverlay = 2130903262; - + // aapt resource value: 0x7F0300DF public const int mediaRouteDefaultIconDrawable = 2130903263; - + // aapt resource value: 0x7F0300E0 public const int mediaRoutePauseDrawable = 2130903264; - + // aapt resource value: 0x7F0300E1 public const int mediaRoutePlayDrawable = 2130903265; - + // aapt resource value: 0x7F0300E2 public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - + // aapt resource value: 0x7F0300E3 public const int mediaRouteSpeakerIconDrawable = 2130903267; - + // aapt resource value: 0x7F0300E4 public const int mediaRouteStopDrawable = 2130903268; - + // aapt resource value: 0x7F0300E5 public const int mediaRouteTheme = 2130903269; - + // aapt resource value: 0x7F0300E6 public const int mediaRouteTvIconDrawable = 2130903270; - + // aapt resource value: 0x7F0300E7 public const int menu = 2130903271; - + // aapt resource value: 0x7F0300E8 public const int multiChoiceItemLayout = 2130903272; - + // aapt resource value: 0x7F0300E9 public const int navigationContentDescription = 2130903273; - + // aapt resource value: 0x7F0300EA public const int navigationIcon = 2130903274; - + // aapt resource value: 0x7F0300EB public const int navigationMode = 2130903275; - + // aapt resource value: 0x7F0300EC public const int numericModifiers = 2130903276; - + // aapt resource value: 0x7F0300ED public const int overlapAnchor = 2130903277; - + // aapt resource value: 0x7F0300EE public const int paddingBottomNoButtons = 2130903278; - + // aapt resource value: 0x7F0300EF public const int paddingEnd = 2130903279; - + // aapt resource value: 0x7F0300F0 public const int paddingStart = 2130903280; - + // aapt resource value: 0x7F0300F1 public const int paddingTopNoTitle = 2130903281; - + // aapt resource value: 0x7F0300F2 public const int panelBackground = 2130903282; - + // aapt resource value: 0x7F0300F3 public const int panelMenuListTheme = 2130903283; - + // aapt resource value: 0x7F0300F4 public const int panelMenuListWidth = 2130903284; - + // aapt resource value: 0x7F0300F5 public const int passwordToggleContentDescription = 2130903285; - + // aapt resource value: 0x7F0300F6 public const int passwordToggleDrawable = 2130903286; - + // aapt resource value: 0x7F0300F7 public const int passwordToggleEnabled = 2130903287; - + // aapt resource value: 0x7F0300F8 public const int passwordToggleTint = 2130903288; - + // aapt resource value: 0x7F0300F9 public const int passwordToggleTintMode = 2130903289; - + // aapt resource value: 0x7F0300FA public const int popupMenuStyle = 2130903290; - + // aapt resource value: 0x7F0300FB public const int popupTheme = 2130903291; - + // aapt resource value: 0x7F0300FC public const int popupWindowStyle = 2130903292; - + // aapt resource value: 0x7F0300FD public const int preserveIconSpacing = 2130903293; - + // aapt resource value: 0x7F0300FE public const int pressedTranslationZ = 2130903294; - + // aapt resource value: 0x7F0300FF public const int progressBarPadding = 2130903295; - + // aapt resource value: 0x7F030100 public const int progressBarStyle = 2130903296; - + // aapt resource value: 0x7F030101 public const int queryBackground = 2130903297; - + // aapt resource value: 0x7F030102 public const int queryHint = 2130903298; - + // aapt resource value: 0x7F030103 public const int radioButtonStyle = 2130903299; - + // aapt resource value: 0x7F030104 public const int ratingBarStyle = 2130903300; - + // aapt resource value: 0x7F030105 public const int ratingBarStyleIndicator = 2130903301; - + // aapt resource value: 0x7F030106 public const int ratingBarStyleSmall = 2130903302; - + // aapt resource value: 0x7F030107 public const int reverseLayout = 2130903303; - + // aapt resource value: 0x7F030108 public const int rippleColor = 2130903304; - + // aapt resource value: 0x7F030109 public const int scrimAnimationDuration = 2130903305; - + // aapt resource value: 0x7F03010A public const int scrimVisibleHeightTrigger = 2130903306; - + // aapt resource value: 0x7F03010B public const int searchHintIcon = 2130903307; - + // aapt resource value: 0x7F03010C public const int searchIcon = 2130903308; - + // aapt resource value: 0x7F03010D public const int searchViewStyle = 2130903309; - + // aapt resource value: 0x7F03010E public const int seekBarStyle = 2130903310; - + // aapt resource value: 0x7F03010F public const int selectableItemBackground = 2130903311; - + // aapt resource value: 0x7F030110 public const int selectableItemBackgroundBorderless = 2130903312; - + // aapt resource value: 0x7F030111 public const int showAsAction = 2130903313; - + // aapt resource value: 0x7F030112 public const int showDividers = 2130903314; - + // aapt resource value: 0x7F030113 public const int showText = 2130903315; - + // aapt resource value: 0x7F030114 public const int showTitle = 2130903316; - + // aapt resource value: 0x7F030115 public const int singleChoiceItemLayout = 2130903317; - + // aapt resource value: 0x7F030116 public const int spanCount = 2130903318; - + // aapt resource value: 0x7F030117 public const int spinBars = 2130903319; - + // aapt resource value: 0x7F030118 public const int spinnerDropDownItemStyle = 2130903320; - + // aapt resource value: 0x7F030119 public const int spinnerStyle = 2130903321; - + // aapt resource value: 0x7F03011A public const int splitTrack = 2130903322; - + // aapt resource value: 0x7F03011B public const int srcCompat = 2130903323; - + // aapt resource value: 0x7F03011C public const int stackFromEnd = 2130903324; - + // aapt resource value: 0x7F03011D public const int state_above_anchor = 2130903325; - + // aapt resource value: 0x7F03011E public const int state_collapsed = 2130903326; - + // aapt resource value: 0x7F03011F public const int state_collapsible = 2130903327; - + // aapt resource value: 0x7F030120 public const int statusBarBackground = 2130903328; - + // aapt resource value: 0x7F030121 public const int statusBarScrim = 2130903329; - + // aapt resource value: 0x7F030122 public const int subMenuArrow = 2130903330; - + // aapt resource value: 0x7F030123 public const int submitBackground = 2130903331; - + // aapt resource value: 0x7F030124 public const int subtitle = 2130903332; - + // aapt resource value: 0x7F030125 public const int subtitleTextAppearance = 2130903333; - + // aapt resource value: 0x7F030126 public const int subtitleTextColor = 2130903334; - + // aapt resource value: 0x7F030127 public const int subtitleTextStyle = 2130903335; - + // aapt resource value: 0x7F030128 public const int suggestionRowLayout = 2130903336; - + // aapt resource value: 0x7F030129 public const int switchMinWidth = 2130903337; - + // aapt resource value: 0x7F03012A public const int switchPadding = 2130903338; - + // aapt resource value: 0x7F03012B public const int switchStyle = 2130903339; - + // aapt resource value: 0x7F03012C public const int switchTextAppearance = 2130903340; - + // aapt resource value: 0x7F03012D public const int tabBackground = 2130903341; - + // aapt resource value: 0x7F03012E public const int tabContentStart = 2130903342; - + // aapt resource value: 0x7F03012F public const int tabGravity = 2130903343; - + // aapt resource value: 0x7F030130 public const int tabIndicatorColor = 2130903344; - + // aapt resource value: 0x7F030131 public const int tabIndicatorHeight = 2130903345; - + // aapt resource value: 0x7F030132 public const int tabMaxWidth = 2130903346; - + // aapt resource value: 0x7F030133 public const int tabMinWidth = 2130903347; - + // aapt resource value: 0x7F030134 public const int tabMode = 2130903348; - + // aapt resource value: 0x7F030135 public const int tabPadding = 2130903349; - + // aapt resource value: 0x7F030136 public const int tabPaddingBottom = 2130903350; - + // aapt resource value: 0x7F030137 public const int tabPaddingEnd = 2130903351; - + // aapt resource value: 0x7F030138 public const int tabPaddingStart = 2130903352; - + // aapt resource value: 0x7F030139 public const int tabPaddingTop = 2130903353; - + // aapt resource value: 0x7F03013A public const int tabSelectedTextColor = 2130903354; - + // aapt resource value: 0x7F03013B public const int tabTextAppearance = 2130903355; - + // aapt resource value: 0x7F03013C public const int tabTextColor = 2130903356; - + // aapt resource value: 0x7F03013D public const int textAllCaps = 2130903357; - + // aapt resource value: 0x7F03013E public const int textAppearanceLargePopupMenu = 2130903358; - + // aapt resource value: 0x7F03013F public const int textAppearanceListItem = 2130903359; - + // aapt resource value: 0x7F030140 public const int textAppearanceListItemSecondary = 2130903360; - + // aapt resource value: 0x7F030141 public const int textAppearanceListItemSmall = 2130903361; - + // aapt resource value: 0x7F030142 public const int textAppearancePopupMenuHeader = 2130903362; - + // aapt resource value: 0x7F030143 public const int textAppearanceSearchResultSubtitle = 2130903363; - + // aapt resource value: 0x7F030144 public const int textAppearanceSearchResultTitle = 2130903364; - + // aapt resource value: 0x7F030145 public const int textAppearanceSmallPopupMenu = 2130903365; - + // aapt resource value: 0x7F030146 public const int textColorAlertDialogListItem = 2130903366; - + // aapt resource value: 0x7F030147 public const int textColorError = 2130903367; - + // aapt resource value: 0x7F030148 public const int textColorSearchUrl = 2130903368; - + // aapt resource value: 0x7F030149 public const int theme = 2130903369; - + // aapt resource value: 0x7F03014A public const int thickness = 2130903370; - + // aapt resource value: 0x7F03014B public const int thumbTextPadding = 2130903371; - + // aapt resource value: 0x7F03014C public const int thumbTint = 2130903372; - + // aapt resource value: 0x7F03014D public const int thumbTintMode = 2130903373; - + // aapt resource value: 0x7F03014E public const int tickMark = 2130903374; - + // aapt resource value: 0x7F03014F public const int tickMarkTint = 2130903375; - + // aapt resource value: 0x7F030150 public const int tickMarkTintMode = 2130903376; - + // aapt resource value: 0x7F030151 public const int tint = 2130903377; - + // aapt resource value: 0x7F030152 public const int tintMode = 2130903378; - + // aapt resource value: 0x7F030153 public const int title = 2130903379; - + // aapt resource value: 0x7F030154 public const int titleEnabled = 2130903380; - + // aapt resource value: 0x7F030155 public const int titleMargin = 2130903381; - + // aapt resource value: 0x7F030156 public const int titleMarginBottom = 2130903382; - + // aapt resource value: 0x7F030157 public const int titleMarginEnd = 2130903383; - + // aapt resource value: 0x7F03015A public const int titleMargins = 2130903386; - + // aapt resource value: 0x7F030158 public const int titleMarginStart = 2130903384; - + // aapt resource value: 0x7F030159 public const int titleMarginTop = 2130903385; - + // aapt resource value: 0x7F03015B public const int titleTextAppearance = 2130903387; - + // aapt resource value: 0x7F03015C public const int titleTextColor = 2130903388; - + // aapt resource value: 0x7F03015D public const int titleTextStyle = 2130903389; - + // aapt resource value: 0x7F03015E public const int toolbarId = 2130903390; - + // aapt resource value: 0x7F03015F public const int toolbarNavigationButtonStyle = 2130903391; - + // aapt resource value: 0x7F030160 public const int toolbarStyle = 2130903392; - + // aapt resource value: 0x7F030161 public const int tooltipForegroundColor = 2130903393; - + // aapt resource value: 0x7F030162 public const int tooltipFrameBackground = 2130903394; - + // aapt resource value: 0x7F030163 public const int tooltipText = 2130903395; - + // aapt resource value: 0x7F030164 public const int track = 2130903396; - + // aapt resource value: 0x7F030165 public const int trackTint = 2130903397; - + // aapt resource value: 0x7F030166 public const int trackTintMode = 2130903398; - + // aapt resource value: 0x7F030167 public const int useCompatPadding = 2130903399; - + // aapt resource value: 0x7F030168 public const int voiceIcon = 2130903400; - + // aapt resource value: 0x7F030169 public const int windowActionBar = 2130903401; - + // aapt resource value: 0x7F03016A public const int windowActionBarOverlay = 2130903402; - + // aapt resource value: 0x7F03016B public const int windowActionModeOverlay = 2130903403; - + // aapt resource value: 0x7F03016C public const int windowFixedHeightMajor = 2130903404; - + // aapt resource value: 0x7F03016D public const int windowFixedHeightMinor = 2130903405; - + // aapt resource value: 0x7F03016E public const int windowFixedWidthMajor = 2130903406; - + // aapt resource value: 0x7F03016F public const int windowFixedWidthMinor = 2130903407; - + // aapt resource value: 0x7F030170 public const int windowMinWidthMajor = 2130903408; - + // aapt resource value: 0x7F030171 public const int windowMinWidthMinor = 2130903409; - + // aapt resource value: 0x7F030172 public const int windowNoTitle = 2130903410; - + static Attribute() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Attribute() { } } - + public partial class Boolean { - + // aapt resource value: 0x7F040000 public const int abc_action_bar_embed_tabs = 2130968576; - + // aapt resource value: 0x7F040001 public const int abc_allow_stacked_button_bar = 2130968577; - + // aapt resource value: 0x7F040002 public const int abc_config_actionMenuItemAllCaps = 2130968578; - + // aapt resource value: 0x7F040003 public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - + // aapt resource value: 0x7F040004 public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; - + static Boolean() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Boolean() { } } - + public partial class Color { - + // aapt resource value: 0x7F050000 public const int abc_background_cache_hint_selector_material_dark = 2131034112; - + // aapt resource value: 0x7F050001 public const int abc_background_cache_hint_selector_material_light = 2131034113; - + // aapt resource value: 0x7F050002 public const int abc_btn_colored_borderless_text_material = 2131034114; - + // aapt resource value: 0x7F050003 public const int abc_btn_colored_text_material = 2131034115; - + // aapt resource value: 0x7F050004 public const int abc_color_highlight_material = 2131034116; - + // aapt resource value: 0x7F050005 public const int abc_hint_foreground_material_dark = 2131034117; - + // aapt resource value: 0x7F050006 public const int abc_hint_foreground_material_light = 2131034118; - + // aapt resource value: 0x7F050007 public const int abc_input_method_navigation_guard = 2131034119; - + // aapt resource value: 0x7F050008 public const int abc_primary_text_disable_only_material_dark = 2131034120; - + // aapt resource value: 0x7F050009 public const int abc_primary_text_disable_only_material_light = 2131034121; - + // aapt resource value: 0x7F05000A public const int abc_primary_text_material_dark = 2131034122; - + // aapt resource value: 0x7F05000B public const int abc_primary_text_material_light = 2131034123; - + // aapt resource value: 0x7F05000C public const int abc_search_url_text = 2131034124; - + // aapt resource value: 0x7F05000D public const int abc_search_url_text_normal = 2131034125; - + // aapt resource value: 0x7F05000E public const int abc_search_url_text_pressed = 2131034126; - + // aapt resource value: 0x7F05000F public const int abc_search_url_text_selected = 2131034127; - + // aapt resource value: 0x7F050010 public const int abc_secondary_text_material_dark = 2131034128; - + // aapt resource value: 0x7F050011 public const int abc_secondary_text_material_light = 2131034129; - + // aapt resource value: 0x7F050012 public const int abc_tint_btn_checkable = 2131034130; - + // aapt resource value: 0x7F050013 public const int abc_tint_default = 2131034131; - + // aapt resource value: 0x7F050014 public const int abc_tint_edittext = 2131034132; - + // aapt resource value: 0x7F050015 public const int abc_tint_seek_thumb = 2131034133; - + // aapt resource value: 0x7F050016 public const int abc_tint_spinner = 2131034134; - + // aapt resource value: 0x7F050017 public const int abc_tint_switch_track = 2131034135; - + // aapt resource value: 0x7F050018 public const int accent_material_dark = 2131034136; - + // aapt resource value: 0x7F050019 public const int accent_material_light = 2131034137; - + // aapt resource value: 0x7F05001A public const int background_floating_material_dark = 2131034138; - + // aapt resource value: 0x7F05001B public const int background_floating_material_light = 2131034139; - + // aapt resource value: 0x7F05001C public const int background_material_dark = 2131034140; - + // aapt resource value: 0x7F05001D public const int background_material_light = 2131034141; - + // aapt resource value: 0x7F05001E public const int bright_foreground_disabled_material_dark = 2131034142; - + // aapt resource value: 0x7F05001F public const int bright_foreground_disabled_material_light = 2131034143; - + // aapt resource value: 0x7F050020 public const int bright_foreground_inverse_material_dark = 2131034144; - + // aapt resource value: 0x7F050021 public const int bright_foreground_inverse_material_light = 2131034145; - + // aapt resource value: 0x7F050022 public const int bright_foreground_material_dark = 2131034146; - + // aapt resource value: 0x7F050023 public const int bright_foreground_material_light = 2131034147; - + // aapt resource value: 0x7F050024 public const int button_material_dark = 2131034148; - + // aapt resource value: 0x7F050025 public const int button_material_light = 2131034149; - + // aapt resource value: 0x7F050026 public const int cardview_dark_background = 2131034150; - + // aapt resource value: 0x7F050027 public const int cardview_light_background = 2131034151; - + // aapt resource value: 0x7F050028 public const int cardview_shadow_end_color = 2131034152; - + // aapt resource value: 0x7F050029 public const int cardview_shadow_start_color = 2131034153; - + // aapt resource value: 0x7F05002A public const int colorAccent = 2131034154; - + // aapt resource value: 0x7F05002B public const int colorPrimary = 2131034155; - + // aapt resource value: 0x7F05002C public const int colorPrimaryDark = 2131034156; - + // aapt resource value: 0x7F05002D public const int design_bottom_navigation_shadow_color = 2131034157; - + // aapt resource value: 0x7F05002E public const int design_error = 2131034158; - + // aapt resource value: 0x7F05002F public const int design_fab_shadow_end_color = 2131034159; - + // aapt resource value: 0x7F050030 public const int design_fab_shadow_mid_color = 2131034160; - + // aapt resource value: 0x7F050031 public const int design_fab_shadow_start_color = 2131034161; - + // aapt resource value: 0x7F050032 public const int design_fab_stroke_end_inner_color = 2131034162; - + // aapt resource value: 0x7F050033 public const int design_fab_stroke_end_outer_color = 2131034163; - + // aapt resource value: 0x7F050034 public const int design_fab_stroke_top_inner_color = 2131034164; - + // aapt resource value: 0x7F050035 public const int design_fab_stroke_top_outer_color = 2131034165; - + // aapt resource value: 0x7F050036 public const int design_snackbar_background_color = 2131034166; - + // aapt resource value: 0x7F050037 public const int design_tint_password_toggle = 2131034167; - + // aapt resource value: 0x7F050038 public const int dim_foreground_disabled_material_dark = 2131034168; - + // aapt resource value: 0x7F050039 public const int dim_foreground_disabled_material_light = 2131034169; - + // aapt resource value: 0x7F05003A public const int dim_foreground_material_dark = 2131034170; - + // aapt resource value: 0x7F05003B public const int dim_foreground_material_light = 2131034171; - + // aapt resource value: 0x7F05003C public const int error_color_material = 2131034172; - + // aapt resource value: 0x7F05003D public const int foreground_material_dark = 2131034173; - + // aapt resource value: 0x7F05003E public const int foreground_material_light = 2131034174; - + // aapt resource value: 0x7F05003F public const int highlighted_text_material_dark = 2131034175; - + // aapt resource value: 0x7F050040 public const int highlighted_text_material_light = 2131034176; - + // aapt resource value: 0x7F050041 public const int ic_launcher_background = 2131034177; - + // aapt resource value: 0x7F050042 public const int material_blue_grey_800 = 2131034178; - + // aapt resource value: 0x7F050043 public const int material_blue_grey_900 = 2131034179; - + // aapt resource value: 0x7F050044 public const int material_blue_grey_950 = 2131034180; - + // aapt resource value: 0x7F050045 public const int material_deep_teal_200 = 2131034181; - + // aapt resource value: 0x7F050046 public const int material_deep_teal_500 = 2131034182; - + // aapt resource value: 0x7F050047 public const int material_grey_100 = 2131034183; - + // aapt resource value: 0x7F050048 public const int material_grey_300 = 2131034184; - + // aapt resource value: 0x7F050049 public const int material_grey_50 = 2131034185; - + // aapt resource value: 0x7F05004A public const int material_grey_600 = 2131034186; - + // aapt resource value: 0x7F05004B public const int material_grey_800 = 2131034187; - + // aapt resource value: 0x7F05004C public const int material_grey_850 = 2131034188; - + // aapt resource value: 0x7F05004D public const int material_grey_900 = 2131034189; - + // aapt resource value: 0x7F05004E public const int notification_action_color_filter = 2131034190; - + // aapt resource value: 0x7F05004F public const int notification_icon_bg_color = 2131034191; - + // aapt resource value: 0x7F050050 public const int notification_material_background_media_default_color = 2131034192; - + // aapt resource value: 0x7F050051 public const int primary_dark_material_dark = 2131034193; - + // aapt resource value: 0x7F050052 public const int primary_dark_material_light = 2131034194; - + // aapt resource value: 0x7F050053 public const int primary_material_dark = 2131034195; - + // aapt resource value: 0x7F050054 public const int primary_material_light = 2131034196; - + // aapt resource value: 0x7F050055 public const int primary_text_default_material_dark = 2131034197; - + // aapt resource value: 0x7F050056 public const int primary_text_default_material_light = 2131034198; - + // aapt resource value: 0x7F050057 public const int primary_text_disabled_material_dark = 2131034199; - + // aapt resource value: 0x7F050058 public const int primary_text_disabled_material_light = 2131034200; - + // aapt resource value: 0x7F050059 public const int ripple_material_dark = 2131034201; - + // aapt resource value: 0x7F05005A public const int ripple_material_light = 2131034202; - + // aapt resource value: 0x7F05005B public const int secondary_text_default_material_dark = 2131034203; - + // aapt resource value: 0x7F05005C public const int secondary_text_default_material_light = 2131034204; - + // aapt resource value: 0x7F05005D public const int secondary_text_disabled_material_dark = 2131034205; - + // aapt resource value: 0x7F05005E public const int secondary_text_disabled_material_light = 2131034206; - + // aapt resource value: 0x7F05005F public const int switch_thumb_disabled_material_dark = 2131034207; - + // aapt resource value: 0x7F050060 public const int switch_thumb_disabled_material_light = 2131034208; - + // aapt resource value: 0x7F050061 public const int switch_thumb_material_dark = 2131034209; - + // aapt resource value: 0x7F050062 public const int switch_thumb_material_light = 2131034210; - + // aapt resource value: 0x7F050063 public const int switch_thumb_normal_material_dark = 2131034211; - + // aapt resource value: 0x7F050064 public const int switch_thumb_normal_material_light = 2131034212; - + // aapt resource value: 0x7F050065 public const int tooltip_background_dark = 2131034213; - + // aapt resource value: 0x7F050066 public const int tooltip_background_light = 2131034214; - + static Color() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Color() { } } - + public partial class Dimension { - + // aapt resource value: 0x7F060000 public const int abc_action_bar_content_inset_material = 2131099648; - + // aapt resource value: 0x7F060001 public const int abc_action_bar_content_inset_with_nav = 2131099649; - + // aapt resource value: 0x7F060002 public const int abc_action_bar_default_height_material = 2131099650; - + // aapt resource value: 0x7F060003 public const int abc_action_bar_default_padding_end_material = 2131099651; - + // aapt resource value: 0x7F060004 public const int abc_action_bar_default_padding_start_material = 2131099652; - + // aapt resource value: 0x7F060005 public const int abc_action_bar_elevation_material = 2131099653; - + // aapt resource value: 0x7F060006 public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - + // aapt resource value: 0x7F060007 public const int abc_action_bar_overflow_padding_end_material = 2131099655; - + // aapt resource value: 0x7F060008 public const int abc_action_bar_overflow_padding_start_material = 2131099656; - + // aapt resource value: 0x7F060009 public const int abc_action_bar_progress_bar_size = 2131099657; - + // aapt resource value: 0x7F06000A public const int abc_action_bar_stacked_max_height = 2131099658; - + // aapt resource value: 0x7F06000B public const int abc_action_bar_stacked_tab_max_width = 2131099659; - + // aapt resource value: 0x7F06000C public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - + // aapt resource value: 0x7F06000D public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - + // aapt resource value: 0x7F06000E public const int abc_action_button_min_height_material = 2131099662; - + // aapt resource value: 0x7F06000F public const int abc_action_button_min_width_material = 2131099663; - + // aapt resource value: 0x7F060010 public const int abc_action_button_min_width_overflow_material = 2131099664; - + // aapt resource value: 0x7F060011 public const int abc_alert_dialog_button_bar_height = 2131099665; - + // aapt resource value: 0x7F060012 public const int abc_button_inset_horizontal_material = 2131099666; - + // aapt resource value: 0x7F060013 public const int abc_button_inset_vertical_material = 2131099667; - + // aapt resource value: 0x7F060014 public const int abc_button_padding_horizontal_material = 2131099668; - + // aapt resource value: 0x7F060015 public const int abc_button_padding_vertical_material = 2131099669; - + // aapt resource value: 0x7F060016 public const int abc_cascading_menus_min_smallest_width = 2131099670; - + // aapt resource value: 0x7F060017 public const int abc_config_prefDialogWidth = 2131099671; - + // aapt resource value: 0x7F060018 public const int abc_control_corner_material = 2131099672; - + // aapt resource value: 0x7F060019 public const int abc_control_inset_material = 2131099673; - + // aapt resource value: 0x7F06001A public const int abc_control_padding_material = 2131099674; - + // aapt resource value: 0x7F06001B public const int abc_dialog_fixed_height_major = 2131099675; - + // aapt resource value: 0x7F06001C public const int abc_dialog_fixed_height_minor = 2131099676; - + // aapt resource value: 0x7F06001D public const int abc_dialog_fixed_width_major = 2131099677; - + // aapt resource value: 0x7F06001E public const int abc_dialog_fixed_width_minor = 2131099678; - + // aapt resource value: 0x7F06001F public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - + // aapt resource value: 0x7F060020 public const int abc_dialog_list_padding_top_no_title = 2131099680; - + // aapt resource value: 0x7F060021 public const int abc_dialog_min_width_major = 2131099681; - + // aapt resource value: 0x7F060022 public const int abc_dialog_min_width_minor = 2131099682; - + // aapt resource value: 0x7F060023 public const int abc_dialog_padding_material = 2131099683; - + // aapt resource value: 0x7F060024 public const int abc_dialog_padding_top_material = 2131099684; - + // aapt resource value: 0x7F060025 public const int abc_dialog_title_divider_material = 2131099685; - + // aapt resource value: 0x7F060026 public const int abc_disabled_alpha_material_dark = 2131099686; - + // aapt resource value: 0x7F060027 public const int abc_disabled_alpha_material_light = 2131099687; - + // aapt resource value: 0x7F060028 public const int abc_dropdownitem_icon_width = 2131099688; - + // aapt resource value: 0x7F060029 public const int abc_dropdownitem_text_padding_left = 2131099689; - + // aapt resource value: 0x7F06002A public const int abc_dropdownitem_text_padding_right = 2131099690; - + // aapt resource value: 0x7F06002B public const int abc_edit_text_inset_bottom_material = 2131099691; - + // aapt resource value: 0x7F06002C public const int abc_edit_text_inset_horizontal_material = 2131099692; - + // aapt resource value: 0x7F06002D public const int abc_edit_text_inset_top_material = 2131099693; - + // aapt resource value: 0x7F06002E public const int abc_floating_window_z = 2131099694; - + // aapt resource value: 0x7F06002F public const int abc_list_item_padding_horizontal_material = 2131099695; - + // aapt resource value: 0x7F060030 public const int abc_panel_menu_list_width = 2131099696; - + // aapt resource value: 0x7F060031 public const int abc_progress_bar_height_material = 2131099697; - + // aapt resource value: 0x7F060032 public const int abc_search_view_preferred_height = 2131099698; - + // aapt resource value: 0x7F060033 public const int abc_search_view_preferred_width = 2131099699; - + // aapt resource value: 0x7F060034 public const int abc_seekbar_track_background_height_material = 2131099700; - + // aapt resource value: 0x7F060035 public const int abc_seekbar_track_progress_height_material = 2131099701; - + // aapt resource value: 0x7F060036 public const int abc_select_dialog_padding_start_material = 2131099702; - + // aapt resource value: 0x7F060037 public const int abc_switch_padding = 2131099703; - + // aapt resource value: 0x7F060038 public const int abc_text_size_body_1_material = 2131099704; - + // aapt resource value: 0x7F060039 public const int abc_text_size_body_2_material = 2131099705; - + // aapt resource value: 0x7F06003A public const int abc_text_size_button_material = 2131099706; - + // aapt resource value: 0x7F06003B public const int abc_text_size_caption_material = 2131099707; - + // aapt resource value: 0x7F06003C public const int abc_text_size_display_1_material = 2131099708; - + // aapt resource value: 0x7F06003D public const int abc_text_size_display_2_material = 2131099709; - + // aapt resource value: 0x7F06003E public const int abc_text_size_display_3_material = 2131099710; - + // aapt resource value: 0x7F06003F public const int abc_text_size_display_4_material = 2131099711; - + // aapt resource value: 0x7F060040 public const int abc_text_size_headline_material = 2131099712; - + // aapt resource value: 0x7F060041 public const int abc_text_size_large_material = 2131099713; - + // aapt resource value: 0x7F060042 public const int abc_text_size_medium_material = 2131099714; - + // aapt resource value: 0x7F060043 public const int abc_text_size_menu_header_material = 2131099715; - + // aapt resource value: 0x7F060044 public const int abc_text_size_menu_material = 2131099716; - + // aapt resource value: 0x7F060045 public const int abc_text_size_small_material = 2131099717; - + // aapt resource value: 0x7F060046 public const int abc_text_size_subhead_material = 2131099718; - + // aapt resource value: 0x7F060047 public const int abc_text_size_subtitle_material_toolbar = 2131099719; - + // aapt resource value: 0x7F060048 public const int abc_text_size_title_material = 2131099720; - + // aapt resource value: 0x7F060049 public const int abc_text_size_title_material_toolbar = 2131099721; - + // aapt resource value: 0x7F06004A public const int cardview_compat_inset_shadow = 2131099722; - + // aapt resource value: 0x7F06004B public const int cardview_default_elevation = 2131099723; - + // aapt resource value: 0x7F06004C public const int cardview_default_radius = 2131099724; - + // aapt resource value: 0x7F06004D public const int compat_button_inset_horizontal_material = 2131099725; - + // aapt resource value: 0x7F06004E public const int compat_button_inset_vertical_material = 2131099726; - + // aapt resource value: 0x7F06004F public const int compat_button_padding_horizontal_material = 2131099727; - + // aapt resource value: 0x7F060050 public const int compat_button_padding_vertical_material = 2131099728; - + // aapt resource value: 0x7F060051 public const int compat_control_corner_material = 2131099729; - + // aapt resource value: 0x7F060052 public const int design_appbar_elevation = 2131099730; - + // aapt resource value: 0x7F060053 public const int design_bottom_navigation_active_item_max_width = 2131099731; - + // aapt resource value: 0x7F060054 public const int design_bottom_navigation_active_text_size = 2131099732; - + // aapt resource value: 0x7F060055 public const int design_bottom_navigation_elevation = 2131099733; - + // aapt resource value: 0x7F060056 public const int design_bottom_navigation_height = 2131099734; - + // aapt resource value: 0x7F060057 public const int design_bottom_navigation_item_max_width = 2131099735; - + // aapt resource value: 0x7F060058 public const int design_bottom_navigation_item_min_width = 2131099736; - + // aapt resource value: 0x7F060059 public const int design_bottom_navigation_margin = 2131099737; - + // aapt resource value: 0x7F06005A public const int design_bottom_navigation_shadow_height = 2131099738; - + // aapt resource value: 0x7F06005B public const int design_bottom_navigation_text_size = 2131099739; - + // aapt resource value: 0x7F06005C public const int design_bottom_sheet_modal_elevation = 2131099740; - + // aapt resource value: 0x7F06005D public const int design_bottom_sheet_peek_height_min = 2131099741; - + // aapt resource value: 0x7F06005E public const int design_fab_border_width = 2131099742; - + // aapt resource value: 0x7F06005F public const int design_fab_elevation = 2131099743; - + // aapt resource value: 0x7F060060 public const int design_fab_image_size = 2131099744; - + // aapt resource value: 0x7F060061 public const int design_fab_size_mini = 2131099745; - + // aapt resource value: 0x7F060062 public const int design_fab_size_normal = 2131099746; - + // aapt resource value: 0x7F060063 public const int design_fab_translation_z_pressed = 2131099747; - + // aapt resource value: 0x7F060064 public const int design_navigation_elevation = 2131099748; - + // aapt resource value: 0x7F060065 public const int design_navigation_icon_padding = 2131099749; - + // aapt resource value: 0x7F060066 public const int design_navigation_icon_size = 2131099750; - + // aapt resource value: 0x7F060067 public const int design_navigation_max_width = 2131099751; - + // aapt resource value: 0x7F060068 public const int design_navigation_padding_bottom = 2131099752; - + // aapt resource value: 0x7F060069 public const int design_navigation_separator_vertical_padding = 2131099753; - + // aapt resource value: 0x7F06006A public const int design_snackbar_action_inline_max_width = 2131099754; - + // aapt resource value: 0x7F06006B public const int design_snackbar_background_corner_radius = 2131099755; - + // aapt resource value: 0x7F06006C public const int design_snackbar_elevation = 2131099756; - + // aapt resource value: 0x7F06006D public const int design_snackbar_extra_spacing_horizontal = 2131099757; - + // aapt resource value: 0x7F06006E public const int design_snackbar_max_width = 2131099758; - + // aapt resource value: 0x7F06006F public const int design_snackbar_min_width = 2131099759; - + // aapt resource value: 0x7F060070 public const int design_snackbar_padding_horizontal = 2131099760; - + // aapt resource value: 0x7F060071 public const int design_snackbar_padding_vertical = 2131099761; - + // aapt resource value: 0x7F060072 public const int design_snackbar_padding_vertical_2lines = 2131099762; - + // aapt resource value: 0x7F060073 public const int design_snackbar_text_size = 2131099763; - + // aapt resource value: 0x7F060074 public const int design_tab_max_width = 2131099764; - + // aapt resource value: 0x7F060075 public const int design_tab_scrollable_min_width = 2131099765; - + // aapt resource value: 0x7F060076 public const int design_tab_text_size = 2131099766; - + // aapt resource value: 0x7F060077 public const int design_tab_text_size_2line = 2131099767; - + // aapt resource value: 0x7F060078 public const int disabled_alpha_material_dark = 2131099768; - + // aapt resource value: 0x7F060079 public const int disabled_alpha_material_light = 2131099769; - + // aapt resource value: 0x7F06007A public const int fastscroll_default_thickness = 2131099770; - + // aapt resource value: 0x7F06007B public const int fastscroll_margin = 2131099771; - + // aapt resource value: 0x7F06007C public const int fastscroll_minimum_range = 2131099772; - + // aapt resource value: 0x7F06007D public const int highlight_alpha_material_colored = 2131099773; - + // aapt resource value: 0x7F06007E public const int highlight_alpha_material_dark = 2131099774; - + // aapt resource value: 0x7F06007F public const int highlight_alpha_material_light = 2131099775; - + // aapt resource value: 0x7F060080 public const int hint_alpha_material_dark = 2131099776; - + // aapt resource value: 0x7F060081 public const int hint_alpha_material_light = 2131099777; - + // aapt resource value: 0x7F060082 public const int hint_pressed_alpha_material_dark = 2131099778; - + // aapt resource value: 0x7F060083 public const int hint_pressed_alpha_material_light = 2131099779; - + // aapt resource value: 0x7F060084 public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - + // aapt resource value: 0x7F060085 public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - + // aapt resource value: 0x7F060086 public const int item_touch_helper_swipe_escape_velocity = 2131099782; - + // aapt resource value: 0x7F060087 public const int mr_controller_volume_group_list_item_height = 2131099783; - + // aapt resource value: 0x7F060088 public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - + // aapt resource value: 0x7F060089 public const int mr_controller_volume_group_list_max_height = 2131099785; - + // aapt resource value: 0x7F06008A public const int mr_controller_volume_group_list_padding_top = 2131099786; - + // aapt resource value: 0x7F06008B public const int mr_dialog_fixed_width_major = 2131099787; - + // aapt resource value: 0x7F06008C public const int mr_dialog_fixed_width_minor = 2131099788; - + // aapt resource value: 0x7F06008D public const int notification_action_icon_size = 2131099789; - + // aapt resource value: 0x7F06008E public const int notification_action_text_size = 2131099790; - + // aapt resource value: 0x7F06008F public const int notification_big_circle_margin = 2131099791; - + // aapt resource value: 0x7F060090 public const int notification_content_margin_start = 2131099792; - + // aapt resource value: 0x7F060091 public const int notification_large_icon_height = 2131099793; - + // aapt resource value: 0x7F060092 public const int notification_large_icon_width = 2131099794; - + // aapt resource value: 0x7F060093 public const int notification_main_column_padding_top = 2131099795; - + // aapt resource value: 0x7F060094 public const int notification_media_narrow_margin = 2131099796; - + // aapt resource value: 0x7F060095 public const int notification_right_icon_size = 2131099797; - + // aapt resource value: 0x7F060096 public const int notification_right_side_padding_top = 2131099798; - + // aapt resource value: 0x7F060097 public const int notification_small_icon_background_padding = 2131099799; - + // aapt resource value: 0x7F060098 public const int notification_small_icon_size_as_large = 2131099800; - + // aapt resource value: 0x7F060099 public const int notification_subtext_size = 2131099801; - + // aapt resource value: 0x7F06009A public const int notification_top_pad = 2131099802; - + // aapt resource value: 0x7F06009B public const int notification_top_pad_large_text = 2131099803; - + // aapt resource value: 0x7F06009C public const int tooltip_corner_radius = 2131099804; - + // aapt resource value: 0x7F06009D public const int tooltip_horizontal_padding = 2131099805; - + // aapt resource value: 0x7F06009E public const int tooltip_margin = 2131099806; - + // aapt resource value: 0x7F06009F public const int tooltip_precise_anchor_extra_offset = 2131099807; - + // aapt resource value: 0x7F0600A0 public const int tooltip_precise_anchor_threshold = 2131099808; - + // aapt resource value: 0x7F0600A1 public const int tooltip_vertical_padding = 2131099809; - + // aapt resource value: 0x7F0600A2 public const int tooltip_y_offset_non_touch = 2131099810; - + // aapt resource value: 0x7F0600A3 public const int tooltip_y_offset_touch = 2131099811; - + static Dimension() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Dimension() { } } - + public partial class Drawable { - + // aapt resource value: 0x7F070006 public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - + // aapt resource value: 0x7F070007 public const int abc_action_bar_item_background_material = 2131165191; - + // aapt resource value: 0x7F070008 public const int abc_btn_borderless_material = 2131165192; - + // aapt resource value: 0x7F070009 public const int abc_btn_check_material = 2131165193; - + // aapt resource value: 0x7F07000A public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - + // aapt resource value: 0x7F07000B public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - + // aapt resource value: 0x7F07000C public const int abc_btn_colored_material = 2131165196; - + // aapt resource value: 0x7F07000D public const int abc_btn_default_mtrl_shape = 2131165197; - + // aapt resource value: 0x7F07000E public const int abc_btn_radio_material = 2131165198; - + // aapt resource value: 0x7F07000F public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - + // aapt resource value: 0x7F070010 public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - + // aapt resource value: 0x7F070011 public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - + // aapt resource value: 0x7F070012 public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - + // aapt resource value: 0x7F070013 public const int abc_cab_background_internal_bg = 2131165203; - + // aapt resource value: 0x7F070014 public const int abc_cab_background_top_material = 2131165204; - + // aapt resource value: 0x7F070015 public const int abc_cab_background_top_mtrl_alpha = 2131165205; - + // aapt resource value: 0x7F070016 public const int abc_control_background_material = 2131165206; - + // aapt resource value: 0x7F070017 public const int abc_dialog_material_background = 2131165207; - + // aapt resource value: 0x7F070018 public const int abc_edit_text_material = 2131165208; - + // aapt resource value: 0x7F070019 public const int abc_ic_ab_back_material = 2131165209; - + // aapt resource value: 0x7F07001A public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - + // aapt resource value: 0x7F07001B public const int abc_ic_clear_material = 2131165211; - + // aapt resource value: 0x7F07001C public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - + // aapt resource value: 0x7F07001D public const int abc_ic_go_search_api_material = 2131165213; - + // aapt resource value: 0x7F07001E public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - + // aapt resource value: 0x7F07001F public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - + // aapt resource value: 0x7F070020 public const int abc_ic_menu_overflow_material = 2131165216; - + // aapt resource value: 0x7F070021 public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - + // aapt resource value: 0x7F070022 public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - + // aapt resource value: 0x7F070023 public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - + // aapt resource value: 0x7F070024 public const int abc_ic_search_api_material = 2131165220; - + // aapt resource value: 0x7F070025 public const int abc_ic_star_black_16dp = 2131165221; - + // aapt resource value: 0x7F070026 public const int abc_ic_star_black_36dp = 2131165222; - + // aapt resource value: 0x7F070027 public const int abc_ic_star_black_48dp = 2131165223; - + // aapt resource value: 0x7F070028 public const int abc_ic_star_half_black_16dp = 2131165224; - + // aapt resource value: 0x7F070029 public const int abc_ic_star_half_black_36dp = 2131165225; - + // aapt resource value: 0x7F07002A public const int abc_ic_star_half_black_48dp = 2131165226; - + // aapt resource value: 0x7F07002B public const int abc_ic_voice_search_api_material = 2131165227; - + // aapt resource value: 0x7F07002C public const int abc_item_background_holo_dark = 2131165228; - + // aapt resource value: 0x7F07002D public const int abc_item_background_holo_light = 2131165229; - + // aapt resource value: 0x7F07002E public const int abc_list_divider_mtrl_alpha = 2131165230; - + // aapt resource value: 0x7F07002F public const int abc_list_focused_holo = 2131165231; - + // aapt resource value: 0x7F070030 public const int abc_list_longpressed_holo = 2131165232; - + // aapt resource value: 0x7F070031 public const int abc_list_pressed_holo_dark = 2131165233; - + // aapt resource value: 0x7F070032 public const int abc_list_pressed_holo_light = 2131165234; - + // aapt resource value: 0x7F070033 public const int abc_list_selector_background_transition_holo_dark = 2131165235; - + // aapt resource value: 0x7F070034 public const int abc_list_selector_background_transition_holo_light = 2131165236; - + // aapt resource value: 0x7F070035 public const int abc_list_selector_disabled_holo_dark = 2131165237; - + // aapt resource value: 0x7F070036 public const int abc_list_selector_disabled_holo_light = 2131165238; - + // aapt resource value: 0x7F070037 public const int abc_list_selector_holo_dark = 2131165239; - + // aapt resource value: 0x7F070038 public const int abc_list_selector_holo_light = 2131165240; - + // aapt resource value: 0x7F070039 public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - + // aapt resource value: 0x7F07003A public const int abc_popup_background_mtrl_mult = 2131165242; - + // aapt resource value: 0x7F07003B public const int abc_ratingbar_indicator_material = 2131165243; - + // aapt resource value: 0x7F07003C public const int abc_ratingbar_material = 2131165244; - + // aapt resource value: 0x7F07003D public const int abc_ratingbar_small_material = 2131165245; - + // aapt resource value: 0x7F07003E public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - + // aapt resource value: 0x7F07003F public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - + // aapt resource value: 0x7F070040 public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - + // aapt resource value: 0x7F070041 public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - + // aapt resource value: 0x7F070042 public const int abc_scrubber_track_mtrl_alpha = 2131165250; - + // aapt resource value: 0x7F070043 public const int abc_seekbar_thumb_material = 2131165251; - + // aapt resource value: 0x7F070044 public const int abc_seekbar_tick_mark_material = 2131165252; - + // aapt resource value: 0x7F070045 public const int abc_seekbar_track_material = 2131165253; - + // aapt resource value: 0x7F070046 public const int abc_spinner_mtrl_am_alpha = 2131165254; - + // aapt resource value: 0x7F070047 public const int abc_spinner_textfield_background_material = 2131165255; - + // aapt resource value: 0x7F070048 public const int abc_switch_thumb_material = 2131165256; - + // aapt resource value: 0x7F070049 public const int abc_switch_track_mtrl_alpha = 2131165257; - + // aapt resource value: 0x7F07004A public const int abc_tab_indicator_material = 2131165258; - + // aapt resource value: 0x7F07004B public const int abc_tab_indicator_mtrl_alpha = 2131165259; - + // aapt resource value: 0x7F070053 public const int abc_textfield_activated_mtrl_alpha = 2131165267; - + // aapt resource value: 0x7F070054 public const int abc_textfield_default_mtrl_alpha = 2131165268; - + // aapt resource value: 0x7F070055 public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - + // aapt resource value: 0x7F070056 public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - + // aapt resource value: 0x7F070057 public const int abc_textfield_search_material = 2131165271; - + // aapt resource value: 0x7F07004C public const int abc_text_cursor_material = 2131165260; - + // aapt resource value: 0x7F07004D public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - + // aapt resource value: 0x7F07004E public const int abc_text_select_handle_left_mtrl_light = 2131165262; - + // aapt resource value: 0x7F07004F public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - + // aapt resource value: 0x7F070050 public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - + // aapt resource value: 0x7F070051 public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - + // aapt resource value: 0x7F070052 public const int abc_text_select_handle_right_mtrl_light = 2131165266; - + // aapt resource value: 0x7F070058 public const int abc_vector_test = 2131165272; - + // aapt resource value: 0x7F070059 public const int avd_hide_password = 2131165273; - + // aapt resource value: 0x7F07005A public const int avd_show_password = 2131165274; - + // aapt resource value: 0x7F07005B public const int design_bottom_navigation_item_background = 2131165275; - + // aapt resource value: 0x7F07005C public const int design_fab_background = 2131165276; - + // aapt resource value: 0x7F07005D public const int design_ic_visibility = 2131165277; - + // aapt resource value: 0x7F07005E public const int design_ic_visibility_off = 2131165278; - + // aapt resource value: 0x7F07005F public const int design_password_eye = 2131165279; - + // aapt resource value: 0x7F070060 public const int design_snackbar_background = 2131165280; - + // aapt resource value: 0x7F070061 public const int ic_audiotrack_dark = 2131165281; - + // aapt resource value: 0x7F070062 public const int ic_audiotrack_light = 2131165282; - + // aapt resource value: 0x7F070063 public const int ic_dialog_close_dark = 2131165283; - + // aapt resource value: 0x7F070064 public const int ic_dialog_close_light = 2131165284; - + // aapt resource value: 0x7F070065 public const int ic_group_collapse_00 = 2131165285; - + // aapt resource value: 0x7F070066 public const int ic_group_collapse_01 = 2131165286; - + // aapt resource value: 0x7F070067 public const int ic_group_collapse_02 = 2131165287; - + // aapt resource value: 0x7F070068 public const int ic_group_collapse_03 = 2131165288; - + // aapt resource value: 0x7F070069 public const int ic_group_collapse_04 = 2131165289; - + // aapt resource value: 0x7F07006A public const int ic_group_collapse_05 = 2131165290; - + // aapt resource value: 0x7F07006B public const int ic_group_collapse_06 = 2131165291; - + // aapt resource value: 0x7F07006C public const int ic_group_collapse_07 = 2131165292; - + // aapt resource value: 0x7F07006D public const int ic_group_collapse_08 = 2131165293; - + // aapt resource value: 0x7F07006E public const int ic_group_collapse_09 = 2131165294; - + // aapt resource value: 0x7F07006F public const int ic_group_collapse_10 = 2131165295; - + // aapt resource value: 0x7F070070 public const int ic_group_collapse_11 = 2131165296; - + // aapt resource value: 0x7F070071 public const int ic_group_collapse_12 = 2131165297; - + // aapt resource value: 0x7F070072 public const int ic_group_collapse_13 = 2131165298; - + // aapt resource value: 0x7F070073 public const int ic_group_collapse_14 = 2131165299; - + // aapt resource value: 0x7F070074 public const int ic_group_collapse_15 = 2131165300; - + // aapt resource value: 0x7F070075 public const int ic_group_expand_00 = 2131165301; - + // aapt resource value: 0x7F070076 public const int ic_group_expand_01 = 2131165302; - + // aapt resource value: 0x7F070077 public const int ic_group_expand_02 = 2131165303; - + // aapt resource value: 0x7F070078 public const int ic_group_expand_03 = 2131165304; - + // aapt resource value: 0x7F070079 public const int ic_group_expand_04 = 2131165305; - + // aapt resource value: 0x7F07007A public const int ic_group_expand_05 = 2131165306; - + // aapt resource value: 0x7F07007B public const int ic_group_expand_06 = 2131165307; - + // aapt resource value: 0x7F07007C public const int ic_group_expand_07 = 2131165308; - + // aapt resource value: 0x7F07007D public const int ic_group_expand_08 = 2131165309; - + // aapt resource value: 0x7F07007E public const int ic_group_expand_09 = 2131165310; - + // aapt resource value: 0x7F07007F public const int ic_group_expand_10 = 2131165311; - + // aapt resource value: 0x7F070080 public const int ic_group_expand_11 = 2131165312; - + // aapt resource value: 0x7F070081 public const int ic_group_expand_12 = 2131165313; - + // aapt resource value: 0x7F070082 public const int ic_group_expand_13 = 2131165314; - + // aapt resource value: 0x7F070083 public const int ic_group_expand_14 = 2131165315; - + // aapt resource value: 0x7F070084 public const int ic_group_expand_15 = 2131165316; - + // aapt resource value: 0x7F070085 public const int ic_media_pause_dark = 2131165317; - + // aapt resource value: 0x7F070086 public const int ic_media_pause_light = 2131165318; - + // aapt resource value: 0x7F070087 public const int ic_media_play_dark = 2131165319; - + // aapt resource value: 0x7F070088 public const int ic_media_play_light = 2131165320; - + // aapt resource value: 0x7F070089 public const int ic_media_stop_dark = 2131165321; - + // aapt resource value: 0x7F07008A public const int ic_media_stop_light = 2131165322; - + // aapt resource value: 0x7F07008B public const int ic_mr_button_connected_00_dark = 2131165323; - + // aapt resource value: 0x7F07008C public const int ic_mr_button_connected_00_light = 2131165324; - + // aapt resource value: 0x7F07008D public const int ic_mr_button_connected_01_dark = 2131165325; - + // aapt resource value: 0x7F07008E public const int ic_mr_button_connected_01_light = 2131165326; - + // aapt resource value: 0x7F07008F public const int ic_mr_button_connected_02_dark = 2131165327; - + // aapt resource value: 0x7F070090 public const int ic_mr_button_connected_02_light = 2131165328; - + // aapt resource value: 0x7F070091 public const int ic_mr_button_connected_03_dark = 2131165329; - + // aapt resource value: 0x7F070092 public const int ic_mr_button_connected_03_light = 2131165330; - + // aapt resource value: 0x7F070093 public const int ic_mr_button_connected_04_dark = 2131165331; - + // aapt resource value: 0x7F070094 public const int ic_mr_button_connected_04_light = 2131165332; - + // aapt resource value: 0x7F070095 public const int ic_mr_button_connected_05_dark = 2131165333; - + // aapt resource value: 0x7F070096 public const int ic_mr_button_connected_05_light = 2131165334; - + // aapt resource value: 0x7F070097 public const int ic_mr_button_connected_06_dark = 2131165335; - + // aapt resource value: 0x7F070098 public const int ic_mr_button_connected_06_light = 2131165336; - + // aapt resource value: 0x7F070099 public const int ic_mr_button_connected_07_dark = 2131165337; - + // aapt resource value: 0x7F07009A public const int ic_mr_button_connected_07_light = 2131165338; - + // aapt resource value: 0x7F07009B public const int ic_mr_button_connected_08_dark = 2131165339; - + // aapt resource value: 0x7F07009C public const int ic_mr_button_connected_08_light = 2131165340; - + // aapt resource value: 0x7F07009D public const int ic_mr_button_connected_09_dark = 2131165341; - + // aapt resource value: 0x7F07009E public const int ic_mr_button_connected_09_light = 2131165342; - + // aapt resource value: 0x7F07009F public const int ic_mr_button_connected_10_dark = 2131165343; - + // aapt resource value: 0x7F0700A0 public const int ic_mr_button_connected_10_light = 2131165344; - + // aapt resource value: 0x7F0700A1 public const int ic_mr_button_connected_11_dark = 2131165345; - + // aapt resource value: 0x7F0700A2 public const int ic_mr_button_connected_11_light = 2131165346; - + // aapt resource value: 0x7F0700A3 public const int ic_mr_button_connected_12_dark = 2131165347; - + // aapt resource value: 0x7F0700A4 public const int ic_mr_button_connected_12_light = 2131165348; - + // aapt resource value: 0x7F0700A5 public const int ic_mr_button_connected_13_dark = 2131165349; - + // aapt resource value: 0x7F0700A6 public const int ic_mr_button_connected_13_light = 2131165350; - + // aapt resource value: 0x7F0700A7 public const int ic_mr_button_connected_14_dark = 2131165351; - + // aapt resource value: 0x7F0700A8 public const int ic_mr_button_connected_14_light = 2131165352; - + // aapt resource value: 0x7F0700A9 public const int ic_mr_button_connected_15_dark = 2131165353; - + // aapt resource value: 0x7F0700AA public const int ic_mr_button_connected_15_light = 2131165354; - + // aapt resource value: 0x7F0700AB public const int ic_mr_button_connected_16_dark = 2131165355; - + // aapt resource value: 0x7F0700AC public const int ic_mr_button_connected_16_light = 2131165356; - + // aapt resource value: 0x7F0700AD public const int ic_mr_button_connected_17_dark = 2131165357; - + // aapt resource value: 0x7F0700AE public const int ic_mr_button_connected_17_light = 2131165358; - + // aapt resource value: 0x7F0700AF public const int ic_mr_button_connected_18_dark = 2131165359; - + // aapt resource value: 0x7F0700B0 public const int ic_mr_button_connected_18_light = 2131165360; - + // aapt resource value: 0x7F0700B1 public const int ic_mr_button_connected_19_dark = 2131165361; - + // aapt resource value: 0x7F0700B2 public const int ic_mr_button_connected_19_light = 2131165362; - + // aapt resource value: 0x7F0700B3 public const int ic_mr_button_connected_20_dark = 2131165363; - + // aapt resource value: 0x7F0700B4 public const int ic_mr_button_connected_20_light = 2131165364; - + // aapt resource value: 0x7F0700B5 public const int ic_mr_button_connected_21_dark = 2131165365; - + // aapt resource value: 0x7F0700B6 public const int ic_mr_button_connected_21_light = 2131165366; - + // aapt resource value: 0x7F0700B7 public const int ic_mr_button_connected_22_dark = 2131165367; - + // aapt resource value: 0x7F0700B8 public const int ic_mr_button_connected_22_light = 2131165368; - + // aapt resource value: 0x7F0700B9 public const int ic_mr_button_connected_23_dark = 2131165369; - + // aapt resource value: 0x7F0700BA public const int ic_mr_button_connected_23_light = 2131165370; - + // aapt resource value: 0x7F0700BB public const int ic_mr_button_connected_24_dark = 2131165371; - + // aapt resource value: 0x7F0700BC public const int ic_mr_button_connected_24_light = 2131165372; - + // aapt resource value: 0x7F0700BD public const int ic_mr_button_connected_25_dark = 2131165373; - + // aapt resource value: 0x7F0700BE public const int ic_mr_button_connected_25_light = 2131165374; - + // aapt resource value: 0x7F0700BF public const int ic_mr_button_connected_26_dark = 2131165375; - + // aapt resource value: 0x7F0700C0 public const int ic_mr_button_connected_26_light = 2131165376; - + // aapt resource value: 0x7F0700C1 public const int ic_mr_button_connected_27_dark = 2131165377; - + // aapt resource value: 0x7F0700C2 public const int ic_mr_button_connected_27_light = 2131165378; - + // aapt resource value: 0x7F0700C3 public const int ic_mr_button_connected_28_dark = 2131165379; - + // aapt resource value: 0x7F0700C4 public const int ic_mr_button_connected_28_light = 2131165380; - + // aapt resource value: 0x7F0700C5 public const int ic_mr_button_connected_29_dark = 2131165381; - + // aapt resource value: 0x7F0700C6 public const int ic_mr_button_connected_29_light = 2131165382; - + // aapt resource value: 0x7F0700C7 public const int ic_mr_button_connected_30_dark = 2131165383; - + // aapt resource value: 0x7F0700C8 public const int ic_mr_button_connected_30_light = 2131165384; - + // aapt resource value: 0x7F0700C9 public const int ic_mr_button_connecting_00_dark = 2131165385; - + // aapt resource value: 0x7F0700CA public const int ic_mr_button_connecting_00_light = 2131165386; - + // aapt resource value: 0x7F0700CB public const int ic_mr_button_connecting_01_dark = 2131165387; - + // aapt resource value: 0x7F0700CC public const int ic_mr_button_connecting_01_light = 2131165388; - + // aapt resource value: 0x7F0700CD public const int ic_mr_button_connecting_02_dark = 2131165389; - + // aapt resource value: 0x7F0700CE public const int ic_mr_button_connecting_02_light = 2131165390; - + // aapt resource value: 0x7F0700CF public const int ic_mr_button_connecting_03_dark = 2131165391; - + // aapt resource value: 0x7F0700D0 public const int ic_mr_button_connecting_03_light = 2131165392; - + // aapt resource value: 0x7F0700D1 public const int ic_mr_button_connecting_04_dark = 2131165393; - + // aapt resource value: 0x7F0700D2 public const int ic_mr_button_connecting_04_light = 2131165394; - + // aapt resource value: 0x7F0700D3 public const int ic_mr_button_connecting_05_dark = 2131165395; - + // aapt resource value: 0x7F0700D4 public const int ic_mr_button_connecting_05_light = 2131165396; - + // aapt resource value: 0x7F0700D5 public const int ic_mr_button_connecting_06_dark = 2131165397; - + // aapt resource value: 0x7F0700D6 public const int ic_mr_button_connecting_06_light = 2131165398; - + // aapt resource value: 0x7F0700D7 public const int ic_mr_button_connecting_07_dark = 2131165399; - + // aapt resource value: 0x7F0700D8 public const int ic_mr_button_connecting_07_light = 2131165400; - + // aapt resource value: 0x7F0700D9 public const int ic_mr_button_connecting_08_dark = 2131165401; - + // aapt resource value: 0x7F0700DA public const int ic_mr_button_connecting_08_light = 2131165402; - + // aapt resource value: 0x7F0700DB public const int ic_mr_button_connecting_09_dark = 2131165403; - + // aapt resource value: 0x7F0700DC public const int ic_mr_button_connecting_09_light = 2131165404; - + // aapt resource value: 0x7F0700DD public const int ic_mr_button_connecting_10_dark = 2131165405; - + // aapt resource value: 0x7F0700DE public const int ic_mr_button_connecting_10_light = 2131165406; - + // aapt resource value: 0x7F0700DF public const int ic_mr_button_connecting_11_dark = 2131165407; - + // aapt resource value: 0x7F0700E0 public const int ic_mr_button_connecting_11_light = 2131165408; - + // aapt resource value: 0x7F0700E1 public const int ic_mr_button_connecting_12_dark = 2131165409; - + // aapt resource value: 0x7F0700E2 public const int ic_mr_button_connecting_12_light = 2131165410; - + // aapt resource value: 0x7F0700E3 public const int ic_mr_button_connecting_13_dark = 2131165411; - + // aapt resource value: 0x7F0700E4 public const int ic_mr_button_connecting_13_light = 2131165412; - + // aapt resource value: 0x7F0700E5 public const int ic_mr_button_connecting_14_dark = 2131165413; - + // aapt resource value: 0x7F0700E6 public const int ic_mr_button_connecting_14_light = 2131165414; - + // aapt resource value: 0x7F0700E7 public const int ic_mr_button_connecting_15_dark = 2131165415; - + // aapt resource value: 0x7F0700E8 public const int ic_mr_button_connecting_15_light = 2131165416; - + // aapt resource value: 0x7F0700E9 public const int ic_mr_button_connecting_16_dark = 2131165417; - + // aapt resource value: 0x7F0700EA public const int ic_mr_button_connecting_16_light = 2131165418; - + // aapt resource value: 0x7F0700EB public const int ic_mr_button_connecting_17_dark = 2131165419; - + // aapt resource value: 0x7F0700EC public const int ic_mr_button_connecting_17_light = 2131165420; - + // aapt resource value: 0x7F0700ED public const int ic_mr_button_connecting_18_dark = 2131165421; - + // aapt resource value: 0x7F0700EE public const int ic_mr_button_connecting_18_light = 2131165422; - + // aapt resource value: 0x7F0700EF public const int ic_mr_button_connecting_19_dark = 2131165423; - + // aapt resource value: 0x7F0700F0 public const int ic_mr_button_connecting_19_light = 2131165424; - + // aapt resource value: 0x7F0700F1 public const int ic_mr_button_connecting_20_dark = 2131165425; - + // aapt resource value: 0x7F0700F2 public const int ic_mr_button_connecting_20_light = 2131165426; - + // aapt resource value: 0x7F0700F3 public const int ic_mr_button_connecting_21_dark = 2131165427; - + // aapt resource value: 0x7F0700F4 public const int ic_mr_button_connecting_21_light = 2131165428; - + // aapt resource value: 0x7F0700F5 public const int ic_mr_button_connecting_22_dark = 2131165429; - + // aapt resource value: 0x7F0700F6 public const int ic_mr_button_connecting_22_light = 2131165430; - + // aapt resource value: 0x7F0700F7 public const int ic_mr_button_connecting_23_dark = 2131165431; - + // aapt resource value: 0x7F0700F8 public const int ic_mr_button_connecting_23_light = 2131165432; - + // aapt resource value: 0x7F0700F9 public const int ic_mr_button_connecting_24_dark = 2131165433; - + // aapt resource value: 0x7F0700FA public const int ic_mr_button_connecting_24_light = 2131165434; - + // aapt resource value: 0x7F0700FB public const int ic_mr_button_connecting_25_dark = 2131165435; - + // aapt resource value: 0x7F0700FC public const int ic_mr_button_connecting_25_light = 2131165436; - + // aapt resource value: 0x7F0700FD public const int ic_mr_button_connecting_26_dark = 2131165437; - + // aapt resource value: 0x7F0700FE public const int ic_mr_button_connecting_26_light = 2131165438; - + // aapt resource value: 0x7F0700FF public const int ic_mr_button_connecting_27_dark = 2131165439; - + // aapt resource value: 0x7F070100 public const int ic_mr_button_connecting_27_light = 2131165440; - + // aapt resource value: 0x7F070101 public const int ic_mr_button_connecting_28_dark = 2131165441; - + // aapt resource value: 0x7F070102 public const int ic_mr_button_connecting_28_light = 2131165442; - + // aapt resource value: 0x7F070103 public const int ic_mr_button_connecting_29_dark = 2131165443; - + // aapt resource value: 0x7F070104 public const int ic_mr_button_connecting_29_light = 2131165444; - + // aapt resource value: 0x7F070105 public const int ic_mr_button_connecting_30_dark = 2131165445; - + // aapt resource value: 0x7F070106 public const int ic_mr_button_connecting_30_light = 2131165446; - + // aapt resource value: 0x7F070107 public const int ic_mr_button_disabled_dark = 2131165447; - + // aapt resource value: 0x7F070108 public const int ic_mr_button_disabled_light = 2131165448; - + // aapt resource value: 0x7F070109 public const int ic_mr_button_disconnected_dark = 2131165449; - + // aapt resource value: 0x7F07010A public const int ic_mr_button_disconnected_light = 2131165450; - + // aapt resource value: 0x7F07010B public const int ic_mr_button_grey = 2131165451; - + // aapt resource value: 0x7F07010C public const int ic_vol_type_speaker_dark = 2131165452; - + // aapt resource value: 0x7F07010D public const int ic_vol_type_speaker_group_dark = 2131165453; - + // aapt resource value: 0x7F07010E public const int ic_vol_type_speaker_group_light = 2131165454; - + // aapt resource value: 0x7F07010F public const int ic_vol_type_speaker_light = 2131165455; - + // aapt resource value: 0x7F070110 public const int ic_vol_type_tv_dark = 2131165456; - + // aapt resource value: 0x7F070111 public const int ic_vol_type_tv_light = 2131165457; - + // aapt resource value: 0x7F070112 public const int mr_button_connected_dark = 2131165458; - + // aapt resource value: 0x7F070113 public const int mr_button_connected_light = 2131165459; - + // aapt resource value: 0x7F070114 public const int mr_button_connecting_dark = 2131165460; - + // aapt resource value: 0x7F070115 public const int mr_button_connecting_light = 2131165461; - + // aapt resource value: 0x7F070116 public const int mr_button_dark = 2131165462; - + // aapt resource value: 0x7F070117 public const int mr_button_light = 2131165463; - + // aapt resource value: 0x7F070118 public const int mr_dialog_close_dark = 2131165464; - + // aapt resource value: 0x7F070119 public const int mr_dialog_close_light = 2131165465; - + // aapt resource value: 0x7F07011A public const int mr_dialog_material_background_dark = 2131165466; - + // aapt resource value: 0x7F07011B public const int mr_dialog_material_background_light = 2131165467; - + // aapt resource value: 0x7F07011C public const int mr_group_collapse = 2131165468; - + // aapt resource value: 0x7F07011D public const int mr_group_expand = 2131165469; - + // aapt resource value: 0x7F07011E public const int mr_media_pause_dark = 2131165470; - + // aapt resource value: 0x7F07011F public const int mr_media_pause_light = 2131165471; - + // aapt resource value: 0x7F070120 public const int mr_media_play_dark = 2131165472; - + // aapt resource value: 0x7F070121 public const int mr_media_play_light = 2131165473; - + // aapt resource value: 0x7F070122 public const int mr_media_stop_dark = 2131165474; - + // aapt resource value: 0x7F070123 public const int mr_media_stop_light = 2131165475; - + // aapt resource value: 0x7F070124 public const int mr_vol_type_audiotrack_dark = 2131165476; - + // aapt resource value: 0x7F070125 public const int mr_vol_type_audiotrack_light = 2131165477; - + // aapt resource value: 0x7F070126 public const int navigation_empty_icon = 2131165478; - + // aapt resource value: 0x7F070127 public const int notification_action_background = 2131165479; - + // aapt resource value: 0x7F070128 public const int notification_bg = 2131165480; - + // aapt resource value: 0x7F070129 public const int notification_bg_low = 2131165481; - + // aapt resource value: 0x7F07012A public const int notification_bg_low_normal = 2131165482; - + // aapt resource value: 0x7F07012B public const int notification_bg_low_pressed = 2131165483; - + // aapt resource value: 0x7F07012C public const int notification_bg_normal = 2131165484; - + // aapt resource value: 0x7F07012D public const int notification_bg_normal_pressed = 2131165485; - + // aapt resource value: 0x7F07012E public const int notification_icon_background = 2131165486; - + // aapt resource value: 0x7F07012F public const int notification_template_icon_bg = 2131165487; - + // aapt resource value: 0x7F070130 public const int notification_template_icon_low_bg = 2131165488; - + // aapt resource value: 0x7F070131 public const int notification_tile_bg = 2131165489; - + // aapt resource value: 0x7F070132 public const int notify_panel_notification_icon_bg = 2131165490; - + // aapt resource value: 0x7F070133 public const int tooltip_frame_dark = 2131165491; - + // aapt resource value: 0x7F070134 public const int tooltip_frame_light = 2131165492; - + static Drawable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Drawable() { } } - + public partial class Id { - + // aapt resource value: 0x7F080006 public const int action0 = 2131230726; - + // aapt resource value: 0x7F080018 public const int actions = 2131230744; - + // aapt resource value: 0x7F080007 public const int action_bar = 2131230727; - + // aapt resource value: 0x7F080008 public const int action_bar_activity_content = 2131230728; - + // aapt resource value: 0x7F080009 public const int action_bar_container = 2131230729; - + // aapt resource value: 0x7F08000A public const int action_bar_root = 2131230730; - + // aapt resource value: 0x7F08000B public const int action_bar_spinner = 2131230731; - + // aapt resource value: 0x7F08000C public const int action_bar_subtitle = 2131230732; - + // aapt resource value: 0x7F08000D public const int action_bar_title = 2131230733; - + // aapt resource value: 0x7F08000E public const int action_container = 2131230734; - + // aapt resource value: 0x7F08000F public const int action_context_bar = 2131230735; - + // aapt resource value: 0x7F080010 public const int action_divider = 2131230736; - + // aapt resource value: 0x7F080011 public const int action_image = 2131230737; - + // aapt resource value: 0x7F080012 public const int action_menu_divider = 2131230738; - + // aapt resource value: 0x7F080013 public const int action_menu_presenter = 2131230739; - + // aapt resource value: 0x7F080014 public const int action_mode_bar = 2131230740; - + // aapt resource value: 0x7F080015 public const int action_mode_bar_stub = 2131230741; - + // aapt resource value: 0x7F080016 public const int action_mode_close_button = 2131230742; - + // aapt resource value: 0x7F080017 public const int action_text = 2131230743; - + // aapt resource value: 0x7F080019 public const int activity_chooser_view_content = 2131230745; - + // aapt resource value: 0x7F08001A public const int add = 2131230746; - + // aapt resource value: 0x7F08001B public const int alertTitle = 2131230747; - + // aapt resource value: 0x7F08001C public const int all = 2131230748; - + // aapt resource value: 0x7F080000 public const int ALT = 2131230720; - + // aapt resource value: 0x7F08001D public const int always = 2131230749; - + // aapt resource value: 0x7F08001E public const int async = 2131230750; - + // aapt resource value: 0x7F08001F public const int auto = 2131230751; - + // aapt resource value: 0x7F080020 public const int beginning = 2131230752; - + // aapt resource value: 0x7F080021 public const int blocking = 2131230753; - + // aapt resource value: 0x7F080022 public const int bottom = 2131230754; - + // aapt resource value: 0x7F080023 public const int bottomtab_navarea = 2131230755; - + // aapt resource value: 0x7F080024 public const int bottomtab_tabbar = 2131230756; - + // aapt resource value: 0x7F080025 public const int buttonPanel = 2131230757; - + // aapt resource value: 0x7F080026 public const int cancel_action = 2131230758; - + // aapt resource value: 0x7F080027 public const int center = 2131230759; - + // aapt resource value: 0x7F080028 public const int center_horizontal = 2131230760; - + // aapt resource value: 0x7F080029 public const int center_vertical = 2131230761; - + // aapt resource value: 0x7F08002A public const int checkbox = 2131230762; - + // aapt resource value: 0x7F08002B public const int chronometer = 2131230763; - + // aapt resource value: 0x7F08002C public const int clip_horizontal = 2131230764; - + // aapt resource value: 0x7F08002D public const int clip_vertical = 2131230765; - + // aapt resource value: 0x7F08002E public const int collapseActionView = 2131230766; - + // aapt resource value: 0x7F08002F public const int container = 2131230767; - + // aapt resource value: 0x7F080030 public const int contentPanel = 2131230768; - + // aapt resource value: 0x7F080031 public const int coordinator = 2131230769; - + // aapt resource value: 0x7F080001 public const int CTRL = 2131230721; - + // aapt resource value: 0x7F080032 public const int custom = 2131230770; - + // aapt resource value: 0x7F080033 public const int customPanel = 2131230771; - + // aapt resource value: 0x7F080034 public const int decor_content_parent = 2131230772; - + // aapt resource value: 0x7F080035 public const int default_activity_button = 2131230773; - + // aapt resource value: 0x7F080036 public const int design_bottom_sheet = 2131230774; - + // aapt resource value: 0x7F080037 public const int design_menu_item_action_area = 2131230775; - + // aapt resource value: 0x7F080038 public const int design_menu_item_action_area_stub = 2131230776; - + // aapt resource value: 0x7F080039 public const int design_menu_item_text = 2131230777; - + // aapt resource value: 0x7F08003A public const int design_navigation_view = 2131230778; - + // aapt resource value: 0x7F08003B public const int disableHome = 2131230779; - + // aapt resource value: 0x7F08003C public const int edit_query = 2131230780; - + // aapt resource value: 0x7F08003D public const int end = 2131230781; - + // aapt resource value: 0x7F08003E public const int end_padder = 2131230782; - + // aapt resource value: 0x7F08003F public const int enterAlways = 2131230783; - + // aapt resource value: 0x7F080040 public const int enterAlwaysCollapsed = 2131230784; - + // aapt resource value: 0x7F080041 public const int exitUntilCollapsed = 2131230785; - + // aapt resource value: 0x7F080043 public const int expanded_menu = 2131230787; - + // aapt resource value: 0x7F080042 public const int expand_activities_button = 2131230786; - + // aapt resource value: 0x7F080044 public const int fill = 2131230788; - + // aapt resource value: 0x7F080045 public const int fill_horizontal = 2131230789; - + // aapt resource value: 0x7F080046 public const int fill_vertical = 2131230790; - + // aapt resource value: 0x7F080047 public const int @fixed = 2131230791; - + // aapt resource value: 0x7F080048 public const int flyoutcontent_appbar = 2131230792; - + // aapt resource value: 0x7F080049 public const int flyoutcontent_recycler = 2131230793; - + // aapt resource value: 0x7F08004A public const int forever = 2131230794; - + // aapt resource value: 0x7F080002 public const int FUNCTION = 2131230722; - + // aapt resource value: 0x7F08004B public const int ghost_view = 2131230795; - + // aapt resource value: 0x7F08004C public const int home = 2131230796; - + // aapt resource value: 0x7F08004D public const int homeAsUp = 2131230797; - + // aapt resource value: 0x7F08004E public const int icon = 2131230798; - + // aapt resource value: 0x7F08004F public const int icon_group = 2131230799; - + // aapt resource value: 0x7F080050 public const int ifRoom = 2131230800; - + // aapt resource value: 0x7F080051 public const int image = 2131230801; - + // aapt resource value: 0x7F080052 public const int info = 2131230802; - + // aapt resource value: 0x7F080053 public const int italic = 2131230803; - + // aapt resource value: 0x7F080054 public const int item_touch_helper_previous_elevation = 2131230804; - + // aapt resource value: 0x7F080055 public const int largeLabel = 2131230805; - + // aapt resource value: 0x7F080056 public const int left = 2131230806; - + // aapt resource value: 0x7F080057 public const int line1 = 2131230807; - + // aapt resource value: 0x7F080058 public const int line3 = 2131230808; - + // aapt resource value: 0x7F080059 public const int listMode = 2131230809; - + // aapt resource value: 0x7F08005A public const int list_item = 2131230810; - + // aapt resource value: 0x7F08005B public const int main_appbar = 2131230811; - + // aapt resource value: 0x7F08005C public const int main_scrollview = 2131230812; - + // aapt resource value: 0x7F08005D public const int main_tablayout = 2131230813; - + // aapt resource value: 0x7F08005E public const int main_toolbar = 2131230814; - + // aapt resource value: 0x7F08005F public const int masked = 2131230815; - + // aapt resource value: 0x7F080060 public const int media_actions = 2131230816; - + // aapt resource value: 0x7F080061 public const int message = 2131230817; - + // aapt resource value: 0x7F080003 public const int META = 2131230723; - + // aapt resource value: 0x7F080062 public const int middle = 2131230818; - + // aapt resource value: 0x7F080063 public const int mini = 2131230819; - + // aapt resource value: 0x7F080064 public const int mr_art = 2131230820; - + // aapt resource value: 0x7F080065 public const int mr_chooser_list = 2131230821; - + // aapt resource value: 0x7F080066 public const int mr_chooser_route_desc = 2131230822; - + // aapt resource value: 0x7F080067 public const int mr_chooser_route_icon = 2131230823; - + // aapt resource value: 0x7F080068 public const int mr_chooser_route_name = 2131230824; - + // aapt resource value: 0x7F080069 public const int mr_chooser_title = 2131230825; - + // aapt resource value: 0x7F08006A public const int mr_close = 2131230826; - + // aapt resource value: 0x7F08006B public const int mr_control_divider = 2131230827; - + // aapt resource value: 0x7F08006C public const int mr_control_playback_ctrl = 2131230828; - + // aapt resource value: 0x7F08006D public const int mr_control_subtitle = 2131230829; - + // aapt resource value: 0x7F08006E public const int mr_control_title = 2131230830; - + // aapt resource value: 0x7F08006F public const int mr_control_title_container = 2131230831; - + // aapt resource value: 0x7F080070 public const int mr_custom_control = 2131230832; - + // aapt resource value: 0x7F080071 public const int mr_default_control = 2131230833; - + // aapt resource value: 0x7F080072 public const int mr_dialog_area = 2131230834; - + // aapt resource value: 0x7F080073 public const int mr_expandable_area = 2131230835; - + // aapt resource value: 0x7F080074 public const int mr_group_expand_collapse = 2131230836; - + // aapt resource value: 0x7F080075 public const int mr_media_main_control = 2131230837; - + // aapt resource value: 0x7F080076 public const int mr_name = 2131230838; - + // aapt resource value: 0x7F080077 public const int mr_playback_control = 2131230839; - + // aapt resource value: 0x7F080078 public const int mr_title_bar = 2131230840; - + // aapt resource value: 0x7F080079 public const int mr_volume_control = 2131230841; - + // aapt resource value: 0x7F08007A public const int mr_volume_group_list = 2131230842; - + // aapt resource value: 0x7F08007B public const int mr_volume_item_icon = 2131230843; - + // aapt resource value: 0x7F08007C public const int mr_volume_slider = 2131230844; - + // aapt resource value: 0x7F08007D public const int multiply = 2131230845; - + // aapt resource value: 0x7F08007E public const int navigation_header_container = 2131230846; - + // aapt resource value: 0x7F08007F public const int never = 2131230847; - + // aapt resource value: 0x7F080080 public const int none = 2131230848; - + // aapt resource value: 0x7F080081 public const int normal = 2131230849; - + // aapt resource value: 0x7F080082 public const int notification_background = 2131230850; - + // aapt resource value: 0x7F080083 public const int notification_main_column = 2131230851; - + // aapt resource value: 0x7F080084 public const int notification_main_column_container = 2131230852; - + // aapt resource value: 0x7F080085 public const int parallax = 2131230853; - + // aapt resource value: 0x7F080086 public const int parentPanel = 2131230854; - + // aapt resource value: 0x7F080087 public const int parent_matrix = 2131230855; - + // aapt resource value: 0x7F080088 public const int pin = 2131230856; - + // aapt resource value: 0x7F080089 public const int progress_circular = 2131230857; - + // aapt resource value: 0x7F08008A public const int progress_horizontal = 2131230858; - + // aapt resource value: 0x7F08008B public const int radio = 2131230859; - + // aapt resource value: 0x7F08008C public const int right = 2131230860; - + // aapt resource value: 0x7F08008D public const int right_icon = 2131230861; - + // aapt resource value: 0x7F08008E public const int right_side = 2131230862; - + // aapt resource value: 0x7F08008F public const int save_image_matrix = 2131230863; - + // aapt resource value: 0x7F080090 public const int save_non_transition_alpha = 2131230864; - + // aapt resource value: 0x7F080091 public const int save_scale_type = 2131230865; - + // aapt resource value: 0x7F080092 public const int screen = 2131230866; - + // aapt resource value: 0x7F080093 public const int scroll = 2131230867; - + // aapt resource value: 0x7F080097 public const int scrollable = 2131230871; - + // aapt resource value: 0x7F080094 public const int scrollIndicatorDown = 2131230868; - + // aapt resource value: 0x7F080095 public const int scrollIndicatorUp = 2131230869; - + // aapt resource value: 0x7F080096 public const int scrollView = 2131230870; - + // aapt resource value: 0x7F080098 public const int search_badge = 2131230872; - + // aapt resource value: 0x7F080099 public const int search_bar = 2131230873; - + // aapt resource value: 0x7F08009A public const int search_button = 2131230874; - + // aapt resource value: 0x7F08009B public const int search_close_btn = 2131230875; - + // aapt resource value: 0x7F08009C public const int search_edit_frame = 2131230876; - + // aapt resource value: 0x7F08009D public const int search_go_btn = 2131230877; - + // aapt resource value: 0x7F08009E public const int search_mag_icon = 2131230878; - + // aapt resource value: 0x7F08009F public const int search_plate = 2131230879; - + // aapt resource value: 0x7F0800A0 public const int search_src_text = 2131230880; - + // aapt resource value: 0x7F0800A1 public const int search_voice_btn = 2131230881; - + // aapt resource value: 0x7F0800A2 public const int select_dialog_listview = 2131230882; - + // aapt resource value: 0x7F0800A3 public const int shellcontent_appbar = 2131230883; - + // aapt resource value: 0x7F0800A4 public const int shellcontent_scrollview = 2131230884; - + // aapt resource value: 0x7F0800A5 public const int shellcontent_toolbar = 2131230885; - + // aapt resource value: 0x7F080004 public const int SHIFT = 2131230724; - + // aapt resource value: 0x7F0800A6 public const int shortcut = 2131230886; - + // aapt resource value: 0x7F0800A7 public const int showCustom = 2131230887; - + // aapt resource value: 0x7F0800A8 public const int showHome = 2131230888; - + // aapt resource value: 0x7F0800A9 public const int showTitle = 2131230889; - + // aapt resource value: 0x7F0800AA public const int smallLabel = 2131230890; - + // aapt resource value: 0x7F0800AB public const int snackbar_action = 2131230891; - + // aapt resource value: 0x7F0800AC public const int snackbar_text = 2131230892; - + // aapt resource value: 0x7F0800AD public const int snap = 2131230893; - + // aapt resource value: 0x7F0800AE public const int spacer = 2131230894; - + // aapt resource value: 0x7F0800AF public const int split_action_bar = 2131230895; - + // aapt resource value: 0x7F0800B0 public const int src_atop = 2131230896; - + // aapt resource value: 0x7F0800B1 public const int src_in = 2131230897; - + // aapt resource value: 0x7F0800B2 public const int src_over = 2131230898; - + // aapt resource value: 0x7F0800B3 public const int start = 2131230899; - + // aapt resource value: 0x7F0800B4 public const int status_bar_latest_event_content = 2131230900; - + // aapt resource value: 0x7F0800B5 public const int submenuarrow = 2131230901; - + // aapt resource value: 0x7F0800B6 public const int submit_area = 2131230902; - + // aapt resource value: 0x7F080005 public const int SYM = 2131230725; - + // aapt resource value: 0x7F0800B7 public const int tabMode = 2131230903; - + // aapt resource value: 0x7F0800B8 public const int tag_transition_group = 2131230904; - + // aapt resource value: 0x7F0800B9 public const int text = 2131230905; - + // aapt resource value: 0x7F0800BA public const int text2 = 2131230906; - + // aapt resource value: 0x7F0800BE public const int textinput_counter = 2131230910; - + // aapt resource value: 0x7F0800BF public const int textinput_error = 2131230911; - + // aapt resource value: 0x7F0800BB public const int textSpacerNoButtons = 2131230907; - + // aapt resource value: 0x7F0800BC public const int textSpacerNoTitle = 2131230908; - + // aapt resource value: 0x7F0800BD public const int text_input_password_toggle = 2131230909; - + // aapt resource value: 0x7F0800C0 public const int time = 2131230912; - + // aapt resource value: 0x7F0800C1 public const int title = 2131230913; - + // aapt resource value: 0x7F0800C2 public const int titleDividerNoCustom = 2131230914; - + // aapt resource value: 0x7F0800C3 public const int title_template = 2131230915; - + // aapt resource value: 0x7F0800C4 public const int top = 2131230916; - + // aapt resource value: 0x7F0800C5 public const int topPanel = 2131230917; - + // aapt resource value: 0x7F0800C6 public const int touch_outside = 2131230918; - + // aapt resource value: 0x7F0800C7 public const int transition_current_scene = 2131230919; - + // aapt resource value: 0x7F0800C8 public const int transition_layout_save = 2131230920; - + // aapt resource value: 0x7F0800C9 public const int transition_position = 2131230921; - + // aapt resource value: 0x7F0800CA public const int transition_scene_layoutid_cache = 2131230922; - + // aapt resource value: 0x7F0800CB public const int transition_transform = 2131230923; - + // aapt resource value: 0x7F0800CC public const int uniform = 2131230924; - + // aapt resource value: 0x7F0800CD public const int up = 2131230925; - + // aapt resource value: 0x7F0800CE public const int useLogo = 2131230926; - + // aapt resource value: 0x7F0800CF public const int view_offset_helper = 2131230927; - + // aapt resource value: 0x7F0800D0 public const int visible = 2131230928; - + // aapt resource value: 0x7F0800D1 public const int volume_item_container = 2131230929; - + // aapt resource value: 0x7F0800D2 public const int withText = 2131230930; - + // aapt resource value: 0x7F0800D3 public const int wrap_content = 2131230931; - + static Id() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Id() { } } - + public partial class Integer { - + // aapt resource value: 0x7F090000 public const int abc_config_activityDefaultDur = 2131296256; - + // aapt resource value: 0x7F090001 public const int abc_config_activityShortDur = 2131296257; - + // aapt resource value: 0x7F090002 public const int app_bar_elevation_anim_duration = 2131296258; - + // aapt resource value: 0x7F090003 public const int bottom_sheet_slide_duration = 2131296259; - + // aapt resource value: 0x7F090004 public const int cancel_button_image_alpha = 2131296260; - + // aapt resource value: 0x7F090005 public const int config_tooltipAnimTime = 2131296261; - + // aapt resource value: 0x7F090006 public const int design_snackbar_text_max_lines = 2131296262; - + // aapt resource value: 0x7F090007 public const int hide_password_duration = 2131296263; - + // aapt resource value: 0x7F090008 public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - + // aapt resource value: 0x7F090009 public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - + // aapt resource value: 0x7F09000A public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - + // aapt resource value: 0x7F09000B public const int show_password_duration = 2131296267; - + // aapt resource value: 0x7F09000C public const int status_bar_notification_info_maxnum = 2131296268; - + static Integer() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Integer() { } } - + public partial class Interpolator { - + // aapt resource value: 0x7F0A0000 public const int mr_fast_out_slow_in = 2131361792; - + // aapt resource value: 0x7F0A0001 public const int mr_linear_out_slow_in = 2131361793; - + static Interpolator() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Interpolator() { } } - + public partial class Layout { - + // aapt resource value: 0x7F0B0000 public const int abc_action_bar_title_item = 2131427328; - + // aapt resource value: 0x7F0B0001 public const int abc_action_bar_up_container = 2131427329; - + // aapt resource value: 0x7F0B0002 public const int abc_action_menu_item_layout = 2131427330; - + // aapt resource value: 0x7F0B0003 public const int abc_action_menu_layout = 2131427331; - + // aapt resource value: 0x7F0B0004 public const int abc_action_mode_bar = 2131427332; - + // aapt resource value: 0x7F0B0005 public const int abc_action_mode_close_item_material = 2131427333; - + // aapt resource value: 0x7F0B0006 public const int abc_activity_chooser_view = 2131427334; - + // aapt resource value: 0x7F0B0007 public const int abc_activity_chooser_view_list_item = 2131427335; - + // aapt resource value: 0x7F0B0008 public const int abc_alert_dialog_button_bar_material = 2131427336; - + // aapt resource value: 0x7F0B0009 public const int abc_alert_dialog_material = 2131427337; - + // aapt resource value: 0x7F0B000A public const int abc_alert_dialog_title_material = 2131427338; - + // aapt resource value: 0x7F0B000B public const int abc_dialog_title_material = 2131427339; - + // aapt resource value: 0x7F0B000C public const int abc_expanded_menu_layout = 2131427340; - + // aapt resource value: 0x7F0B000D public const int abc_list_menu_item_checkbox = 2131427341; - + // aapt resource value: 0x7F0B000E public const int abc_list_menu_item_icon = 2131427342; - + // aapt resource value: 0x7F0B000F public const int abc_list_menu_item_layout = 2131427343; - + // aapt resource value: 0x7F0B0010 public const int abc_list_menu_item_radio = 2131427344; - + // aapt resource value: 0x7F0B0011 public const int abc_popup_menu_header_item_layout = 2131427345; - + // aapt resource value: 0x7F0B0012 public const int abc_popup_menu_item_layout = 2131427346; - + // aapt resource value: 0x7F0B0013 public const int abc_screen_content_include = 2131427347; - + // aapt resource value: 0x7F0B0014 public const int abc_screen_simple = 2131427348; - + // aapt resource value: 0x7F0B0015 public const int abc_screen_simple_overlay_action_mode = 2131427349; - + // aapt resource value: 0x7F0B0016 public const int abc_screen_toolbar = 2131427350; - + // aapt resource value: 0x7F0B0017 public const int abc_search_dropdown_item_icons_2line = 2131427351; - + // aapt resource value: 0x7F0B0018 public const int abc_search_view = 2131427352; - + // aapt resource value: 0x7F0B0019 public const int abc_select_dialog_material = 2131427353; - + // aapt resource value: 0x7F0B001A public const int activity_main = 2131427354; - + // aapt resource value: 0x7F0B001B public const int BottomTabLayout = 2131427355; - + // aapt resource value: 0x7F0B001C public const int design_bottom_navigation_item = 2131427356; - + // aapt resource value: 0x7F0B001D public const int design_bottom_sheet_dialog = 2131427357; - + // aapt resource value: 0x7F0B001E public const int design_layout_snackbar = 2131427358; - + // aapt resource value: 0x7F0B001F public const int design_layout_snackbar_include = 2131427359; - + // aapt resource value: 0x7F0B0020 public const int design_layout_tab_icon = 2131427360; - + // aapt resource value: 0x7F0B0021 public const int design_layout_tab_text = 2131427361; - + // aapt resource value: 0x7F0B0022 public const int design_menu_item_action_area = 2131427362; - + // aapt resource value: 0x7F0B0023 public const int design_navigation_item = 2131427363; - + // aapt resource value: 0x7F0B0024 public const int design_navigation_item_header = 2131427364; - + // aapt resource value: 0x7F0B0025 public const int design_navigation_item_separator = 2131427365; - + // aapt resource value: 0x7F0B0026 public const int design_navigation_item_subheader = 2131427366; - + // aapt resource value: 0x7F0B0027 public const int design_navigation_menu = 2131427367; - + // aapt resource value: 0x7F0B0028 public const int design_navigation_menu_item = 2131427368; - + // aapt resource value: 0x7F0B0029 public const int design_text_input_password_icon = 2131427369; - + // aapt resource value: 0x7F0B002A public const int FlyoutContent = 2131427370; - + // aapt resource value: 0x7F0B002B public const int mr_chooser_dialog = 2131427371; - + // aapt resource value: 0x7F0B002C public const int mr_chooser_list_item = 2131427372; - + // aapt resource value: 0x7F0B002D public const int mr_controller_material_dialog_b = 2131427373; - + // aapt resource value: 0x7F0B002E public const int mr_controller_volume_item = 2131427374; - + // aapt resource value: 0x7F0B002F public const int mr_playback_control = 2131427375; - + // aapt resource value: 0x7F0B0030 public const int mr_volume_control = 2131427376; - + // aapt resource value: 0x7F0B0031 public const int notification_action = 2131427377; - + // aapt resource value: 0x7F0B0032 public const int notification_action_tombstone = 2131427378; - + // aapt resource value: 0x7F0B0033 public const int notification_media_action = 2131427379; - + // aapt resource value: 0x7F0B0034 public const int notification_media_cancel_action = 2131427380; - + // aapt resource value: 0x7F0B0035 public const int notification_template_big_media = 2131427381; - + // aapt resource value: 0x7F0B0036 public const int notification_template_big_media_custom = 2131427382; - + // aapt resource value: 0x7F0B0037 public const int notification_template_big_media_narrow = 2131427383; - + // aapt resource value: 0x7F0B0038 public const int notification_template_big_media_narrow_custom = 2131427384; - + // aapt resource value: 0x7F0B0039 public const int notification_template_custom_big = 2131427385; - + // aapt resource value: 0x7F0B003A public const int notification_template_icon_group = 2131427386; - + // aapt resource value: 0x7F0B003B public const int notification_template_lines_media = 2131427387; - + // aapt resource value: 0x7F0B003C public const int notification_template_media = 2131427388; - + // aapt resource value: 0x7F0B003D public const int notification_template_media_custom = 2131427389; - + // aapt resource value: 0x7F0B003E public const int notification_template_part_chronometer = 2131427390; - + // aapt resource value: 0x7F0B003F public const int notification_template_part_time = 2131427391; - + // aapt resource value: 0x7F0B0040 public const int RootLayout = 2131427392; - + // aapt resource value: 0x7F0B0041 public const int select_dialog_item_material = 2131427393; - + // aapt resource value: 0x7F0B0042 public const int select_dialog_multichoice_material = 2131427394; - + // aapt resource value: 0x7F0B0043 public const int select_dialog_singlechoice_material = 2131427395; - + // aapt resource value: 0x7F0B0044 public const int ShellContent = 2131427396; - + // aapt resource value: 0x7F0B0045 public const int support_simple_spinner_dropdown_item = 2131427397; - + // aapt resource value: 0x7F0B0046 public const int tooltip = 2131427398; - + static Layout() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Layout() { } } - + public partial class Mipmap { - + // aapt resource value: 0x7F0C0000 public const int ic_launcher = 2131492864; - + // aapt resource value: 0x7F0C0001 public const int ic_launcher_foreground = 2131492865; - + // aapt resource value: 0x7F0C0002 public const int ic_launcher_round = 2131492866; - + static Mipmap() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Mipmap() { } } - + public partial class String { - + // aapt resource value: 0x7F0D0000 public const int abc_action_bar_home_description = 2131558400; - + // aapt resource value: 0x7F0D0001 public const int abc_action_bar_up_description = 2131558401; - + // aapt resource value: 0x7F0D0002 public const int abc_action_menu_overflow_description = 2131558402; - + // aapt resource value: 0x7F0D0003 public const int abc_action_mode_done = 2131558403; - + // aapt resource value: 0x7F0D0005 public const int abc_activitychooserview_choose_application = 2131558405; - + // aapt resource value: 0x7F0D0004 public const int abc_activity_chooser_view_see_all = 2131558404; - + // aapt resource value: 0x7F0D0006 public const int abc_capital_off = 2131558406; - + // aapt resource value: 0x7F0D0007 public const int abc_capital_on = 2131558407; - + // aapt resource value: 0x7F0D0008 public const int abc_font_family_body_1_material = 2131558408; - + // aapt resource value: 0x7F0D0009 public const int abc_font_family_body_2_material = 2131558409; - + // aapt resource value: 0x7F0D000A public const int abc_font_family_button_material = 2131558410; - + // aapt resource value: 0x7F0D000B public const int abc_font_family_caption_material = 2131558411; - + // aapt resource value: 0x7F0D000C public const int abc_font_family_display_1_material = 2131558412; - + // aapt resource value: 0x7F0D000D public const int abc_font_family_display_2_material = 2131558413; - + // aapt resource value: 0x7F0D000E public const int abc_font_family_display_3_material = 2131558414; - + // aapt resource value: 0x7F0D000F public const int abc_font_family_display_4_material = 2131558415; - + // aapt resource value: 0x7F0D0010 public const int abc_font_family_headline_material = 2131558416; - + // aapt resource value: 0x7F0D0011 public const int abc_font_family_menu_material = 2131558417; - + // aapt resource value: 0x7F0D0012 public const int abc_font_family_subhead_material = 2131558418; - + // aapt resource value: 0x7F0D0013 public const int abc_font_family_title_material = 2131558419; - + // aapt resource value: 0x7F0D0015 public const int abc_searchview_description_clear = 2131558421; - + // aapt resource value: 0x7F0D0016 public const int abc_searchview_description_query = 2131558422; - + // aapt resource value: 0x7F0D0017 public const int abc_searchview_description_search = 2131558423; - + // aapt resource value: 0x7F0D0018 public const int abc_searchview_description_submit = 2131558424; - + // aapt resource value: 0x7F0D0019 public const int abc_searchview_description_voice = 2131558425; - + // aapt resource value: 0x7F0D0014 public const int abc_search_hint = 2131558420; - + // aapt resource value: 0x7F0D001A public const int abc_shareactionprovider_share_with = 2131558426; - + // aapt resource value: 0x7F0D001B public const int abc_shareactionprovider_share_with_application = 2131558427; - + // aapt resource value: 0x7F0D001C public const int abc_toolbar_collapse_description = 2131558428; - + // aapt resource value: 0x7F0D001D public const int action_settings = 2131558429; - + // aapt resource value: 0x7F0D001F public const int appbar_scrolling_view_behavior = 2131558431; - + // aapt resource value: 0x7F0D001E public const int app_name = 2131558430; - + // aapt resource value: 0x7F0D0020 public const int bottom_sheet_behavior = 2131558432; - + // aapt resource value: 0x7F0D0021 public const int character_counter_pattern = 2131558433; - + // aapt resource value: 0x7F0D0022 public const int mr_button_content_description = 2131558434; - + // aapt resource value: 0x7F0D0023 public const int mr_cast_button_connected = 2131558435; - + // aapt resource value: 0x7F0D0024 public const int mr_cast_button_connecting = 2131558436; - + // aapt resource value: 0x7F0D0025 public const int mr_cast_button_disconnected = 2131558437; - + // aapt resource value: 0x7F0D0026 public const int mr_chooser_searching = 2131558438; - + // aapt resource value: 0x7F0D0027 public const int mr_chooser_title = 2131558439; - + // aapt resource value: 0x7F0D0028 public const int mr_controller_album_art = 2131558440; - + // aapt resource value: 0x7F0D0029 public const int mr_controller_casting_screen = 2131558441; - + // aapt resource value: 0x7F0D002A public const int mr_controller_close_description = 2131558442; - + // aapt resource value: 0x7F0D002B public const int mr_controller_collapse_group = 2131558443; - + // aapt resource value: 0x7F0D002C public const int mr_controller_disconnect = 2131558444; - + // aapt resource value: 0x7F0D002D public const int mr_controller_expand_group = 2131558445; - + // aapt resource value: 0x7F0D002E public const int mr_controller_no_info_available = 2131558446; - + // aapt resource value: 0x7F0D002F public const int mr_controller_no_media_selected = 2131558447; - + // aapt resource value: 0x7F0D0030 public const int mr_controller_pause = 2131558448; - + // aapt resource value: 0x7F0D0031 public const int mr_controller_play = 2131558449; - + // aapt resource value: 0x7F0D0032 public const int mr_controller_stop = 2131558450; - + // aapt resource value: 0x7F0D0033 public const int mr_controller_stop_casting = 2131558451; - + // aapt resource value: 0x7F0D0034 public const int mr_controller_volume_slider = 2131558452; - + // aapt resource value: 0x7F0D0035 public const int mr_system_route_name = 2131558453; - + // aapt resource value: 0x7F0D0036 public const int mr_user_route_category_name = 2131558454; - + // aapt resource value: 0x7F0D0037 public const int password_toggle_content_description = 2131558455; - + // aapt resource value: 0x7F0D0038 public const int path_password_eye = 2131558456; - + // aapt resource value: 0x7F0D0039 public const int path_password_eye_mask_strike_through = 2131558457; - + // aapt resource value: 0x7F0D003A public const int path_password_eye_mask_visible = 2131558458; - + // aapt resource value: 0x7F0D003B public const int path_password_strike_through = 2131558459; - + // aapt resource value: 0x7F0D003C public const int search_menu_title = 2131558460; - + // aapt resource value: 0x7F0D003D public const int status_bar_notification_info_overflow = 2131558461; - + static String() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private String() { } } - + public partial class Style { - + // aapt resource value: 0x7F0E0000 public const int AlertDialog_AppCompat = 2131623936; - + // aapt resource value: 0x7F0E0001 public const int AlertDialog_AppCompat_Light = 2131623937; - + // aapt resource value: 0x7F0E0002 public const int Animation_AppCompat_Dialog = 2131623938; - + // aapt resource value: 0x7F0E0003 public const int Animation_AppCompat_DropDownUp = 2131623939; - + // aapt resource value: 0x7F0E0004 public const int Animation_AppCompat_Tooltip = 2131623940; - + // aapt resource value: 0x7F0E0005 public const int Animation_Design_BottomSheetDialog = 2131623941; - + // aapt resource value: 0x7F0E0006 public const int Base_AlertDialog_AppCompat = 2131623942; - + // aapt resource value: 0x7F0E0007 public const int Base_AlertDialog_AppCompat_Light = 2131623943; - + // aapt resource value: 0x7F0E0008 public const int Base_Animation_AppCompat_Dialog = 2131623944; - + // aapt resource value: 0x7F0E0009 public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - + // aapt resource value: 0x7F0E000A public const int Base_Animation_AppCompat_Tooltip = 2131623946; - + // aapt resource value: 0x7F0E000B public const int Base_CardView = 2131623947; - + // aapt resource value: 0x7F0E000D public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - + // aapt resource value: 0x7F0E000C public const int Base_DialogWindowTitle_AppCompat = 2131623948; - + // aapt resource value: 0x7F0E000E public const int Base_TextAppearance_AppCompat = 2131623950; - + // aapt resource value: 0x7F0E000F public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - + // aapt resource value: 0x7F0E0010 public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - + // aapt resource value: 0x7F0E0011 public const int Base_TextAppearance_AppCompat_Button = 2131623953; - + // aapt resource value: 0x7F0E0012 public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - + // aapt resource value: 0x7F0E0013 public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - + // aapt resource value: 0x7F0E0014 public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - + // aapt resource value: 0x7F0E0015 public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - + // aapt resource value: 0x7F0E0016 public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - + // aapt resource value: 0x7F0E0017 public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - + // aapt resource value: 0x7F0E0018 public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - + // aapt resource value: 0x7F0E0019 public const int Base_TextAppearance_AppCompat_Large = 2131623961; - + // aapt resource value: 0x7F0E001A public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - + // aapt resource value: 0x7F0E001B public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - + // aapt resource value: 0x7F0E001C public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - + // aapt resource value: 0x7F0E001D public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - + // aapt resource value: 0x7F0E001E public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - + // aapt resource value: 0x7F0E001F public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - + // aapt resource value: 0x7F0E0020 public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - + // aapt resource value: 0x7F0E0021 public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - + // aapt resource value: 0x7F0E0022 public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - + // aapt resource value: 0x7F0E0023 public const int Base_TextAppearance_AppCompat_Small = 2131623971; - + // aapt resource value: 0x7F0E0024 public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - + // aapt resource value: 0x7F0E0025 public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - + // aapt resource value: 0x7F0E0026 public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - + // aapt resource value: 0x7F0E0027 public const int Base_TextAppearance_AppCompat_Title = 2131623975; - + // aapt resource value: 0x7F0E0028 public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - + // aapt resource value: 0x7F0E0029 public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - + // aapt resource value: 0x7F0E002A public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - + // aapt resource value: 0x7F0E002B public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - + // aapt resource value: 0x7F0E002C public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - + // aapt resource value: 0x7F0E002D public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - + // aapt resource value: 0x7F0E002E public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - + // aapt resource value: 0x7F0E002F public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - + // aapt resource value: 0x7F0E0030 public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - + // aapt resource value: 0x7F0E0031 public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - + // aapt resource value: 0x7F0E0032 public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - + // aapt resource value: 0x7F0E0033 public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - + // aapt resource value: 0x7F0E0034 public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - + // aapt resource value: 0x7F0E0035 public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - + // aapt resource value: 0x7F0E0036 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - + // aapt resource value: 0x7F0E0037 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - + // aapt resource value: 0x7F0E0038 public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - + // aapt resource value: 0x7F0E0039 public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - + // aapt resource value: 0x7F0E003A public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - + // aapt resource value: 0x7F0E003B public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - + // aapt resource value: 0x7F0E003C public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - + // aapt resource value: 0x7F0E003D public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - + // aapt resource value: 0x7F0E004C public const int Base_ThemeOverlay_AppCompat = 2131624012; - + // aapt resource value: 0x7F0E004D public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - + // aapt resource value: 0x7F0E004E public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - + // aapt resource value: 0x7F0E004F public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - + // aapt resource value: 0x7F0E0050 public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - + // aapt resource value: 0x7F0E0051 public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - + // aapt resource value: 0x7F0E0052 public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - + // aapt resource value: 0x7F0E003E public const int Base_Theme_AppCompat = 2131623998; - + // aapt resource value: 0x7F0E003F public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - + // aapt resource value: 0x7F0E0040 public const int Base_Theme_AppCompat_Dialog = 2131624000; - + // aapt resource value: 0x7F0E0044 public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - + // aapt resource value: 0x7F0E0041 public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - + // aapt resource value: 0x7F0E0042 public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - + // aapt resource value: 0x7F0E0043 public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - + // aapt resource value: 0x7F0E0045 public const int Base_Theme_AppCompat_Light = 2131624005; - + // aapt resource value: 0x7F0E0046 public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - + // aapt resource value: 0x7F0E0047 public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - + // aapt resource value: 0x7F0E004B public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - + // aapt resource value: 0x7F0E0048 public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - + // aapt resource value: 0x7F0E0049 public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - + // aapt resource value: 0x7F0E004A public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - + // aapt resource value: 0x7F0E0055 public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - + // aapt resource value: 0x7F0E0053 public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - + // aapt resource value: 0x7F0E0054 public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - + // aapt resource value: 0x7F0E0056 public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - + // aapt resource value: 0x7F0E0057 public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - + // aapt resource value: 0x7F0E0058 public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - + // aapt resource value: 0x7F0E005D public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - + // aapt resource value: 0x7F0E0059 public const int Base_V21_Theme_AppCompat = 2131624025; - + // aapt resource value: 0x7F0E005A public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - + // aapt resource value: 0x7F0E005B public const int Base_V21_Theme_AppCompat_Light = 2131624027; - + // aapt resource value: 0x7F0E005C public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - + // aapt resource value: 0x7F0E005E public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - + // aapt resource value: 0x7F0E005F public const int Base_V22_Theme_AppCompat = 2131624031; - + // aapt resource value: 0x7F0E0060 public const int Base_V22_Theme_AppCompat_Light = 2131624032; - + // aapt resource value: 0x7F0E0061 public const int Base_V23_Theme_AppCompat = 2131624033; - + // aapt resource value: 0x7F0E0062 public const int Base_V23_Theme_AppCompat_Light = 2131624034; - + // aapt resource value: 0x7F0E0063 public const int Base_V26_Theme_AppCompat = 2131624035; - + // aapt resource value: 0x7F0E0064 public const int Base_V26_Theme_AppCompat_Light = 2131624036; - + // aapt resource value: 0x7F0E0065 public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - + // aapt resource value: 0x7F0E0066 public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - + // aapt resource value: 0x7F0E006B public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - + // aapt resource value: 0x7F0E0067 public const int Base_V7_Theme_AppCompat = 2131624039; - + // aapt resource value: 0x7F0E0068 public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - + // aapt resource value: 0x7F0E0069 public const int Base_V7_Theme_AppCompat_Light = 2131624041; - + // aapt resource value: 0x7F0E006A public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - + // aapt resource value: 0x7F0E006C public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - + // aapt resource value: 0x7F0E006D public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - + // aapt resource value: 0x7F0E006E public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - + // aapt resource value: 0x7F0E006F public const int Base_Widget_AppCompat_ActionBar = 2131624047; - + // aapt resource value: 0x7F0E0070 public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - + // aapt resource value: 0x7F0E0071 public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - + // aapt resource value: 0x7F0E0072 public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - + // aapt resource value: 0x7F0E0073 public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - + // aapt resource value: 0x7F0E0074 public const int Base_Widget_AppCompat_ActionButton = 2131624052; - + // aapt resource value: 0x7F0E0075 public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - + // aapt resource value: 0x7F0E0076 public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - + // aapt resource value: 0x7F0E0077 public const int Base_Widget_AppCompat_ActionMode = 2131624055; - + // aapt resource value: 0x7F0E0078 public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - + // aapt resource value: 0x7F0E0079 public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - + // aapt resource value: 0x7F0E007A public const int Base_Widget_AppCompat_Button = 2131624058; - + // aapt resource value: 0x7F0E0080 public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - + // aapt resource value: 0x7F0E0081 public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - + // aapt resource value: 0x7F0E007B public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - + // aapt resource value: 0x7F0E007C public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - + // aapt resource value: 0x7F0E007D public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - + // aapt resource value: 0x7F0E007E public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - + // aapt resource value: 0x7F0E007F public const int Base_Widget_AppCompat_Button_Small = 2131624063; - + // aapt resource value: 0x7F0E0082 public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - + // aapt resource value: 0x7F0E0083 public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - + // aapt resource value: 0x7F0E0084 public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - + // aapt resource value: 0x7F0E0085 public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - + // aapt resource value: 0x7F0E0086 public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - + // aapt resource value: 0x7F0E0087 public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - + // aapt resource value: 0x7F0E0088 public const int Base_Widget_AppCompat_EditText = 2131624072; - + // aapt resource value: 0x7F0E0089 public const int Base_Widget_AppCompat_ImageButton = 2131624073; - + // aapt resource value: 0x7F0E008A public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - + // aapt resource value: 0x7F0E008B public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - + // aapt resource value: 0x7F0E008C public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - + // aapt resource value: 0x7F0E008D public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - + // aapt resource value: 0x7F0E008E public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - + // aapt resource value: 0x7F0E008F public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - + // aapt resource value: 0x7F0E0090 public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - + // aapt resource value: 0x7F0E0091 public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - + // aapt resource value: 0x7F0E0092 public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - + // aapt resource value: 0x7F0E0093 public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - + // aapt resource value: 0x7F0E0094 public const int Base_Widget_AppCompat_ListView = 2131624084; - + // aapt resource value: 0x7F0E0095 public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - + // aapt resource value: 0x7F0E0096 public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - + // aapt resource value: 0x7F0E0097 public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - + // aapt resource value: 0x7F0E0098 public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - + // aapt resource value: 0x7F0E0099 public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - + // aapt resource value: 0x7F0E009A public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - + // aapt resource value: 0x7F0E009B public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - + // aapt resource value: 0x7F0E009C public const int Base_Widget_AppCompat_RatingBar = 2131624092; - + // aapt resource value: 0x7F0E009D public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - + // aapt resource value: 0x7F0E009E public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - + // aapt resource value: 0x7F0E009F public const int Base_Widget_AppCompat_SearchView = 2131624095; - + // aapt resource value: 0x7F0E00A0 public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - + // aapt resource value: 0x7F0E00A1 public const int Base_Widget_AppCompat_SeekBar = 2131624097; - + // aapt resource value: 0x7F0E00A2 public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - + // aapt resource value: 0x7F0E00A3 public const int Base_Widget_AppCompat_Spinner = 2131624099; - + // aapt resource value: 0x7F0E00A4 public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - + // aapt resource value: 0x7F0E00A5 public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - + // aapt resource value: 0x7F0E00A6 public const int Base_Widget_AppCompat_Toolbar = 2131624102; - + // aapt resource value: 0x7F0E00A7 public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - + // aapt resource value: 0x7F0E00A8 public const int Base_Widget_Design_AppBarLayout = 2131624104; - + // aapt resource value: 0x7F0E00A9 public const int Base_Widget_Design_TabLayout = 2131624105; - + // aapt resource value: 0x7F0E00AA public const int CardView = 2131624106; - + // aapt resource value: 0x7F0E00AB public const int CardView_Dark = 2131624107; - + // aapt resource value: 0x7F0E00AC public const int CardView_Light = 2131624108; - + // aapt resource value: 0x7F0E00AD public const int Platform_AppCompat = 2131624109; - + // aapt resource value: 0x7F0E00AE public const int Platform_AppCompat_Light = 2131624110; - + // aapt resource value: 0x7F0E00AF public const int Platform_ThemeOverlay_AppCompat = 2131624111; - + // aapt resource value: 0x7F0E00B0 public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - + // aapt resource value: 0x7F0E00B1 public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - + // aapt resource value: 0x7F0E00B2 public const int Platform_V11_AppCompat = 2131624114; - + // aapt resource value: 0x7F0E00B3 public const int Platform_V11_AppCompat_Light = 2131624115; - + // aapt resource value: 0x7F0E00B4 public const int Platform_V14_AppCompat = 2131624116; - + // aapt resource value: 0x7F0E00B5 public const int Platform_V14_AppCompat_Light = 2131624117; - + // aapt resource value: 0x7F0E00B6 public const int Platform_V21_AppCompat = 2131624118; - + // aapt resource value: 0x7F0E00B7 public const int Platform_V21_AppCompat_Light = 2131624119; - + // aapt resource value: 0x7F0E00B8 public const int Platform_V25_AppCompat = 2131624120; - + // aapt resource value: 0x7F0E00B9 public const int Platform_V25_AppCompat_Light = 2131624121; - + // aapt resource value: 0x7F0E00BA public const int Platform_Widget_AppCompat_Spinner = 2131624122; - + // aapt resource value: 0x7F0E00BB public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - + // aapt resource value: 0x7F0E00BC public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - + // aapt resource value: 0x7F0E00BD public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - + // aapt resource value: 0x7F0E00BE public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - + // aapt resource value: 0x7F0E00BF public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - + // aapt resource value: 0x7F0E00C0 public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - + // aapt resource value: 0x7F0E00C6 public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - + // aapt resource value: 0x7F0E00C1 public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - + // aapt resource value: 0x7F0E00C2 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - + // aapt resource value: 0x7F0E00C3 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - + // aapt resource value: 0x7F0E00C4 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - + // aapt resource value: 0x7F0E00C5 public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - + // aapt resource value: 0x7F0E00C7 public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - + // aapt resource value: 0x7F0E00C8 public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - + // aapt resource value: 0x7F0E00C9 public const int TextAppearance_AppCompat = 2131624137; - + // aapt resource value: 0x7F0E00CA public const int TextAppearance_AppCompat_Body1 = 2131624138; - + // aapt resource value: 0x7F0E00CB public const int TextAppearance_AppCompat_Body2 = 2131624139; - + // aapt resource value: 0x7F0E00CC public const int TextAppearance_AppCompat_Button = 2131624140; - + // aapt resource value: 0x7F0E00CD public const int TextAppearance_AppCompat_Caption = 2131624141; - + // aapt resource value: 0x7F0E00CE public const int TextAppearance_AppCompat_Display1 = 2131624142; - + // aapt resource value: 0x7F0E00CF public const int TextAppearance_AppCompat_Display2 = 2131624143; - + // aapt resource value: 0x7F0E00D0 public const int TextAppearance_AppCompat_Display3 = 2131624144; - + // aapt resource value: 0x7F0E00D1 public const int TextAppearance_AppCompat_Display4 = 2131624145; - + // aapt resource value: 0x7F0E00D2 public const int TextAppearance_AppCompat_Headline = 2131624146; - + // aapt resource value: 0x7F0E00D3 public const int TextAppearance_AppCompat_Inverse = 2131624147; - + // aapt resource value: 0x7F0E00D4 public const int TextAppearance_AppCompat_Large = 2131624148; - + // aapt resource value: 0x7F0E00D5 public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - + // aapt resource value: 0x7F0E00D6 public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - + // aapt resource value: 0x7F0E00D7 public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - + // aapt resource value: 0x7F0E00D8 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - + // aapt resource value: 0x7F0E00D9 public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - + // aapt resource value: 0x7F0E00DA public const int TextAppearance_AppCompat_Medium = 2131624154; - + // aapt resource value: 0x7F0E00DB public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - + // aapt resource value: 0x7F0E00DC public const int TextAppearance_AppCompat_Menu = 2131624156; - + // aapt resource value: 0x7F0E00DD public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - + // aapt resource value: 0x7F0E00DE public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - + // aapt resource value: 0x7F0E00DF public const int TextAppearance_AppCompat_Small = 2131624159; - + // aapt resource value: 0x7F0E00E0 public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - + // aapt resource value: 0x7F0E00E1 public const int TextAppearance_AppCompat_Subhead = 2131624161; - + // aapt resource value: 0x7F0E00E2 public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - + // aapt resource value: 0x7F0E00E3 public const int TextAppearance_AppCompat_Title = 2131624163; - + // aapt resource value: 0x7F0E00E4 public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - + // aapt resource value: 0x7F0E00E5 public const int TextAppearance_AppCompat_Tooltip = 2131624165; - + // aapt resource value: 0x7F0E00E6 public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - + // aapt resource value: 0x7F0E00E7 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - + // aapt resource value: 0x7F0E00E8 public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - + // aapt resource value: 0x7F0E00E9 public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - + // aapt resource value: 0x7F0E00EA public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - + // aapt resource value: 0x7F0E00EB public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - + // aapt resource value: 0x7F0E00EC public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - + // aapt resource value: 0x7F0E00ED public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - + // aapt resource value: 0x7F0E00EE public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - + // aapt resource value: 0x7F0E00EF public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - + // aapt resource value: 0x7F0E00F0 public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - + // aapt resource value: 0x7F0E00F1 public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - + // aapt resource value: 0x7F0E00F2 public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - + // aapt resource value: 0x7F0E00F3 public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - + // aapt resource value: 0x7F0E00F4 public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - + // aapt resource value: 0x7F0E00F5 public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - + // aapt resource value: 0x7F0E00F6 public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - + // aapt resource value: 0x7F0E00F7 public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - + // aapt resource value: 0x7F0E00F8 public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - + // aapt resource value: 0x7F0E00F9 public const int TextAppearance_Compat_Notification = 2131624185; - + // aapt resource value: 0x7F0E00FA public const int TextAppearance_Compat_Notification_Info = 2131624186; - + // aapt resource value: 0x7F0E00FB public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - + // aapt resource value: 0x7F0E00FC public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - + // aapt resource value: 0x7F0E00FD public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - + // aapt resource value: 0x7F0E00FE public const int TextAppearance_Compat_Notification_Media = 2131624190; - + // aapt resource value: 0x7F0E00FF public const int TextAppearance_Compat_Notification_Time = 2131624191; - + // aapt resource value: 0x7F0E0100 public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - + // aapt resource value: 0x7F0E0101 public const int TextAppearance_Compat_Notification_Title = 2131624193; - + // aapt resource value: 0x7F0E0102 public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - + // aapt resource value: 0x7F0E0103 public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - + // aapt resource value: 0x7F0E0104 public const int TextAppearance_Design_Counter = 2131624196; - + // aapt resource value: 0x7F0E0105 public const int TextAppearance_Design_Counter_Overflow = 2131624197; - + // aapt resource value: 0x7F0E0106 public const int TextAppearance_Design_Error = 2131624198; - + // aapt resource value: 0x7F0E0107 public const int TextAppearance_Design_Hint = 2131624199; - + // aapt resource value: 0x7F0E0108 public const int TextAppearance_Design_Snackbar_Message = 2131624200; - + // aapt resource value: 0x7F0E0109 public const int TextAppearance_Design_Tab = 2131624201; - + // aapt resource value: 0x7F0E010A public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - + // aapt resource value: 0x7F0E010B public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - + // aapt resource value: 0x7F0E010C public const int TextAppearance_MediaRouter_Title = 2131624204; - + // aapt resource value: 0x7F0E010D public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - + // aapt resource value: 0x7F0E010E public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - + // aapt resource value: 0x7F0E010F public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - + // aapt resource value: 0x7F0E012F public const int ThemeOverlay_AppCompat = 2131624239; - + // aapt resource value: 0x7F0E0130 public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - + // aapt resource value: 0x7F0E0131 public const int ThemeOverlay_AppCompat_Dark = 2131624241; - + // aapt resource value: 0x7F0E0132 public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - + // aapt resource value: 0x7F0E0133 public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - + // aapt resource value: 0x7F0E0134 public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - + // aapt resource value: 0x7F0E0135 public const int ThemeOverlay_AppCompat_Light = 2131624245; - + // aapt resource value: 0x7F0E0136 public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - + // aapt resource value: 0x7F0E0137 public const int ThemeOverlay_MediaRouter_Light = 2131624247; - + // aapt resource value: 0x7F0E0110 public const int Theme_AppCompat = 2131624208; - + // aapt resource value: 0x7F0E0111 public const int Theme_AppCompat_CompactMenu = 2131624209; - + // aapt resource value: 0x7F0E0112 public const int Theme_AppCompat_DayNight = 2131624210; - + // aapt resource value: 0x7F0E0113 public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - + // aapt resource value: 0x7F0E0114 public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - + // aapt resource value: 0x7F0E0117 public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - + // aapt resource value: 0x7F0E0115 public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - + // aapt resource value: 0x7F0E0116 public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - + // aapt resource value: 0x7F0E0118 public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - + // aapt resource value: 0x7F0E0119 public const int Theme_AppCompat_Dialog = 2131624217; - + // aapt resource value: 0x7F0E011C public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - + // aapt resource value: 0x7F0E011A public const int Theme_AppCompat_Dialog_Alert = 2131624218; - + // aapt resource value: 0x7F0E011B public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - + // aapt resource value: 0x7F0E011D public const int Theme_AppCompat_Light = 2131624221; - + // aapt resource value: 0x7F0E011E public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - + // aapt resource value: 0x7F0E011F public const int Theme_AppCompat_Light_Dialog = 2131624223; - + // aapt resource value: 0x7F0E0122 public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - + // aapt resource value: 0x7F0E0120 public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - + // aapt resource value: 0x7F0E0121 public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - + // aapt resource value: 0x7F0E0123 public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - + // aapt resource value: 0x7F0E0124 public const int Theme_AppCompat_NoActionBar = 2131624228; - + // aapt resource value: 0x7F0E0125 public const int Theme_Design = 2131624229; - + // aapt resource value: 0x7F0E0126 public const int Theme_Design_BottomSheetDialog = 2131624230; - + // aapt resource value: 0x7F0E0127 public const int Theme_Design_Light = 2131624231; - + // aapt resource value: 0x7F0E0128 public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - + // aapt resource value: 0x7F0E0129 public const int Theme_Design_Light_NoActionBar = 2131624233; - + // aapt resource value: 0x7F0E012A public const int Theme_Design_NoActionBar = 2131624234; - + // aapt resource value: 0x7F0E012B public const int Theme_MediaRouter = 2131624235; - + // aapt resource value: 0x7F0E012C public const int Theme_MediaRouter_Light = 2131624236; - + // aapt resource value: 0x7F0E012E public const int Theme_MediaRouter_LightControlPanel = 2131624238; - + // aapt resource value: 0x7F0E012D public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - + // aapt resource value: 0x7F0E0138 public const int Widget_AppCompat_ActionBar = 2131624248; - + // aapt resource value: 0x7F0E0139 public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - + // aapt resource value: 0x7F0E013A public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - + // aapt resource value: 0x7F0E013B public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - + // aapt resource value: 0x7F0E013C public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - + // aapt resource value: 0x7F0E013D public const int Widget_AppCompat_ActionButton = 2131624253; - + // aapt resource value: 0x7F0E013E public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - + // aapt resource value: 0x7F0E013F public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - + // aapt resource value: 0x7F0E0140 public const int Widget_AppCompat_ActionMode = 2131624256; - + // aapt resource value: 0x7F0E0141 public const int Widget_AppCompat_ActivityChooserView = 2131624257; - + // aapt resource value: 0x7F0E0142 public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - + // aapt resource value: 0x7F0E0143 public const int Widget_AppCompat_Button = 2131624259; - + // aapt resource value: 0x7F0E0149 public const int Widget_AppCompat_ButtonBar = 2131624265; - + // aapt resource value: 0x7F0E014A public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - + // aapt resource value: 0x7F0E0144 public const int Widget_AppCompat_Button_Borderless = 2131624260; - + // aapt resource value: 0x7F0E0145 public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - + // aapt resource value: 0x7F0E0146 public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - + // aapt resource value: 0x7F0E0147 public const int Widget_AppCompat_Button_Colored = 2131624263; - + // aapt resource value: 0x7F0E0148 public const int Widget_AppCompat_Button_Small = 2131624264; - + // aapt resource value: 0x7F0E014B public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - + // aapt resource value: 0x7F0E014C public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - + // aapt resource value: 0x7F0E014D public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - + // aapt resource value: 0x7F0E014E public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - + // aapt resource value: 0x7F0E014F public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - + // aapt resource value: 0x7F0E0150 public const int Widget_AppCompat_EditText = 2131624272; - + // aapt resource value: 0x7F0E0151 public const int Widget_AppCompat_ImageButton = 2131624273; - + // aapt resource value: 0x7F0E0152 public const int Widget_AppCompat_Light_ActionBar = 2131624274; - + // aapt resource value: 0x7F0E0153 public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - + // aapt resource value: 0x7F0E0154 public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - + // aapt resource value: 0x7F0E0155 public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - + // aapt resource value: 0x7F0E0156 public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - + // aapt resource value: 0x7F0E0157 public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - + // aapt resource value: 0x7F0E0158 public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - + // aapt resource value: 0x7F0E0159 public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - + // aapt resource value: 0x7F0E015A public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - + // aapt resource value: 0x7F0E015B public const int Widget_AppCompat_Light_ActionButton = 2131624283; - + // aapt resource value: 0x7F0E015C public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - + // aapt resource value: 0x7F0E015D public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - + // aapt resource value: 0x7F0E015E public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - + // aapt resource value: 0x7F0E015F public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - + // aapt resource value: 0x7F0E0160 public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - + // aapt resource value: 0x7F0E0161 public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - + // aapt resource value: 0x7F0E0162 public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - + // aapt resource value: 0x7F0E0163 public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - + // aapt resource value: 0x7F0E0164 public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - + // aapt resource value: 0x7F0E0165 public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - + // aapt resource value: 0x7F0E0166 public const int Widget_AppCompat_Light_SearchView = 2131624294; - + // aapt resource value: 0x7F0E0167 public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - + // aapt resource value: 0x7F0E0168 public const int Widget_AppCompat_ListMenuView = 2131624296; - + // aapt resource value: 0x7F0E0169 public const int Widget_AppCompat_ListPopupWindow = 2131624297; - + // aapt resource value: 0x7F0E016A public const int Widget_AppCompat_ListView = 2131624298; - + // aapt resource value: 0x7F0E016B public const int Widget_AppCompat_ListView_DropDown = 2131624299; - + // aapt resource value: 0x7F0E016C public const int Widget_AppCompat_ListView_Menu = 2131624300; - + // aapt resource value: 0x7F0E016D public const int Widget_AppCompat_PopupMenu = 2131624301; - + // aapt resource value: 0x7F0E016E public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - + // aapt resource value: 0x7F0E016F public const int Widget_AppCompat_PopupWindow = 2131624303; - + // aapt resource value: 0x7F0E0170 public const int Widget_AppCompat_ProgressBar = 2131624304; - + // aapt resource value: 0x7F0E0171 public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - + // aapt resource value: 0x7F0E0172 public const int Widget_AppCompat_RatingBar = 2131624306; - + // aapt resource value: 0x7F0E0173 public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - + // aapt resource value: 0x7F0E0174 public const int Widget_AppCompat_RatingBar_Small = 2131624308; - + // aapt resource value: 0x7F0E0175 public const int Widget_AppCompat_SearchView = 2131624309; - + // aapt resource value: 0x7F0E0176 public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - + // aapt resource value: 0x7F0E0177 public const int Widget_AppCompat_SeekBar = 2131624311; - + // aapt resource value: 0x7F0E0178 public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - + // aapt resource value: 0x7F0E0179 public const int Widget_AppCompat_Spinner = 2131624313; - + // aapt resource value: 0x7F0E017A public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - + // aapt resource value: 0x7F0E017B public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - + // aapt resource value: 0x7F0E017C public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - + // aapt resource value: 0x7F0E017D public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - + // aapt resource value: 0x7F0E017E public const int Widget_AppCompat_Toolbar = 2131624318; - + // aapt resource value: 0x7F0E017F public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - + // aapt resource value: 0x7F0E0180 public const int Widget_Compat_NotificationActionContainer = 2131624320; - + // aapt resource value: 0x7F0E0181 public const int Widget_Compat_NotificationActionText = 2131624321; - + // aapt resource value: 0x7F0E0182 public const int Widget_Design_AppBarLayout = 2131624322; - + // aapt resource value: 0x7F0E0183 public const int Widget_Design_BottomNavigationView = 2131624323; - + // aapt resource value: 0x7F0E0184 public const int Widget_Design_BottomSheet_Modal = 2131624324; - + // aapt resource value: 0x7F0E0185 public const int Widget_Design_CollapsingToolbar = 2131624325; - + // aapt resource value: 0x7F0E0186 public const int Widget_Design_CoordinatorLayout = 2131624326; - + // aapt resource value: 0x7F0E0187 public const int Widget_Design_FloatingActionButton = 2131624327; - + // aapt resource value: 0x7F0E0188 public const int Widget_Design_NavigationView = 2131624328; - + // aapt resource value: 0x7F0E0189 public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - + // aapt resource value: 0x7F0E018A public const int Widget_Design_Snackbar = 2131624330; - + // aapt resource value: 0x7F0E018B public const int Widget_Design_TabLayout = 2131624331; - + // aapt resource value: 0x7F0E018C public const int Widget_Design_TextInputLayout = 2131624332; - + // aapt resource value: 0x7F0E018D public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - + // aapt resource value: 0x7F0E018E public const int Widget_MediaRouter_MediaRouteButton = 2131624334; - + static Style() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Style() { } } - + public partial class Styleable { - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } public static int[] ActionBar = new int[] { 2130903089, @@ -7417,112 +7417,112 @@ public partial class Styleable 2130903335, 2130903379, 2130903389}; - + // aapt resource value: { 0x10100B3 } public static int[] ActionBarLayout = new int[] { 16842931}; - + // aapt resource value: 0 public const int ActionBarLayout_android_layout_gravity = 0; - + // aapt resource value: 0 public const int ActionBar_background = 0; - + // aapt resource value: 1 public const int ActionBar_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionBar_backgroundStacked = 2; - + // aapt resource value: 3 public const int ActionBar_contentInsetEnd = 3; - + // aapt resource value: 4 public const int ActionBar_contentInsetEndWithActions = 4; - + // aapt resource value: 5 public const int ActionBar_contentInsetLeft = 5; - + // aapt resource value: 6 public const int ActionBar_contentInsetRight = 6; - + // aapt resource value: 7 public const int ActionBar_contentInsetStart = 7; - + // aapt resource value: 8 public const int ActionBar_contentInsetStartWithNavigation = 8; - + // aapt resource value: 9 public const int ActionBar_customNavigationLayout = 9; - + // aapt resource value: 10 public const int ActionBar_displayOptions = 10; - + // aapt resource value: 11 public const int ActionBar_divider = 11; - + // aapt resource value: 12 public const int ActionBar_elevation = 12; - + // aapt resource value: 13 public const int ActionBar_height = 13; - + // aapt resource value: 14 public const int ActionBar_hideOnContentScroll = 14; - + // aapt resource value: 15 public const int ActionBar_homeAsUpIndicator = 15; - + // aapt resource value: 16 public const int ActionBar_homeLayout = 16; - + // aapt resource value: 17 public const int ActionBar_icon = 17; - + // aapt resource value: 18 public const int ActionBar_indeterminateProgressStyle = 18; - + // aapt resource value: 19 public const int ActionBar_itemPadding = 19; - + // aapt resource value: 20 public const int ActionBar_logo = 20; - + // aapt resource value: 21 public const int ActionBar_navigationMode = 21; - + // aapt resource value: 22 public const int ActionBar_popupTheme = 22; - + // aapt resource value: 23 public const int ActionBar_progressBarPadding = 23; - + // aapt resource value: 24 public const int ActionBar_progressBarStyle = 24; - + // aapt resource value: 25 public const int ActionBar_subtitle = 25; - + // aapt resource value: 26 public const int ActionBar_subtitleTextStyle = 26; - + // aapt resource value: 27 public const int ActionBar_title = 27; - + // aapt resource value: 28 public const int ActionBar_titleTextStyle = 28; - + // aapt resource value: { 0x101013F } public static int[] ActionMenuItemView = new int[] { 16843071}; - + // aapt resource value: 0 public const int ActionMenuItemView_android_minWidth = 0; - + // aapt resource value: { 0xFFFFFFFF } public static int[] ActionMenuView = new int[] { -1}; - + // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } public static int[] ActionMode = new int[] { 2130903089, @@ -7531,36 +7531,36 @@ public partial class Styleable 2130903208, 2130903335, 2130903389}; - + // aapt resource value: 0 public const int ActionMode_background = 0; - + // aapt resource value: 1 public const int ActionMode_backgroundSplit = 1; - + // aapt resource value: 2 public const int ActionMode_closeItemLayout = 2; - + // aapt resource value: 3 public const int ActionMode_height = 3; - + // aapt resource value: 4 public const int ActionMode_subtitleTextStyle = 4; - + // aapt resource value: 5 public const int ActionMode_titleTextStyle = 5; - + // aapt resource value: { 0x7F03008A,0x7F0300B5 } public static int[] ActivityChooserView = new int[] { 2130903178, 2130903221}; - + // aapt resource value: 0 public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; - + // aapt resource value: 1 public const int ActivityChooserView_initialActivityCount = 1; - + // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } public static int[] AlertDialog = new int[] { 16842994, @@ -7570,28 +7570,28 @@ public partial class Styleable 2130903272, 2130903316, 2130903317}; - + // aapt resource value: 0 public const int AlertDialog_android_layout = 0; - + // aapt resource value: 1 public const int AlertDialog_buttonPanelSideLayout = 1; - + // aapt resource value: 2 public const int AlertDialog_listItemLayout = 2; - + // aapt resource value: 3 public const int AlertDialog_listLayout = 3; - + // aapt resource value: 4 public const int AlertDialog_multiChoiceItemLayout = 4; - + // aapt resource value: 5 public const int AlertDialog_showTitle = 5; - + // aapt resource value: 6 public const int AlertDialog_singleChoiceItemLayout = 6; - + // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } public static int[] AppBarLayout = new int[] { 16842964, @@ -7599,82 +7599,82 @@ public partial class Styleable 16844096, 2130903175, 2130903179}; - + // aapt resource value: { 0x7F03011E,0x7F03011F } public static int[] AppBarLayoutStates = new int[] { 2130903326, 2130903327}; - + // aapt resource value: 0 public const int AppBarLayoutStates_state_collapsed = 0; - + // aapt resource value: 1 public const int AppBarLayoutStates_state_collapsible = 1; - + // aapt resource value: 0 public const int AppBarLayout_android_background = 0; - + // aapt resource value: 2 public const int AppBarLayout_android_keyboardNavigationCluster = 2; - + // aapt resource value: 1 public const int AppBarLayout_android_touchscreenBlocksFocus = 1; - + // aapt resource value: 3 public const int AppBarLayout_elevation = 3; - + // aapt resource value: 4 public const int AppBarLayout_expanded = 4; - + // aapt resource value: { 0x7F0300C8,0x7F0300C9 } public static int[] AppBarLayout_Layout = new int[] { 2130903240, 2130903241}; - + // aapt resource value: 0 public const int AppBarLayout_Layout_layout_scrollFlags = 0; - + // aapt resource value: 1 public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; - + // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } public static int[] AppCompatImageView = new int[] { 16843033, 2130903323, 2130903377, 2130903378}; - + // aapt resource value: 0 public const int AppCompatImageView_android_src = 0; - + // aapt resource value: 1 public const int AppCompatImageView_srcCompat = 1; - + // aapt resource value: 2 public const int AppCompatImageView_tint = 2; - + // aapt resource value: 3 public const int AppCompatImageView_tintMode = 3; - + // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } public static int[] AppCompatSeekBar = new int[] { 16843074, 2130903374, 2130903375, 2130903376}; - + // aapt resource value: 0 public const int AppCompatSeekBar_android_thumb = 0; - + // aapt resource value: 1 public const int AppCompatSeekBar_tickMark = 1; - + // aapt resource value: 2 public const int AppCompatSeekBar_tickMarkTint = 2; - + // aapt resource value: 3 public const int AppCompatSeekBar_tickMarkTintMode = 3; - + // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } public static int[] AppCompatTextHelper = new int[] { 16842804, @@ -7684,28 +7684,28 @@ public partial class Styleable 16843120, 16843666, 16843667}; - + // aapt resource value: 2 public const int AppCompatTextHelper_android_drawableBottom = 2; - + // aapt resource value: 6 public const int AppCompatTextHelper_android_drawableEnd = 6; - + // aapt resource value: 3 public const int AppCompatTextHelper_android_drawableLeft = 3; - + // aapt resource value: 4 public const int AppCompatTextHelper_android_drawableRight = 4; - + // aapt resource value: 5 public const int AppCompatTextHelper_android_drawableStart = 5; - + // aapt resource value: 1 public const int AppCompatTextHelper_android_drawableTop = 1; - + // aapt resource value: 0 public const int AppCompatTextHelper_android_textAppearance = 0; - + // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } public static int[] AppCompatTextView = new int[] { 16842804, @@ -7716,31 +7716,31 @@ public partial class Styleable 2130903088, 2130903195, 2130903357}; - + // aapt resource value: 0 public const int AppCompatTextView_android_textAppearance = 0; - + // aapt resource value: 1 public const int AppCompatTextView_autoSizeMaxTextSize = 1; - + // aapt resource value: 2 public const int AppCompatTextView_autoSizeMinTextSize = 2; - + // aapt resource value: 3 public const int AppCompatTextView_autoSizePresetSizes = 3; - + // aapt resource value: 4 public const int AppCompatTextView_autoSizeStepGranularity = 4; - + // aapt resource value: 5 public const int AppCompatTextView_autoSizeTextType = 5; - + // aapt resource value: 6 public const int AppCompatTextView_fontFamily = 6; - + // aapt resource value: 7 public const int AppCompatTextView_textAllCaps = 7; - + // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } public static int[] AppCompatTheme = new int[] { 16842839, @@ -7862,364 +7862,364 @@ public partial class Styleable 2130903408, 2130903409, 2130903410}; - + // aapt resource value: 2 public const int AppCompatTheme_actionBarDivider = 2; - + // aapt resource value: 3 public const int AppCompatTheme_actionBarItemBackground = 3; - + // aapt resource value: 4 public const int AppCompatTheme_actionBarPopupTheme = 4; - + // aapt resource value: 5 public const int AppCompatTheme_actionBarSize = 5; - + // aapt resource value: 6 public const int AppCompatTheme_actionBarSplitStyle = 6; - + // aapt resource value: 7 public const int AppCompatTheme_actionBarStyle = 7; - + // aapt resource value: 8 public const int AppCompatTheme_actionBarTabBarStyle = 8; - + // aapt resource value: 9 public const int AppCompatTheme_actionBarTabStyle = 9; - + // aapt resource value: 10 public const int AppCompatTheme_actionBarTabTextStyle = 10; - + // aapt resource value: 11 public const int AppCompatTheme_actionBarTheme = 11; - + // aapt resource value: 12 public const int AppCompatTheme_actionBarWidgetTheme = 12; - + // aapt resource value: 13 public const int AppCompatTheme_actionButtonStyle = 13; - + // aapt resource value: 14 public const int AppCompatTheme_actionDropDownStyle = 14; - + // aapt resource value: 15 public const int AppCompatTheme_actionMenuTextAppearance = 15; - + // aapt resource value: 16 public const int AppCompatTheme_actionMenuTextColor = 16; - + // aapt resource value: 17 public const int AppCompatTheme_actionModeBackground = 17; - + // aapt resource value: 18 public const int AppCompatTheme_actionModeCloseButtonStyle = 18; - + // aapt resource value: 19 public const int AppCompatTheme_actionModeCloseDrawable = 19; - + // aapt resource value: 20 public const int AppCompatTheme_actionModeCopyDrawable = 20; - + // aapt resource value: 21 public const int AppCompatTheme_actionModeCutDrawable = 21; - + // aapt resource value: 22 public const int AppCompatTheme_actionModeFindDrawable = 22; - + // aapt resource value: 23 public const int AppCompatTheme_actionModePasteDrawable = 23; - + // aapt resource value: 24 public const int AppCompatTheme_actionModePopupWindowStyle = 24; - + // aapt resource value: 25 public const int AppCompatTheme_actionModeSelectAllDrawable = 25; - + // aapt resource value: 26 public const int AppCompatTheme_actionModeShareDrawable = 26; - + // aapt resource value: 27 public const int AppCompatTheme_actionModeSplitBackground = 27; - + // aapt resource value: 28 public const int AppCompatTheme_actionModeStyle = 28; - + // aapt resource value: 29 public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - + // aapt resource value: 30 public const int AppCompatTheme_actionOverflowButtonStyle = 30; - + // aapt resource value: 31 public const int AppCompatTheme_actionOverflowMenuStyle = 31; - + // aapt resource value: 32 public const int AppCompatTheme_activityChooserViewStyle = 32; - + // aapt resource value: 33 public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; - + // aapt resource value: 34 public const int AppCompatTheme_alertDialogCenterButtons = 34; - + // aapt resource value: 35 public const int AppCompatTheme_alertDialogStyle = 35; - + // aapt resource value: 36 public const int AppCompatTheme_alertDialogTheme = 36; - + // aapt resource value: 1 public const int AppCompatTheme_android_windowAnimationStyle = 1; - + // aapt resource value: 0 public const int AppCompatTheme_android_windowIsFloating = 0; - + // aapt resource value: 37 public const int AppCompatTheme_autoCompleteTextViewStyle = 37; - + // aapt resource value: 38 public const int AppCompatTheme_borderlessButtonStyle = 38; - + // aapt resource value: 39 public const int AppCompatTheme_buttonBarButtonStyle = 39; - + // aapt resource value: 40 public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - + // aapt resource value: 41 public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - + // aapt resource value: 42 public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - + // aapt resource value: 43 public const int AppCompatTheme_buttonBarStyle = 43; - + // aapt resource value: 44 public const int AppCompatTheme_buttonStyle = 44; - + // aapt resource value: 45 public const int AppCompatTheme_buttonStyleSmall = 45; - + // aapt resource value: 46 public const int AppCompatTheme_checkboxStyle = 46; - + // aapt resource value: 47 public const int AppCompatTheme_checkedTextViewStyle = 47; - + // aapt resource value: 48 public const int AppCompatTheme_colorAccent = 48; - + // aapt resource value: 49 public const int AppCompatTheme_colorBackgroundFloating = 49; - + // aapt resource value: 50 public const int AppCompatTheme_colorButtonNormal = 50; - + // aapt resource value: 51 public const int AppCompatTheme_colorControlActivated = 51; - + // aapt resource value: 52 public const int AppCompatTheme_colorControlHighlight = 52; - + // aapt resource value: 53 public const int AppCompatTheme_colorControlNormal = 53; - + // aapt resource value: 54 public const int AppCompatTheme_colorError = 54; - + // aapt resource value: 55 public const int AppCompatTheme_colorPrimary = 55; - + // aapt resource value: 56 public const int AppCompatTheme_colorPrimaryDark = 56; - + // aapt resource value: 57 public const int AppCompatTheme_colorSwitchThumbNormal = 57; - + // aapt resource value: 58 public const int AppCompatTheme_controlBackground = 58; - + // aapt resource value: 59 public const int AppCompatTheme_dialogPreferredPadding = 59; - + // aapt resource value: 60 public const int AppCompatTheme_dialogTheme = 60; - + // aapt resource value: 61 public const int AppCompatTheme_dividerHorizontal = 61; - + // aapt resource value: 62 public const int AppCompatTheme_dividerVertical = 62; - + // aapt resource value: 64 public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - + // aapt resource value: 63 public const int AppCompatTheme_dropDownListViewStyle = 63; - + // aapt resource value: 65 public const int AppCompatTheme_editTextBackground = 65; - + // aapt resource value: 66 public const int AppCompatTheme_editTextColor = 66; - + // aapt resource value: 67 public const int AppCompatTheme_editTextStyle = 67; - + // aapt resource value: 68 public const int AppCompatTheme_homeAsUpIndicator = 68; - + // aapt resource value: 69 public const int AppCompatTheme_imageButtonStyle = 69; - + // aapt resource value: 70 public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - + // aapt resource value: 71 public const int AppCompatTheme_listDividerAlertDialog = 71; - + // aapt resource value: 72 public const int AppCompatTheme_listMenuViewStyle = 72; - + // aapt resource value: 73 public const int AppCompatTheme_listPopupWindowStyle = 73; - + // aapt resource value: 74 public const int AppCompatTheme_listPreferredItemHeight = 74; - + // aapt resource value: 75 public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - + // aapt resource value: 76 public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - + // aapt resource value: 77 public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - + // aapt resource value: 78 public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - + // aapt resource value: 79 public const int AppCompatTheme_panelBackground = 79; - + // aapt resource value: 80 public const int AppCompatTheme_panelMenuListTheme = 80; - + // aapt resource value: 81 public const int AppCompatTheme_panelMenuListWidth = 81; - + // aapt resource value: 82 public const int AppCompatTheme_popupMenuStyle = 82; - + // aapt resource value: 83 public const int AppCompatTheme_popupWindowStyle = 83; - + // aapt resource value: 84 public const int AppCompatTheme_radioButtonStyle = 84; - + // aapt resource value: 85 public const int AppCompatTheme_ratingBarStyle = 85; - + // aapt resource value: 86 public const int AppCompatTheme_ratingBarStyleIndicator = 86; - + // aapt resource value: 87 public const int AppCompatTheme_ratingBarStyleSmall = 87; - + // aapt resource value: 88 public const int AppCompatTheme_searchViewStyle = 88; - + // aapt resource value: 89 public const int AppCompatTheme_seekBarStyle = 89; - + // aapt resource value: 90 public const int AppCompatTheme_selectableItemBackground = 90; - + // aapt resource value: 91 public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - + // aapt resource value: 92 public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - + // aapt resource value: 93 public const int AppCompatTheme_spinnerStyle = 93; - + // aapt resource value: 94 public const int AppCompatTheme_switchStyle = 94; - + // aapt resource value: 95 public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - + // aapt resource value: 96 public const int AppCompatTheme_textAppearanceListItem = 96; - + // aapt resource value: 97 public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - + // aapt resource value: 98 public const int AppCompatTheme_textAppearanceListItemSmall = 98; - + // aapt resource value: 99 public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - + // aapt resource value: 100 public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - + // aapt resource value: 101 public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - + // aapt resource value: 102 public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - + // aapt resource value: 103 public const int AppCompatTheme_textColorAlertDialogListItem = 103; - + // aapt resource value: 104 public const int AppCompatTheme_textColorSearchUrl = 104; - + // aapt resource value: 105 public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - + // aapt resource value: 106 public const int AppCompatTheme_toolbarStyle = 106; - + // aapt resource value: 107 public const int AppCompatTheme_tooltipForegroundColor = 107; - + // aapt resource value: 108 public const int AppCompatTheme_tooltipFrameBackground = 108; - + // aapt resource value: 109 public const int AppCompatTheme_windowActionBar = 109; - + // aapt resource value: 110 public const int AppCompatTheme_windowActionBarOverlay = 110; - + // aapt resource value: 111 public const int AppCompatTheme_windowActionModeOverlay = 111; - + // aapt resource value: 112 public const int AppCompatTheme_windowFixedHeightMajor = 112; - + // aapt resource value: 113 public const int AppCompatTheme_windowFixedHeightMinor = 113; - + // aapt resource value: 114 public const int AppCompatTheme_windowFixedWidthMajor = 114; - + // aapt resource value: 115 public const int AppCompatTheme_windowFixedWidthMinor = 115; - + // aapt resource value: 116 public const int AppCompatTheme_windowMinWidthMajor = 116; - + // aapt resource value: 117 public const int AppCompatTheme_windowMinWidthMinor = 117; - + // aapt resource value: 118 public const int AppCompatTheme_windowNoTitle = 118; - + // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } public static int[] BottomNavigationView = new int[] { 2130903175, @@ -8227,44 +8227,44 @@ public partial class Styleable 2130903225, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int BottomNavigationView_elevation = 0; - + // aapt resource value: 1 public const int BottomNavigationView_itemBackground = 1; - + // aapt resource value: 2 public const int BottomNavigationView_itemIconTint = 2; - + // aapt resource value: 3 public const int BottomNavigationView_itemTextColor = 3; - + // aapt resource value: 4 public const int BottomNavigationView_menu = 4; - + // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } public static int[] BottomSheetBehavior_Layout = new int[] { 2130903096, 2130903098, 2130903099}; - + // aapt resource value: 0 public const int BottomSheetBehavior_Layout_behavior_hideable = 0; - + // aapt resource value: 1 public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; - + // aapt resource value: 2 public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; - + // aapt resource value: { 0x7F030026 } public static int[] ButtonBarLayout = new int[] { 2130903078}; - + // aapt resource value: 0 public const int ButtonBarLayout_allowStacking = 0; - + // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } public static int[] CardView = new int[] { 16843071, @@ -8280,46 +8280,46 @@ public partial class Styleable 2130903150, 2130903151, 2130903152}; - + // aapt resource value: 1 public const int CardView_android_minHeight = 1; - + // aapt resource value: 0 public const int CardView_android_minWidth = 0; - + // aapt resource value: 2 public const int CardView_cardBackgroundColor = 2; - + // aapt resource value: 3 public const int CardView_cardCornerRadius = 3; - + // aapt resource value: 4 public const int CardView_cardElevation = 4; - + // aapt resource value: 5 public const int CardView_cardMaxElevation = 5; - + // aapt resource value: 6 public const int CardView_cardPreventCornerOverlap = 6; - + // aapt resource value: 7 public const int CardView_cardUseCompatPadding = 7; - + // aapt resource value: 8 public const int CardView_contentPadding = 8; - + // aapt resource value: 9 public const int CardView_contentPaddingBottom = 9; - + // aapt resource value: 10 public const int CardView_contentPaddingLeft = 10; - + // aapt resource value: 11 public const int CardView_contentPaddingRight = 11; - + // aapt resource value: 12 public const int CardView_contentPaddingTop = 12; - + // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } public static int[] CollapsingToolbarLayout = new int[] { 2130903127, @@ -8338,104 +8338,104 @@ public partial class Styleable 2130903379, 2130903380, 2130903390}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - + // aapt resource value: 2 public const int CollapsingToolbarLayout_contentScrim = 2; - + // aapt resource value: 3 public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - + // aapt resource value: 4 public const int CollapsingToolbarLayout_expandedTitleMargin = 4; - + // aapt resource value: 5 public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - + // aapt resource value: 6 public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - + // aapt resource value: 7 public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - + // aapt resource value: 8 public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; - + // aapt resource value: 9 public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; - + // aapt resource value: { 0x7F0300C3,0x7F0300C4 } public static int[] CollapsingToolbarLayout_Layout = new int[] { 2130903235, 2130903236}; - + // aapt resource value: 0 public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; - + // aapt resource value: 1 public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; - + // aapt resource value: 10 public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; - + // aapt resource value: 11 public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; - + // aapt resource value: 12 public const int CollapsingToolbarLayout_statusBarScrim = 12; - + // aapt resource value: 13 public const int CollapsingToolbarLayout_title = 13; - + // aapt resource value: 14 public const int CollapsingToolbarLayout_titleEnabled = 14; - + // aapt resource value: 15 public const int CollapsingToolbarLayout_toolbarId = 15; - + // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } public static int[] ColorStateListItem = new int[] { 16843173, 16843551, 2130903079}; - + // aapt resource value: 2 public const int ColorStateListItem_alpha = 2; - + // aapt resource value: 1 public const int ColorStateListItem_android_alpha = 1; - + // aapt resource value: 0 public const int ColorStateListItem_android_color = 0; - + // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } public static int[] CompoundButton = new int[] { 16843015, 2130903113, 2130903114}; - + // aapt resource value: 0 public const int CompoundButton_android_button = 0; - + // aapt resource value: 1 public const int CompoundButton_buttonTint = 1; - + // aapt resource value: 2 public const int CompoundButton_buttonTintMode = 2; - + // aapt resource value: { 0x7F0300BD,0x7F030120 } public static int[] CoordinatorLayout = new int[] { 2130903229, 2130903328}; - + // aapt resource value: 0 public const int CoordinatorLayout_keylines = 0; - + // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } public static int[] CoordinatorLayout_Layout = new int[] { 16842931, @@ -8445,46 +8445,46 @@ public partial class Styleable 2130903237, 2130903238, 2130903239}; - + // aapt resource value: 0 public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - + // aapt resource value: 1 public const int CoordinatorLayout_Layout_layout_anchor = 1; - + // aapt resource value: 2 public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - + // aapt resource value: 3 public const int CoordinatorLayout_Layout_layout_behavior = 3; - + // aapt resource value: 4 public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; - + // aapt resource value: 5 public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - + // aapt resource value: 6 public const int CoordinatorLayout_Layout_layout_keyline = 6; - + // aapt resource value: 1 public const int CoordinatorLayout_statusBarBackground = 1; - + // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } public static int[] DesignTheme = new int[] { 2130903102, 2130903103, 2130903367}; - + // aapt resource value: 0 public const int DesignTheme_bottomSheetDialogTheme = 0; - + // aapt resource value: 1 public const int DesignTheme_bottomSheetStyle = 1; - + // aapt resource value: 2 public const int DesignTheme_textColorError = 2; - + // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } public static int[] DrawerArrowToggle = new int[] { 2130903081, @@ -8495,31 +8495,31 @@ public partial class Styleable 2130903205, 2130903319, 2130903370}; - + // aapt resource value: 0 public const int DrawerArrowToggle_arrowHeadLength = 0; - + // aapt resource value: 1 public const int DrawerArrowToggle_arrowShaftLength = 1; - + // aapt resource value: 2 public const int DrawerArrowToggle_barLength = 2; - + // aapt resource value: 3 public const int DrawerArrowToggle_color = 3; - + // aapt resource value: 4 public const int DrawerArrowToggle_drawableSize = 4; - + // aapt resource value: 5 public const int DrawerArrowToggle_gapBetweenBars = 5; - + // aapt resource value: 6 public const int DrawerArrowToggle_spinBars = 6; - + // aapt resource value: 7 public const int DrawerArrowToggle_thickness = 7; - + // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } public static int[] FloatingActionButton = new int[] { 2130903092, @@ -8530,38 +8530,38 @@ public partial class Styleable 2130903294, 2130903304, 2130903399}; - + // aapt resource value: 0 public const int FloatingActionButton_backgroundTint = 0; - + // aapt resource value: 1 public const int FloatingActionButton_backgroundTintMode = 1; - + // aapt resource value: { 0x7F030037 } public static int[] FloatingActionButton_Behavior_Layout = new int[] { 2130903095}; - + // aapt resource value: 0 public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - + // aapt resource value: 2 public const int FloatingActionButton_borderWidth = 2; - + // aapt resource value: 3 public const int FloatingActionButton_elevation = 3; - + // aapt resource value: 4 public const int FloatingActionButton_fabSize = 4; - + // aapt resource value: 5 public const int FloatingActionButton_pressedTranslationZ = 5; - + // aapt resource value: 6 public const int FloatingActionButton_rippleColor = 6; - + // aapt resource value: 7 public const int FloatingActionButton_useCompatPadding = 7; - + // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } public static int[] FontFamily = new int[] { 2130903196, @@ -8570,7 +8570,7 @@ public partial class Styleable 2130903199, 2130903200, 2130903201}; - + // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } public static int[] FontFamilyFont = new int[] { 16844082, @@ -8579,58 +8579,58 @@ public partial class Styleable 2130903194, 2130903202, 2130903203}; - + // aapt resource value: 0 public const int FontFamilyFont_android_font = 0; - + // aapt resource value: 2 public const int FontFamilyFont_android_fontStyle = 2; - + // aapt resource value: 1 public const int FontFamilyFont_android_fontWeight = 1; - + // aapt resource value: 3 public const int FontFamilyFont_font = 3; - + // aapt resource value: 4 public const int FontFamilyFont_fontStyle = 4; - + // aapt resource value: 5 public const int FontFamilyFont_fontWeight = 5; - + // aapt resource value: 0 public const int FontFamily_fontProviderAuthority = 0; - + // aapt resource value: 1 public const int FontFamily_fontProviderCerts = 1; - + // aapt resource value: 2 public const int FontFamily_fontProviderFetchStrategy = 2; - + // aapt resource value: 3 public const int FontFamily_fontProviderFetchTimeout = 3; - + // aapt resource value: 4 public const int FontFamily_fontProviderPackage = 4; - + // aapt resource value: 5 public const int FontFamily_fontProviderQuery = 5; - + // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } public static int[] ForegroundLinearLayout = new int[] { 16843017, 16843264, 2130903204}; - + // aapt resource value: 0 public const int ForegroundLinearLayout_android_foreground = 0; - + // aapt resource value: 1 public const int ForegroundLinearLayout_android_foregroundGravity = 1; - + // aapt resource value: 2 public const int ForegroundLinearLayout_foregroundInsidePadding = 2; - + // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } public static int[] LinearLayoutCompat = new int[] { 16842927, @@ -8642,83 +8642,83 @@ public partial class Styleable 2130903166, 2130903257, 2130903314}; - + // aapt resource value: 2 public const int LinearLayoutCompat_android_baselineAligned = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; - + // aapt resource value: 0 public const int LinearLayoutCompat_android_gravity = 0; - + // aapt resource value: 1 public const int LinearLayoutCompat_android_orientation = 1; - + // aapt resource value: 4 public const int LinearLayoutCompat_android_weightSum = 4; - + // aapt resource value: 5 public const int LinearLayoutCompat_divider = 5; - + // aapt resource value: 6 public const int LinearLayoutCompat_dividerPadding = 6; - + // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } public static int[] LinearLayoutCompat_Layout = new int[] { 16842931, 16842996, 16842997, 16843137}; - + // aapt resource value: 0 public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; - + // aapt resource value: 2 public const int LinearLayoutCompat_Layout_android_layout_height = 2; - + // aapt resource value: 3 public const int LinearLayoutCompat_Layout_android_layout_weight = 3; - + // aapt resource value: 1 public const int LinearLayoutCompat_Layout_android_layout_width = 1; - + // aapt resource value: 7 public const int LinearLayoutCompat_measureWithLargestChild = 7; - + // aapt resource value: 8 public const int LinearLayoutCompat_showDividers = 8; - + // aapt resource value: { 0x10102AC,0x10102AD } public static int[] ListPopupWindow = new int[] { 16843436, 16843437}; - + // aapt resource value: 0 public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; - + // aapt resource value: 1 public const int ListPopupWindow_android_dropDownVerticalOffset = 1; - + // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } public static int[] MediaRouteButton = new int[] { 16843071, 16843072, 2130903187, 2130903260}; - + // aapt resource value: 1 public const int MediaRouteButton_android_minHeight = 1; - + // aapt resource value: 0 public const int MediaRouteButton_android_minWidth = 0; - + // aapt resource value: 2 public const int MediaRouteButton_externalRouteEnabledDrawable = 2; - + // aapt resource value: 3 public const int MediaRouteButton_mediaRouteButtonTint = 3; - + // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } public static int[] MenuGroup = new int[] { 16842766, @@ -8727,25 +8727,25 @@ public partial class Styleable 16843230, 16843231, 16843232}; - + // aapt resource value: 5 public const int MenuGroup_android_checkableBehavior = 5; - + // aapt resource value: 0 public const int MenuGroup_android_enabled = 0; - + // aapt resource value: 1 public const int MenuGroup_android_id = 1; - + // aapt resource value: 3 public const int MenuGroup_android_menuCategory = 3; - + // aapt resource value: 4 public const int MenuGroup_android_orderInCategory = 4; - + // aapt resource value: 2 public const int MenuGroup_android_visible = 2; - + // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } public static int[] MenuItem = new int[] { 16842754, @@ -8771,76 +8771,76 @@ public partial class Styleable 2130903276, 2130903313, 2130903395}; - + // aapt resource value: 13 public const int MenuItem_actionLayout = 13; - + // aapt resource value: 14 public const int MenuItem_actionProviderClass = 14; - + // aapt resource value: 15 public const int MenuItem_actionViewClass = 15; - + // aapt resource value: 16 public const int MenuItem_alphabeticModifiers = 16; - + // aapt resource value: 9 public const int MenuItem_android_alphabeticShortcut = 9; - + // aapt resource value: 11 public const int MenuItem_android_checkable = 11; - + // aapt resource value: 3 public const int MenuItem_android_checked = 3; - + // aapt resource value: 1 public const int MenuItem_android_enabled = 1; - + // aapt resource value: 0 public const int MenuItem_android_icon = 0; - + // aapt resource value: 2 public const int MenuItem_android_id = 2; - + // aapt resource value: 5 public const int MenuItem_android_menuCategory = 5; - + // aapt resource value: 10 public const int MenuItem_android_numericShortcut = 10; - + // aapt resource value: 12 public const int MenuItem_android_onClick = 12; - + // aapt resource value: 6 public const int MenuItem_android_orderInCategory = 6; - + // aapt resource value: 7 public const int MenuItem_android_title = 7; - + // aapt resource value: 8 public const int MenuItem_android_titleCondensed = 8; - + // aapt resource value: 4 public const int MenuItem_android_visible = 4; - + // aapt resource value: 17 public const int MenuItem_contentDescription = 17; - + // aapt resource value: 18 public const int MenuItem_iconTint = 18; - + // aapt resource value: 19 public const int MenuItem_iconTintMode = 19; - + // aapt resource value: 20 public const int MenuItem_numericModifiers = 20; - + // aapt resource value: 21 public const int MenuItem_showAsAction = 21; - + // aapt resource value: 22 public const int MenuItem_tooltipText = 22; - + // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } public static int[] MenuView = new int[] { 16842926, @@ -8852,34 +8852,34 @@ public partial class Styleable 16843057, 2130903293, 2130903330}; - + // aapt resource value: 4 public const int MenuView_android_headerBackground = 4; - + // aapt resource value: 2 public const int MenuView_android_horizontalDivider = 2; - + // aapt resource value: 5 public const int MenuView_android_itemBackground = 5; - + // aapt resource value: 6 public const int MenuView_android_itemIconDisabledAlpha = 6; - + // aapt resource value: 1 public const int MenuView_android_itemTextAppearance = 1; - + // aapt resource value: 3 public const int MenuView_android_verticalDivider = 3; - + // aapt resource value: 0 public const int MenuView_android_windowAnimationStyle = 0; - + // aapt resource value: 7 public const int MenuView_preserveIconSpacing = 7; - + // aapt resource value: 8 public const int MenuView_subMenuArrow = 8; - + // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } public static int[] NavigationView = new int[] { 16842964, @@ -8892,70 +8892,70 @@ public partial class Styleable 2130903227, 2130903228, 2130903271}; - + // aapt resource value: 0 public const int NavigationView_android_background = 0; - + // aapt resource value: 1 public const int NavigationView_android_fitsSystemWindows = 1; - + // aapt resource value: 2 public const int NavigationView_android_maxWidth = 2; - + // aapt resource value: 3 public const int NavigationView_elevation = 3; - + // aapt resource value: 4 public const int NavigationView_headerLayout = 4; - + // aapt resource value: 5 public const int NavigationView_itemBackground = 5; - + // aapt resource value: 6 public const int NavigationView_itemIconTint = 6; - + // aapt resource value: 7 public const int NavigationView_itemTextAppearance = 7; - + // aapt resource value: 8 public const int NavigationView_itemTextColor = 8; - + // aapt resource value: 9 public const int NavigationView_menu = 9; - + // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } public static int[] PopupWindow = new int[] { 16843126, 16843465, 2130903277}; - + // aapt resource value: { 0x7F03011D } public static int[] PopupWindowBackgroundState = new int[] { 2130903325}; - + // aapt resource value: 0 public const int PopupWindowBackgroundState_state_above_anchor = 0; - + // aapt resource value: 1 public const int PopupWindow_android_popupAnimationStyle = 1; - + // aapt resource value: 0 public const int PopupWindow_android_popupBackground = 0; - + // aapt resource value: 2 public const int PopupWindow_overlapAnchor = 2; - + // aapt resource value: { 0x7F0300EE,0x7F0300F1 } public static int[] RecycleListView = new int[] { 2130903278, 2130903281}; - + // aapt resource value: 0 public const int RecycleListView_paddingBottomNoButtons = 0; - + // aapt resource value: 1 public const int RecycleListView_paddingTopNoTitle = 1; - + // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } public static int[] RecyclerView = new int[] { 16842948, @@ -8969,54 +8969,54 @@ public partial class Styleable 2130903303, 2130903318, 2130903324}; - + // aapt resource value: 1 public const int RecyclerView_android_descendantFocusability = 1; - + // aapt resource value: 0 public const int RecyclerView_android_orientation = 0; - + // aapt resource value: 2 public const int RecyclerView_fastScrollEnabled = 2; - + // aapt resource value: 3 public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - + // aapt resource value: 4 public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - + // aapt resource value: 5 public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - + // aapt resource value: 6 public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - + // aapt resource value: 7 public const int RecyclerView_layoutManager = 7; - + // aapt resource value: 8 public const int RecyclerView_reverseLayout = 8; - + // aapt resource value: 9 public const int RecyclerView_spanCount = 9; - + // aapt resource value: 10 public const int RecyclerView_stackFromEnd = 10; - + // aapt resource value: { 0x7F0300B6 } public static int[] ScrimInsetsFrameLayout = new int[] { 2130903222}; - + // aapt resource value: 0 public const int ScrimInsetsFrameLayout_insetForeground = 0; - + // aapt resource value: { 0x7F030039 } public static int[] ScrollingViewBehavior_Layout = new int[] { 2130903097}; - + // aapt resource value: 0 public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; - + // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } public static int[] SearchView = new int[] { 16842970, @@ -9036,73 +9036,73 @@ public partial class Styleable 2130903331, 2130903336, 2130903400}; - + // aapt resource value: 0 public const int SearchView_android_focusable = 0; - + // aapt resource value: 3 public const int SearchView_android_imeOptions = 3; - + // aapt resource value: 2 public const int SearchView_android_inputType = 2; - + // aapt resource value: 1 public const int SearchView_android_maxWidth = 1; - + // aapt resource value: 4 public const int SearchView_closeIcon = 4; - + // aapt resource value: 5 public const int SearchView_commitIcon = 5; - + // aapt resource value: 6 public const int SearchView_defaultQueryHint = 6; - + // aapt resource value: 7 public const int SearchView_goIcon = 7; - + // aapt resource value: 8 public const int SearchView_iconifiedByDefault = 8; - + // aapt resource value: 9 public const int SearchView_layout = 9; - + // aapt resource value: 10 public const int SearchView_queryBackground = 10; - + // aapt resource value: 11 public const int SearchView_queryHint = 11; - + // aapt resource value: 12 public const int SearchView_searchHintIcon = 12; - + // aapt resource value: 13 public const int SearchView_searchIcon = 13; - + // aapt resource value: 14 public const int SearchView_submitBackground = 14; - + // aapt resource value: 15 public const int SearchView_suggestionRowLayout = 15; - + // aapt resource value: 16 public const int SearchView_voiceIcon = 16; - + // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } public static int[] SnackbarLayout = new int[] { 16843039, 2130903175, 2130903255}; - + // aapt resource value: 0 public const int SnackbarLayout_android_maxWidth = 0; - + // aapt resource value: 1 public const int SnackbarLayout_elevation = 1; - + // aapt resource value: 2 public const int SnackbarLayout_maxActionInlineWidth = 2; - + // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } public static int[] Spinner = new int[] { 16842930, @@ -9110,22 +9110,22 @@ public partial class Styleable 16843131, 16843362, 2130903291}; - + // aapt resource value: 3 public const int Spinner_android_dropDownWidth = 3; - + // aapt resource value: 0 public const int Spinner_android_entries = 0; - + // aapt resource value: 1 public const int Spinner_android_popupBackground = 1; - + // aapt resource value: 2 public const int Spinner_android_prompt = 2; - + // aapt resource value: 4 public const int Spinner_popupTheme = 4; - + // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } public static int[] SwitchCompat = new int[] { 16843044, @@ -9142,64 +9142,64 @@ public partial class Styleable 2130903396, 2130903397, 2130903398}; - + // aapt resource value: 1 public const int SwitchCompat_android_textOff = 1; - + // aapt resource value: 0 public const int SwitchCompat_android_textOn = 0; - + // aapt resource value: 2 public const int SwitchCompat_android_thumb = 2; - + // aapt resource value: 3 public const int SwitchCompat_showText = 3; - + // aapt resource value: 4 public const int SwitchCompat_splitTrack = 4; - + // aapt resource value: 5 public const int SwitchCompat_switchMinWidth = 5; - + // aapt resource value: 6 public const int SwitchCompat_switchPadding = 6; - + // aapt resource value: 7 public const int SwitchCompat_switchTextAppearance = 7; - + // aapt resource value: 8 public const int SwitchCompat_thumbTextPadding = 8; - + // aapt resource value: 9 public const int SwitchCompat_thumbTint = 9; - + // aapt resource value: 10 public const int SwitchCompat_thumbTintMode = 10; - + // aapt resource value: 11 public const int SwitchCompat_track = 11; - + // aapt resource value: 12 public const int SwitchCompat_trackTint = 12; - + // aapt resource value: 13 public const int SwitchCompat_trackTintMode = 13; - + // aapt resource value: { 0x1010002,0x10100F2,0x101014F } public static int[] TabItem = new int[] { 16842754, 16842994, 16843087}; - + // aapt resource value: 0 public const int TabItem_android_icon = 0; - + // aapt resource value: 1 public const int TabItem_android_layout = 1; - + // aapt resource value: 2 public const int TabItem_android_text = 2; - + // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } public static int[] TabLayout = new int[] { 2130903341, @@ -9218,55 +9218,55 @@ public partial class Styleable 2130903354, 2130903355, 2130903356}; - + // aapt resource value: 0 public const int TabLayout_tabBackground = 0; - + // aapt resource value: 1 public const int TabLayout_tabContentStart = 1; - + // aapt resource value: 2 public const int TabLayout_tabGravity = 2; - + // aapt resource value: 3 public const int TabLayout_tabIndicatorColor = 3; - + // aapt resource value: 4 public const int TabLayout_tabIndicatorHeight = 4; - + // aapt resource value: 5 public const int TabLayout_tabMaxWidth = 5; - + // aapt resource value: 6 public const int TabLayout_tabMinWidth = 6; - + // aapt resource value: 7 public const int TabLayout_tabMode = 7; - + // aapt resource value: 8 public const int TabLayout_tabPadding = 8; - + // aapt resource value: 9 public const int TabLayout_tabPaddingBottom = 9; - + // aapt resource value: 10 public const int TabLayout_tabPaddingEnd = 10; - + // aapt resource value: 11 public const int TabLayout_tabPaddingStart = 11; - + // aapt resource value: 12 public const int TabLayout_tabPaddingTop = 12; - + // aapt resource value: 13 public const int TabLayout_tabSelectedTextColor = 13; - + // aapt resource value: 14 public const int TabLayout_tabTextAppearance = 14; - + // aapt resource value: 15 public const int TabLayout_tabTextColor = 15; - + // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } public static int[] TextAppearance = new int[] { 16842901, @@ -9282,46 +9282,46 @@ public partial class Styleable 16843692, 2130903195, 2130903357}; - + // aapt resource value: 10 public const int TextAppearance_android_fontFamily = 10; - + // aapt resource value: 6 public const int TextAppearance_android_shadowColor = 6; - + // aapt resource value: 7 public const int TextAppearance_android_shadowDx = 7; - + // aapt resource value: 8 public const int TextAppearance_android_shadowDy = 8; - + // aapt resource value: 9 public const int TextAppearance_android_shadowRadius = 9; - + // aapt resource value: 3 public const int TextAppearance_android_textColor = 3; - + // aapt resource value: 4 public const int TextAppearance_android_textColorHint = 4; - + // aapt resource value: 5 public const int TextAppearance_android_textColorLink = 5; - + // aapt resource value: 0 public const int TextAppearance_android_textSize = 0; - + // aapt resource value: 2 public const int TextAppearance_android_textStyle = 2; - + // aapt resource value: 1 public const int TextAppearance_android_typeface = 1; - + // aapt resource value: 11 public const int TextAppearance_fontFamily = 11; - + // aapt resource value: 12 public const int TextAppearance_textAllCaps = 12; - + // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } public static int[] TextInputLayout = new int[] { 16842906, @@ -9340,55 +9340,55 @@ public partial class Styleable 2130903287, 2130903288, 2130903289}; - + // aapt resource value: 1 public const int TextInputLayout_android_hint = 1; - + // aapt resource value: 0 public const int TextInputLayout_android_textColorHint = 0; - + // aapt resource value: 2 public const int TextInputLayout_counterEnabled = 2; - + // aapt resource value: 3 public const int TextInputLayout_counterMaxLength = 3; - + // aapt resource value: 4 public const int TextInputLayout_counterOverflowTextAppearance = 4; - + // aapt resource value: 5 public const int TextInputLayout_counterTextAppearance = 5; - + // aapt resource value: 6 public const int TextInputLayout_errorEnabled = 6; - + // aapt resource value: 7 public const int TextInputLayout_errorTextAppearance = 7; - + // aapt resource value: 8 public const int TextInputLayout_hintAnimationEnabled = 8; - + // aapt resource value: 9 public const int TextInputLayout_hintEnabled = 9; - + // aapt resource value: 10 public const int TextInputLayout_hintTextAppearance = 10; - + // aapt resource value: 11 public const int TextInputLayout_passwordToggleContentDescription = 11; - + // aapt resource value: 12 public const int TextInputLayout_passwordToggleDrawable = 12; - + // aapt resource value: 13 public const int TextInputLayout_passwordToggleEnabled = 13; - + // aapt resource value: 14 public const int TextInputLayout_passwordToggleTint = 14; - + // aapt resource value: 15 public const int TextInputLayout_passwordToggleTintMode = 15; - + // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } public static int[] Toolbar = new int[] { 16842927, @@ -9420,94 +9420,94 @@ public partial class Styleable 2130903386, 2130903387, 2130903388}; - + // aapt resource value: 0 public const int Toolbar_android_gravity = 0; - + // aapt resource value: 1 public const int Toolbar_android_minHeight = 1; - + // aapt resource value: 2 public const int Toolbar_buttonGravity = 2; - + // aapt resource value: 3 public const int Toolbar_collapseContentDescription = 3; - + // aapt resource value: 4 public const int Toolbar_collapseIcon = 4; - + // aapt resource value: 5 public const int Toolbar_contentInsetEnd = 5; - + // aapt resource value: 6 public const int Toolbar_contentInsetEndWithActions = 6; - + // aapt resource value: 7 public const int Toolbar_contentInsetLeft = 7; - + // aapt resource value: 8 public const int Toolbar_contentInsetRight = 8; - + // aapt resource value: 9 public const int Toolbar_contentInsetStart = 9; - + // aapt resource value: 10 public const int Toolbar_contentInsetStartWithNavigation = 10; - + // aapt resource value: 11 public const int Toolbar_logo = 11; - + // aapt resource value: 12 public const int Toolbar_logoDescription = 12; - + // aapt resource value: 13 public const int Toolbar_maxButtonHeight = 13; - + // aapt resource value: 14 public const int Toolbar_navigationContentDescription = 14; - + // aapt resource value: 15 public const int Toolbar_navigationIcon = 15; - + // aapt resource value: 16 public const int Toolbar_popupTheme = 16; - + // aapt resource value: 17 public const int Toolbar_subtitle = 17; - + // aapt resource value: 18 public const int Toolbar_subtitleTextAppearance = 18; - + // aapt resource value: 19 public const int Toolbar_subtitleTextColor = 19; - + // aapt resource value: 20 public const int Toolbar_title = 20; - + // aapt resource value: 21 public const int Toolbar_titleMargin = 21; - + // aapt resource value: 22 public const int Toolbar_titleMarginBottom = 22; - + // aapt resource value: 23 public const int Toolbar_titleMarginEnd = 23; - + // aapt resource value: 26 public const int Toolbar_titleMargins = 26; - + // aapt resource value: 24 public const int Toolbar_titleMarginStart = 24; - + // aapt resource value: 25 public const int Toolbar_titleMarginTop = 25; - + // aapt resource value: 27 public const int Toolbar_titleTextAppearance = 27; - + // aapt resource value: 28 public const int Toolbar_titleTextColor = 28; - + // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } public static int[] View = new int[] { 16842752, @@ -9515,57 +9515,57 @@ public partial class Styleable 2130903279, 2130903280, 2130903369}; - + // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } public static int[] ViewBackgroundHelper = new int[] { 16842964, 2130903092, 2130903093}; - + // aapt resource value: 0 public const int ViewBackgroundHelper_android_background = 0; - + // aapt resource value: 1 public const int ViewBackgroundHelper_backgroundTint = 1; - + // aapt resource value: 2 public const int ViewBackgroundHelper_backgroundTintMode = 2; - + // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } public static int[] ViewStubCompat = new int[] { 16842960, 16842994, 16842995}; - + // aapt resource value: 0 public const int ViewStubCompat_android_id = 0; - + // aapt resource value: 2 public const int ViewStubCompat_android_inflatedId = 2; - + // aapt resource value: 1 public const int ViewStubCompat_android_layout = 1; - + // aapt resource value: 1 public const int View_android_focusable = 1; - + // aapt resource value: 0 public const int View_android_theme = 0; - + // aapt resource value: 2 public const int View_paddingEnd = 2; - + // aapt resource value: 3 public const int View_paddingStart = 3; - + // aapt resource value: 4 public const int View_theme = 4; - + static Styleable() { global::Android.Runtime.ResourceIdManager.UpdateIdValues(); } - + private Styleable() { } From 5ed6c661ca29bf13a3ce45887b76782b39f2074a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 12:00:42 -0700 Subject: [PATCH 476/499] Plumb nullable ApplicationInfo into HTTP config --- .../Integrations/HttpConfigurationBuilder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index ce8458af..4f138db4 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -237,7 +237,7 @@ public HttpConfigurationBuilder Wrapper(string wrapperName, string wrapperVersio /// Key for authenticating with LD service /// Application Info for this application environment /// an - public HttpConfiguration CreateHttpConfiguration(string authKey, ApplicationInfo applicationInfo) => + public HttpConfiguration CreateHttpConfiguration(string authKey, ApplicationInfo? applicationInfo) => new HttpConfiguration( MakeHttpProperties(authKey, applicationInfo), _messageHandler, @@ -255,7 +255,7 @@ public LdValue DescribeConfiguration(LdClientContext context) => // which is more correct, but we can't really set ReadTimeout in this SDK .Build(); - private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo applicationInfo) + private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo? applicationInfo) { Func handlerFn; if (_messageHandler is null) @@ -273,9 +273,13 @@ private HttpProperties MakeHttpProperties(string authToken, ApplicationInfo appl .WithHttpMessageHandlerFactory(handlerFn) .WithProxy(_proxy) .WithUserAgent(SdkPackage.UserAgent) - .WithApplicationTags(applicationInfo) .WithWrapper(_wrapperName, _wrapperVersion); + if (applicationInfo.HasValue) + { + httpProperties = httpProperties.WithApplicationTags(applicationInfo.Value); + } + foreach (var kv in _customHeaders) { httpProperties = httpProperties.WithHeader(kv.Key, kv.Value); From ae7ce4bbf924c42c68ba4f47991aef9a28717996 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 12:37:17 -0700 Subject: [PATCH 477/499] don't add null properties into auto env contexts --- .../Internal/AutoEnvContextDecorator.cs | 33 +++++++++++-------- .../Internal/AutoEnvContextDecoratorTest.cs | 17 +--------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 9586df46..c50050f7 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -3,6 +3,7 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.Client.Internal.DataStores; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; namespace LaunchDarkly.Sdk.Client.Internal { @@ -95,7 +96,10 @@ private static Context MakeLdContextFromRecipe(ContextRecipe recipe) var builder = Context.Builder(recipe.Kind, recipe.KeyCallable.Invoke()); foreach (var entry in recipe.AttributeCallables) { - builder.Set(entry.Key, entry.Value.Invoke()); + var value = entry.Value.Invoke(); + if (!value.IsNull) { + builder.Set(entry.Key, value); + } } return builder.Build(); @@ -107,10 +111,10 @@ private List MakeRecipeList() var applicationCallables = new Dictionary> { { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationId) }, - { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationName) }, - { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersion) }, - { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) }, + { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationId) }, + { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationName) }, + { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersion) }, + { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersionName) }, { ATTR_LOCALE, () => LdValue.Of(_environmentReporter.Locale) } }; @@ -118,15 +122,18 @@ private List MakeRecipeList() var deviceCallables = new Dictionary> { { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_MANUFACTURER, () => LdValue.Of(_environmentReporter.DeviceInfo.Manufacturer) }, - { ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo.Model) }, + { ATTR_MANUFACTURER, () => LdValue.Of(_environmentReporter.DeviceInfo?.Manufacturer) }, + { ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo?.Model) }, { ATTR_OS, () => - LdValue.BuildObject() - .Add(ATTR_FAMILY, _environmentReporter.OsInfo.Family) - .Add(ATTR_NAME, _environmentReporter.OsInfo.Name) - .Add(ATTR_VERSION, _environmentReporter.OsInfo.Version) - .Build() + _environmentReporter.OsInfo == null || + _environmentReporter.OsInfo.Equals(new OsInfo(null, null, null)) + ? LdValue.Null + : LdValue.BuildObject() + .Add(ATTR_FAMILY, _environmentReporter.OsInfo?.Family) + .Add(ATTR_NAME, _environmentReporter.OsInfo?.Name) + .Add(ATTR_VERSION, _environmentReporter.OsInfo?.Version) + .Build() } }; @@ -135,7 +142,7 @@ private List MakeRecipeList() new ContextRecipe( ldApplicationKind, () => Base64.UrlSafeSha256Hash( - _environmentReporter.ApplicationInfo.ApplicationId ?? "" + _environmentReporter.ApplicationInfo?.ApplicationId ?? "" ), applicationCallables ), diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 2a5e1a6e..0e33da3e 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -22,26 +22,18 @@ public void AdheresToSchemaTest() // Create the expected context after the code runs // because there will be persistence side effects var applicationKind = ContextKind.Of(AutoEnvContextDecorator.LD_APPLICATION_KIND); - var expectedApplicationKey = Base64.UrlSafeSha256Hash(envReporter.ApplicationInfo.ApplicationId); + var expectedApplicationKey = Base64.UrlSafeSha256Hash(envReporter.ApplicationInfo?.ApplicationId ?? ""); var expectedAppContext = Context.Builder(applicationKind, expectedApplicationKey) .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) .Set(AutoEnvContextDecorator.ATTR_ID, SdkPackage.Name) .Set(AutoEnvContextDecorator.ATTR_NAME, SdkPackage.Name) .Set(AutoEnvContextDecorator.ATTR_VERSION, SdkPackage.Version) .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, SdkPackage.Version) - .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") .Build(); var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") - .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") - .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() - .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") - .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") - .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") - .Build()) .Build(); var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedAppContext).Add(expectedDeviceContext) @@ -84,13 +76,6 @@ public void DoesNotOverwriteCustomerDataTest() var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") - .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") - .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() - .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") - .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") - .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") - .Build()) .Build(); var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedDeviceContext).Build(); From 3c87760c2e280533ea844222a494d96fa01f8dcb Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 12:55:29 -0700 Subject: [PATCH 478/499] fix empty return tags --- src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs | 2 +- .../Integrations/PersistenceConfigurationBuilder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs index 75b9ed72..7a3ed36e 100644 --- a/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/ConfigurationBuilder.cs @@ -131,7 +131,7 @@ public ConfigurationBuilder ApplicationInfo(ApplicationInfoBuilder applicationIn /// more details. /// /// Enable / disable Auto Environment Attributes functionality. - /// + /// the same builder public ConfigurationBuilder AutoEnvironmentAttributes(AutoEnvAttributes autoEnvAttributes) { _autoEnvAttributes = autoEnvAttributes == AutoEnvAttributes.Enabled; // map enum to boolean diff --git a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs index 972c8cf4..1d88a15f 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/PersistenceConfigurationBuilder.cs @@ -84,7 +84,7 @@ public PersistenceConfigurationBuilder Storage(IComponentConfigurer /// /// - /// + /// the builder public PersistenceConfigurationBuilder MaxCachedContexts(int maxCachedContexts) { _maxCachedContexts = maxCachedContexts; From 73bc1827f3545cc755a8f57bddd6d671084af668 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 12:59:33 -0700 Subject: [PATCH 479/499] nits --- .../Internal/AutoEnvContextDecorator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 9586df46..c8e3eefc 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -58,7 +58,7 @@ public Context DecorateContext(Context context) var builder = Context.MultiBuilder(); builder.Add(context); - foreach (ContextRecipe recipe in MakeRecipeList()) + foreach (var recipe in MakeRecipeList()) { if (!context.TryGetContextByKind(recipe.Kind, out _)) { @@ -75,7 +75,7 @@ public Context DecorateContext(Context context) return builder.Build(); } - private class ContextRecipe + private readonly struct ContextRecipe { public ContextKind Kind { get; } public Func KeyCallable { get; } From d3da78a7ea9fc6a99fbe3e7eddc10c3cfc401924 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 12 Oct 2023 13:03:58 -0700 Subject: [PATCH 480/499] rename attributes --- .../Internal/AutoEnvContextDecorator.cs | 56 +++++++++---------- .../Internal/AutoEnvContextDecoratorTest.cs | 48 ++++++++-------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index c8e3eefc..1722009b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -12,19 +12,19 @@ namespace LaunchDarkly.Sdk.Client.Internal /// internal class AutoEnvContextDecorator { - internal const string LD_APPLICATION_KIND = "ld_application"; - internal const string LD_DEVICE_KIND = "ld_device"; - internal const string ATTR_ID = "id"; - internal const string ATTR_NAME = "name"; - internal const string ATTR_VERSION = "version"; - internal const string ATTR_VERSION_NAME = "versionName"; - internal const string ATTR_MANUFACTURER = "manufacturer"; - internal const string ATTR_MODEL = "model"; - internal const string ATTR_LOCALE = "locale"; - internal const string ATTR_OS = "os"; - internal const string ATTR_FAMILY = "family"; - internal const string ENV_ATTRIBUTES_VERSION = "envAttributesVersion"; - internal const string SPEC_VERSION = "1.0"; + internal const string LdApplicationKind = "ld_application"; + internal const string LdDeviceKind = "ld_device"; + internal const string AttrId = "id"; + internal const string AttrName = "name"; + internal const string AttrVersion = "version"; + internal const string AttrVersionName = "versionName"; + internal const string AttrManufacturer = "manufacturer"; + internal const string AttrModel = "model"; + internal const string AttrLocale = "locale"; + internal const string AttrOs = "os"; + internal const string AttrFamily = "family"; + internal const string EnvAttributesVersion = "envAttributesVersion"; + internal const string SpecVersion = "1.0"; private readonly PersistentDataStoreWrapper _persistentData; private readonly IEnvironmentReporter _environmentReporter; @@ -103,29 +103,29 @@ private static Context MakeLdContextFromRecipe(ContextRecipe recipe) private List MakeRecipeList() { - var ldApplicationKind = ContextKind.Of(LD_APPLICATION_KIND); + var ldApplicationKind = ContextKind.Of(LdApplicationKind); var applicationCallables = new Dictionary> { - { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationId) }, - { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationName) }, - { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersion) }, - { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) }, - { ATTR_LOCALE, () => LdValue.Of(_environmentReporter.Locale) } + { EnvAttributesVersion, () => LdValue.Of(SpecVersion) }, + { AttrId, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationId) }, + { AttrName, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationName) }, + { AttrVersion, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersion) }, + { AttrVersionName, () => LdValue.Of(_environmentReporter.ApplicationInfo.ApplicationVersionName) }, + { AttrLocale, () => LdValue.Of(_environmentReporter.Locale) } }; - var ldDeviceKind = ContextKind.Of(LD_DEVICE_KIND); + var ldDeviceKind = ContextKind.Of(LdDeviceKind); var deviceCallables = new Dictionary> { - { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_MANUFACTURER, () => LdValue.Of(_environmentReporter.DeviceInfo.Manufacturer) }, - { ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo.Model) }, + { EnvAttributesVersion, () => LdValue.Of(SpecVersion) }, + { AttrManufacturer, () => LdValue.Of(_environmentReporter.DeviceInfo.Manufacturer) }, + { AttrModel, () => LdValue.Of(_environmentReporter.DeviceInfo.Model) }, { - ATTR_OS, () => + AttrOs, () => LdValue.BuildObject() - .Add(ATTR_FAMILY, _environmentReporter.OsInfo.Family) - .Add(ATTR_NAME, _environmentReporter.OsInfo.Name) - .Add(ATTR_VERSION, _environmentReporter.OsInfo.Version) + .Add(AttrFamily, _environmentReporter.OsInfo.Family) + .Add(AttrName, _environmentReporter.OsInfo.Name) + .Add(AttrVersion, _environmentReporter.OsInfo.Version) .Build() } }; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 2a5e1a6e..225d3bd7 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -21,26 +21,26 @@ public void AdheresToSchemaTest() // Create the expected context after the code runs // because there will be persistence side effects - var applicationKind = ContextKind.Of(AutoEnvContextDecorator.LD_APPLICATION_KIND); + var applicationKind = ContextKind.Of(AutoEnvContextDecorator.LdApplicationKind); var expectedApplicationKey = Base64.UrlSafeSha256Hash(envReporter.ApplicationInfo.ApplicationId); var expectedAppContext = Context.Builder(applicationKind, expectedApplicationKey) - .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Set(AutoEnvContextDecorator.ATTR_ID, SdkPackage.Name) - .Set(AutoEnvContextDecorator.ATTR_NAME, SdkPackage.Name) - .Set(AutoEnvContextDecorator.ATTR_VERSION, SdkPackage.Version) - .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, SdkPackage.Version) - .Set(AutoEnvContextDecorator.ATTR_LOCALE, "unknown") + .Set(AutoEnvContextDecorator.EnvAttributesVersion, AutoEnvContextDecorator.SpecVersion) + .Set(AutoEnvContextDecorator.AttrId, SdkPackage.Name) + .Set(AutoEnvContextDecorator.AttrName, SdkPackage.Name) + .Set(AutoEnvContextDecorator.AttrVersion, SdkPackage.Version) + .Set(AutoEnvContextDecorator.AttrVersionName, SdkPackage.Version) + .Set(AutoEnvContextDecorator.AttrLocale, "unknown") .Build(); - var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); + var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LdDeviceKind); var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) - .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") - .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") - .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() - .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") - .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") - .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") + .Set(AutoEnvContextDecorator.EnvAttributesVersion, AutoEnvContextDecorator.SpecVersion) + .Set(AutoEnvContextDecorator.AttrManufacturer, "unknown") + .Set(AutoEnvContextDecorator.AttrModel, "unknown") + .Set(AutoEnvContextDecorator.AttrOs, LdValue.BuildObject() + .Add(AutoEnvContextDecorator.AttrFamily, "unknown") + .Add(AutoEnvContextDecorator.AttrName, "unknown") + .Add(AutoEnvContextDecorator.AttrVersion, "unknown") .Build()) .Build(); @@ -64,7 +64,7 @@ public void CustomCultureInPlatformLayerIsPropagated() Context outContext; - Assert.True(output.TryGetContextByKind(new ContextKind(AutoEnvContextDecorator.LD_APPLICATION_KIND), out outContext)); + Assert.True(output.TryGetContextByKind(new ContextKind(AutoEnvContextDecorator.LdApplicationKind), out outContext)); Assert.Equal("en-GB", outContext.GetValue("locale").AsString); } @@ -81,15 +81,15 @@ public void DoesNotOverwriteCustomerDataTest() var output = decoratorUnderTest.DecorateContext(input); // Create the expected device context after the code runs because of persistence side effects - var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); + var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LdDeviceKind); var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) - .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Set(AutoEnvContextDecorator.ATTR_MANUFACTURER, "unknown") - .Set(AutoEnvContextDecorator.ATTR_MODEL, "unknown") - .Set(AutoEnvContextDecorator.ATTR_OS, LdValue.BuildObject() - .Add(AutoEnvContextDecorator.ATTR_FAMILY, "unknown") - .Add(AutoEnvContextDecorator.ATTR_NAME, "unknown") - .Add(AutoEnvContextDecorator.ATTR_VERSION, "unknown") + .Set(AutoEnvContextDecorator.EnvAttributesVersion, AutoEnvContextDecorator.SpecVersion) + .Set(AutoEnvContextDecorator.AttrManufacturer, "unknown") + .Set(AutoEnvContextDecorator.AttrModel, "unknown") + .Set(AutoEnvContextDecorator.AttrOs, LdValue.BuildObject() + .Add(AutoEnvContextDecorator.AttrFamily, "unknown") + .Add(AutoEnvContextDecorator.AttrName, "unknown") + .Add(AutoEnvContextDecorator.AttrVersion, "unknown") .Build()) .Build(); From 28c67b9d275c67271adf06b99cf9afeb69689a5e Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Fri, 13 Oct 2023 09:06:07 -0500 Subject: [PATCH 481/499] refactor: Updated AutoEnvContextDecorator recipe system to use nodes and TryWrite to more elegantly handle omitting null values from contexts. --- .../Internal/AutoEnvContextDecorator.cs | 160 +++++++++++++----- .../LaunchDarkly.ClientSdk.csproj | 4 +- .../Internal/AutoEnvContextDecoratorTest.cs | 17 +- 3 files changed, 128 insertions(+), 53 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index c50050f7..5728a23b 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -3,7 +3,6 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.Client.Internal.DataStores; -using LaunchDarkly.Sdk.EnvReporting.LayerModels; namespace LaunchDarkly.Sdk.Client.Internal { @@ -59,12 +58,12 @@ public Context DecorateContext(Context context) var builder = Context.MultiBuilder(); builder.Add(context); - foreach (ContextRecipe recipe in MakeRecipeList()) + foreach (var recipe in MakeRecipeList()) { if (!context.TryGetContextByKind(recipe.Kind, out _)) { // only add contexts for recipe Kinds not already in context to avoid overwriting data. - builder.Add(MakeLdContextFromRecipe(recipe)); + recipe.TryWrite(builder); } else { @@ -80,61 +79,56 @@ private class ContextRecipe { public ContextKind Kind { get; } public Func KeyCallable { get; } - public Dictionary> AttributeCallables { get; } + public List RecipeNodes { get; } - public ContextRecipe(ContextKind kind, Func keyCallable, - Dictionary> attributeCallables) + public ContextRecipe(ContextKind kind, Func keyCallable, List recipeNodes) { Kind = kind; KeyCallable = keyCallable; - AttributeCallables = attributeCallables; + RecipeNodes = recipeNodes; } - } - private static Context MakeLdContextFromRecipe(ContextRecipe recipe) - { - var builder = Context.Builder(recipe.Kind, recipe.KeyCallable.Invoke()); - foreach (var entry in recipe.AttributeCallables) + public void TryWrite(ContextMultiBuilder multiBuilder) { - var value = entry.Value.Invoke(); - if (!value.IsNull) { - builder.Set(entry.Key, value); + var contextBuilder = Context.Builder(Kind, KeyCallable.Invoke()); + var adaptedBuilder = new ContextBuilderAdapter(contextBuilder); + var wrote = false; + RecipeNodes.ForEach(it => { wrote |= it.TryWrite(adaptedBuilder); }); + if (wrote) + { + contextBuilder.Set(ENV_ATTRIBUTES_VERSION, SPEC_VERSION); + multiBuilder.Add(contextBuilder.Build()); } } - - return builder.Build(); } private List MakeRecipeList() { var ldApplicationKind = ContextKind.Of(LD_APPLICATION_KIND); - var applicationCallables = new Dictionary> + var applicationNodes = new List { - { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationId) }, - { ATTR_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationName) }, - { ATTR_VERSION, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersion) }, - { ATTR_VERSION_NAME, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersionName) }, - { ATTR_LOCALE, () => LdValue.Of(_environmentReporter.Locale) } + new ConcreteRecipeNode(ATTR_ID, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationId)), + new ConcreteRecipeNode(ATTR_NAME, + () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationName)), + new ConcreteRecipeNode(ATTR_VERSION, + () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersion)), + new ConcreteRecipeNode(ATTR_VERSION_NAME, + () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersionName)), + new ConcreteRecipeNode(ATTR_LOCALE, () => LdValue.Of(_environmentReporter.Locale)), }; var ldDeviceKind = ContextKind.Of(LD_DEVICE_KIND); - var deviceCallables = new Dictionary> + var deviceNodes = new List { - { ENV_ATTRIBUTES_VERSION, () => LdValue.Of(SPEC_VERSION) }, - { ATTR_MANUFACTURER, () => LdValue.Of(_environmentReporter.DeviceInfo?.Manufacturer) }, - { ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo?.Model) }, + new ConcreteRecipeNode(ATTR_MANUFACTURER, + () => LdValue.Of(_environmentReporter.DeviceInfo?.Manufacturer)), + new ConcreteRecipeNode(ATTR_MODEL, () => LdValue.Of(_environmentReporter.DeviceInfo?.Model)), + new CompositeRecipeNode(ATTR_OS, new List { - ATTR_OS, () => - _environmentReporter.OsInfo == null || - _environmentReporter.OsInfo.Equals(new OsInfo(null, null, null)) - ? LdValue.Null - : LdValue.BuildObject() - .Add(ATTR_FAMILY, _environmentReporter.OsInfo?.Family) - .Add(ATTR_NAME, _environmentReporter.OsInfo?.Name) - .Add(ATTR_VERSION, _environmentReporter.OsInfo?.Version) - .Build() - } + new ConcreteRecipeNode(ATTR_FAMILY, () => LdValue.Of(_environmentReporter.OsInfo?.Family)), + new ConcreteRecipeNode(ATTR_NAME, () => LdValue.Of(_environmentReporter.OsInfo?.Name)), + new ConcreteRecipeNode(ATTR_VERSION, () => LdValue.Of(_environmentReporter.OsInfo?.Version)), + }) }; return new List @@ -144,12 +138,12 @@ private List MakeRecipeList() () => Base64.UrlSafeSha256Hash( _environmentReporter.ApplicationInfo?.ApplicationId ?? "" ), - applicationCallables + applicationNodes ), new ContextRecipe( ldDeviceKind, () => GetOrCreateAutoContextKey(_persistentData, ldDeviceKind), - deviceCallables + deviceNodes ) }; } @@ -164,5 +158,91 @@ private string GetOrCreateAutoContextKey(PersistentDataStoreWrapper store, Conte } return uniqueId; } + + private interface ISettableMap + { + void Set(string attributeName, LdValue value); + } + + private interface IRecipeNode + { + bool TryWrite(ISettableMap settableMap); + } + + private class CompositeRecipeNode : IRecipeNode + { + private readonly string _name; + private readonly List _nodes; + + public CompositeRecipeNode(string name, List nodes) + { + _name = name; + _nodes = nodes; + } + + public bool TryWrite(ISettableMap map) + { + var wrote = false; + var ldValueBuilder = LdValue.BuildObject(); + var adaptedBuilder = new ObjectBuilderAdapter(ldValueBuilder); + _nodes.ForEach(it => { wrote |= it.TryWrite(adaptedBuilder); }); + if (wrote) + { + map.Set(_name, ldValueBuilder.Build()); + } + + return wrote; + } + } + + private class ConcreteRecipeNode : IRecipeNode + { + private readonly string _name; + private readonly Func _valueFunc; + + public ConcreteRecipeNode(string name, Func valueFunc) + { + _name = name; + _valueFunc = valueFunc; + } + + public bool TryWrite(ISettableMap map) + { + var result = _valueFunc.Invoke(); + if (!result.HasValue || result.Value == LdValue.Null) return false; + map.Set(_name, result.Value); + return true; + } + } + + private class ObjectBuilderAdapter : ISettableMap + { + private readonly LdValue.ObjectBuilder _underlyingBuilder; + + public ObjectBuilderAdapter(LdValue.ObjectBuilder builder) + { + _underlyingBuilder = builder; + } + + public void Set(string attributeName, LdValue value) + { + _underlyingBuilder.Set(attributeName, value); + } + } + + private class ContextBuilderAdapter : ISettableMap + { + private readonly ContextBuilder _underlyingBuilder; + + public ContextBuilderAdapter(ContextBuilder builder) + { + _underlyingBuilder = builder; + } + + public void Set(string attributeName, LdValue value) + { + _underlyingBuilder.Set(attributeName, value); + } + } } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 36e56361..1e3b32dd 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 0e33da3e..3513a65d 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -31,12 +31,10 @@ public void AdheresToSchemaTest() .Set(AutoEnvContextDecorator.ATTR_VERSION_NAME, SdkPackage.Version) .Build(); - var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); - var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) - .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Build(); + // TODO: We should include ld_device in these tests. I think that may require a way to mock the platform + // layer or run on an actual platform that supports getting device information such as Android. - var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedAppContext).Add(expectedDeviceContext) + var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedAppContext) .Build(); Assert.Equal(expectedOutput, output); @@ -72,13 +70,10 @@ public void DoesNotOverwriteCustomerDataTest() .Set("dontOverwriteMeBro", "really bro").Build(); var output = decoratorUnderTest.DecorateContext(input); - // Create the expected device context after the code runs because of persistence side effects - var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LD_DEVICE_KIND); - var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) - .Set(AutoEnvContextDecorator.ENV_ATTRIBUTES_VERSION, AutoEnvContextDecorator.SPEC_VERSION) - .Build(); + // TODO: We should include ld_device in these tests. I think that may require a way to mock the platform + // layer or run on an actual platform that supports getting device information such as Android. - var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedDeviceContext).Build(); + var expectedOutput = Context.MultiBuilder().Add(input).Build(); Assert.Equal(expectedOutput, output); } From 6410d81ef39fc6b710bf435a28c0f143ae40d7bf Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Fri, 13 Oct 2023 09:09:33 -0500 Subject: [PATCH 482/499] reverting accidental csproj commit --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 1e3b32dd..36e56361 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + From 5f1df2efed5ee68b0d737cfeb9f5cc1dbe93b5cc Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 13 Oct 2023 13:49:52 -0700 Subject: [PATCH 483/499] add test for ld_device schema --- .../Internal/AutoEnvContextDecoratorTest.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 2b2a6494..1ec2a081 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -2,6 +2,7 @@ using LaunchDarkly.Sdk.Client.Internal.DataStores; using LaunchDarkly.Sdk.Client.Subsystems; using LaunchDarkly.Sdk.EnvReporting; +using LaunchDarkly.Sdk.EnvReporting.LayerModels; using Xunit; namespace LaunchDarkly.Sdk.Client.Internal @@ -11,7 +12,13 @@ public class AutoEnvContextDecoratorTest : BaseTest [Fact] public void AdheresToSchemaTest() { - var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer).Build(); + const string osFamily = "family_foo"; + const string osName = "name_bar"; + const string osVersion = null; + + var envReporter = new EnvironmentReporterBuilder().SetSdkLayer(SdkAttributes.Layer) + .SetPlatformLayer(new Layer(null, new OsInfo(osFamily, osName, osVersion), null, null)).Build(); + var store = MakeMockDataStoreWrapper(); var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); @@ -31,10 +38,16 @@ public void AdheresToSchemaTest() .Set(AutoEnvContextDecorator.AttrVersionName, SdkPackage.Version) .Build(); - // TODO: We should include ld_device in these tests. I think that may require a way to mock the platform - // layer or run on an actual platform that supports getting device information such as Android. + var deviceKind = ContextKind.Of(AutoEnvContextDecorator.LdDeviceKind); + var expectedDeviceContext = Context.Builder(deviceKind, store.GetGeneratedContextKey(deviceKind)) + .Set(AutoEnvContextDecorator.EnvAttributesVersion, AutoEnvContextDecorator.SpecVersion) + .Set(AutoEnvContextDecorator.AttrOs, + LdValue.BuildObject().Set("family", osFamily).Set("name", osName).Build()).Build(); - var expectedOutput = Context.MultiBuilder().Add(input).Add(expectedAppContext) + var expectedOutput = Context.MultiBuilder() + .Add(input) + .Add(expectedAppContext) + .Add(expectedDeviceContext) .Build(); Assert.Equal(expectedOutput, output); @@ -54,7 +67,8 @@ public void CustomCultureInPlatformLayerIsPropagated() Context outContext; - Assert.True(output.TryGetContextByKind(new ContextKind(AutoEnvContextDecorator.LdApplicationKind), out outContext)); + Assert.True(output.TryGetContextByKind(new ContextKind(AutoEnvContextDecorator.LdApplicationKind), + out outContext)); Assert.Equal("en-GB", outContext.GetValue("locale").AsString); } From 035328964d860ee5b7d3714daf446138622eb0e5 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 13 Oct 2023 14:59:33 -0700 Subject: [PATCH 484/499] unify Node classes into single class --- .../Internal/AutoEnvContextDecorator.cs | 101 ++++++++---------- .../LdClientTests.cs | 8 +- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs index 0ae9f1f3..c41670aa 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/AutoEnvContextDecorator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using LaunchDarkly.Logging; using LaunchDarkly.Sdk.EnvReporting; using LaunchDarkly.Sdk.Client.Internal.DataStores; @@ -78,10 +79,10 @@ public Context DecorateContext(Context context) private readonly struct ContextRecipe { public ContextKind Kind { get; } - public Func KeyCallable { get; } - public List RecipeNodes { get; } + private Func KeyCallable { get; } + private List RecipeNodes { get; } - public ContextRecipe(ContextKind kind, Func keyCallable, List recipeNodes) + public ContextRecipe(ContextKind kind, Func keyCallable, List recipeNodes) { Kind = kind; KeyCallable = keyCallable; @@ -92,9 +93,7 @@ public void TryWrite(ContextMultiBuilder multiBuilder) { var contextBuilder = Context.Builder(Kind, KeyCallable.Invoke()); var adaptedBuilder = new ContextBuilderAdapter(contextBuilder); - var wrote = false; - RecipeNodes.ForEach(it => { wrote |= it.TryWrite(adaptedBuilder); }); - if (wrote) + if (RecipeNodes.Aggregate(false, (wrote, node) => wrote | node.TryWrite(adaptedBuilder))) { contextBuilder.Set(EnvAttributesVersion, SpecVersion); multiBuilder.Add(contextBuilder.Build()); @@ -105,29 +104,29 @@ public void TryWrite(ContextMultiBuilder multiBuilder) private List MakeRecipeList() { var ldApplicationKind = ContextKind.Of(LdApplicationKind); - var applicationNodes = new List + var applicationNodes = new List { - new ConcreteRecipeNode(AttrId, () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationId)), - new ConcreteRecipeNode(AttrName, - () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationName)), - new ConcreteRecipeNode(AttrVersion, - () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersion)), - new ConcreteRecipeNode(AttrVersionName, - () => LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersionName)), - new ConcreteRecipeNode(AttrLocale, () => LdValue.Of(_environmentReporter.Locale)), + new Node(AttrId, LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationId)), + new Node(AttrName, + LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationName)), + new Node(AttrVersion, + LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersion)), + new Node(AttrVersionName, + LdValue.Of(_environmentReporter.ApplicationInfo?.ApplicationVersionName)), + new Node(AttrLocale, LdValue.Of(_environmentReporter.Locale)), }; var ldDeviceKind = ContextKind.Of(LdDeviceKind); - var deviceNodes = new List + var deviceNodes = new List { - new ConcreteRecipeNode(AttrManufacturer, - () => LdValue.Of(_environmentReporter.DeviceInfo?.Manufacturer)), - new ConcreteRecipeNode(AttrModel, () => LdValue.Of(_environmentReporter.DeviceInfo?.Model)), - new CompositeRecipeNode(AttrOs, new List + new Node(AttrManufacturer, + LdValue.Of(_environmentReporter.DeviceInfo?.Manufacturer)), + new Node(AttrModel, LdValue.Of(_environmentReporter.DeviceInfo?.Model)), + new Node(AttrOs, new List { - new ConcreteRecipeNode(AttrFamily, () => LdValue.Of(_environmentReporter.OsInfo?.Family)), - new ConcreteRecipeNode(AttrName, () => LdValue.Of(_environmentReporter.OsInfo?.Name)), - new ConcreteRecipeNode(AttrVersion, () => LdValue.Of(_environmentReporter.OsInfo?.Version)), + new Node(AttrFamily, LdValue.Of(_environmentReporter.OsInfo?.Family)), + new Node(AttrName, LdValue.Of(_environmentReporter.OsInfo?.Name)), + new Node(AttrVersion, LdValue.Of(_environmentReporter.OsInfo?.Version)), }) }; @@ -164,57 +163,45 @@ private interface ISettableMap void Set(string attributeName, LdValue value); } - private interface IRecipeNode + private class Node { - bool TryWrite(ISettableMap settableMap); - } + private readonly string _key; + private readonly LdValue? _value; + private readonly List _children; - private class CompositeRecipeNode : IRecipeNode - { - private readonly string _name; - private readonly List _nodes; + public Node(string key, List children) + { + _key = key; + _children = children; + } - public CompositeRecipeNode(string name, List nodes) + public Node(string key, LdValue value) { - _name = name; - _nodes = nodes; + _key = key; + _value = value; } - public bool TryWrite(ISettableMap map) + public bool TryWrite(ISettableMap settableMap) { - var wrote = false; - var ldValueBuilder = LdValue.BuildObject(); - var adaptedBuilder = new ObjectBuilderAdapter(ldValueBuilder); - _nodes.ForEach(it => { wrote |= it.TryWrite(adaptedBuilder); }); - if (wrote) + if (_value.HasValue && !_value.Value.IsNull) { - map.Set(_name, ldValueBuilder.Build()); + settableMap.Set(_key, _value.Value); + return true; } - return wrote; - } - } + if (_children == null) return false; - private class ConcreteRecipeNode : IRecipeNode - { - private readonly string _name; - private readonly Func _valueFunc; + var objBuilder = LdValue.BuildObject(); + var adaptedBuilder = new ObjectBuilderAdapter(objBuilder); - public ConcreteRecipeNode(string name, Func valueFunc) - { - _name = name; - _valueFunc = valueFunc; - } + if (!_children.Aggregate(false, (wrote, node) => wrote | node.TryWrite(adaptedBuilder))) return false; - public bool TryWrite(ISettableMap map) - { - var result = _valueFunc.Invoke(); - if (!result.HasValue || result.Value == LdValue.Null) return false; - map.Set(_name, result.Value); + settableMap.Set(_key, objBuilder.Build()); return true; } } + private class ObjectBuilderAdapter : ISettableMap { private readonly LdValue.ObjectBuilder _underlyingBuilder; diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 87153ee8..2a122279 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -153,7 +153,7 @@ public async void InitWithAnonUserPassesGeneratedUserToDataSource() } [Fact] - public async void InitWithAutoEnvAttributesEnabledAddsContexts() + public async void InitWithAutoEnvAttributesEnabledAddAppInfoContext() { MockPollingProcessor stub = new MockPollingProcessor(DataSetBuilder.Empty); var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); @@ -167,7 +167,7 @@ public async void InitWithAutoEnvAttributesEnabledAddsContexts() { var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + Assert.False(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } @@ -411,7 +411,7 @@ public async void IdentifyWithAnonUserPassesGeneratedUserToDataSource() } [Fact] - public async void IdentifyWithAutoEnvAttributesEnabledAddsContexts() + public async void IdentifyWithAutoEnvAttributesEnabledAddsAppInfoContext() { var stub = new MockPollingProcessor(DataSetBuilder.Empty); var dataSourceConfig = new CapturingComponentConfigurer(stub.AsSingletonFactory()); @@ -427,7 +427,7 @@ public async void IdentifyWithAutoEnvAttributesEnabledAddsContexts() var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + Assert.False(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } From 94e84d7f38c43a9802eb789398975b6b375a8ca9 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 13 Oct 2023 15:06:50 -0700 Subject: [PATCH 485/499] cleanup tests --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- .../Internal/AutoEnvContextDecoratorTest.cs | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 36e56361..1e3b32dd 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs index 1ec2a081..e52bf1e2 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/AutoEnvContextDecoratorTest.cs @@ -80,13 +80,10 @@ public void DoesNotOverwriteCustomerDataTest() var store = MakeMockDataStoreWrapper(); var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); - var input = Context.Builder(ContextKind.Of("ld_application"), "aKey") + var input = Context.Builder(ContextKind.Of(AutoEnvContextDecorator.LdApplicationKind), "aKey") .Set("dontOverwriteMeBro", "really bro").Build(); var output = decoratorUnderTest.DecorateContext(input); - // TODO: We should include ld_device in these tests. I think that may require a way to mock the platform - // layer or run on an actual platform that supports getting device information such as Android. - var expectedOutput = Context.MultiBuilder().Add(input).Build(); Assert.Equal(expectedOutput, output); @@ -99,9 +96,9 @@ public void DoesNotOverwriteCustomerDataMultiContextTest() var store = MakeMockDataStoreWrapper(); var decoratorUnderTest = MakeDecoratorWithPersistence(store, envReporter); - var input1 = Context.Builder(ContextKind.Of("ld_application"), "aKey") + var input1 = Context.Builder(ContextKind.Of(AutoEnvContextDecorator.LdApplicationKind), "aKey") .Set("dontOverwriteMeBro", "really bro").Build(); - var input2 = Context.Builder(ContextKind.Of("ld_device"), "anotherKey") + var input2 = Context.Builder(ContextKind.Of(AutoEnvContextDecorator.LdDeviceKind), "anotherKey") .Set("AndDontOverwriteThisEither", "bro").Build(); var multiContextInput = Context.MultiBuilder().Add(input1).Add(input2).Build(); var output = decoratorUnderTest.DecorateContext(multiContextInput); From bd7f7bd7e29c44ad802b139a32730e4c67b5f2bd Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 17 Oct 2023 08:55:53 -0700 Subject: [PATCH 486/499] update dependencies --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 1e3b32dd..625e3f11 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -37,9 +37,9 @@ - + - + From d6627cb81540605e1667ca3d0313545c9d71343b Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 17 Oct 2023 09:49:30 -0700 Subject: [PATCH 487/499] .net can be discouraging at times (fix package reference) --- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 625e3f11..e99aea05 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -39,7 +39,7 @@ - + From ea8102157929491596ca1de01736d4fde9319ba7 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 17 Oct 2023 12:16:17 -0500 Subject: [PATCH 488/499] Fixing compilation issue in Android tests and reverting ld_device assertion. --- .../LdClientContextTests.cs | 8 ++++---- tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs index 7b2b61fa..c7add35b 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs @@ -18,7 +18,7 @@ public void TestMakeEnvironmentReporterUsesApplicationInfoWhenSet() ).Build(); var output = LdClientContext.MakeEnvironmentReporter(configuration); - Assert.Equal("mockId", output.ApplicationInfo.ApplicationId); + Assert.Equal("mockId", output.ApplicationInfo?.ApplicationId); } [Fact] @@ -28,7 +28,7 @@ public void TestMakeEnvironmentReporterDefaultsToSdkLayerWhenNothingSet() .Build(); var output = LdClientContext.MakeEnvironmentReporter(configuration); - Assert.Equal(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo.ApplicationId); + Assert.Equal(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo?.ApplicationId); } [Fact] @@ -38,7 +38,7 @@ public void TestMakeEnvironmentReporterUsesPlatformLayerWhenAutoEnvEnabled() .Build(); var output = LdClientContext.MakeEnvironmentReporter(configuration); - Assert.NotEqual(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo.ApplicationId); + Assert.NotEqual(SdkAttributes.Layer.ApplicationInfo?.ApplicationId, output.ApplicationInfo?.ApplicationId); } [Fact] @@ -50,7 +50,7 @@ public void TestMakeEnvironmentReporterUsesApplicationInfoWhenSetAndAutoEnvEnabl ).Build(); var output = LdClientContext.MakeEnvironmentReporter(configuration); - Assert.Equal("mockId", output.ApplicationInfo.ApplicationId); + Assert.Equal("mockId", output.ApplicationInfo?.ApplicationId); } } } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index 2a122279..dc78eb03 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -167,7 +167,7 @@ public async void InitWithAutoEnvAttributesEnabledAddAppInfoContext() { var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.False(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } @@ -427,7 +427,7 @@ public async void IdentifyWithAutoEnvAttributesEnabledAddsAppInfoContext() var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.False(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); + Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } From c9f105eb2f78728842a0a136e909250d0467fdc9 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 17 Oct 2023 12:31:31 -0500 Subject: [PATCH 489/499] Removing ld_device from multiplatform test since it will not pass on all platforms being tested. --- tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs index dc78eb03..d432c0ad 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/LdClientTests.cs @@ -167,7 +167,6 @@ public async void InitWithAutoEnvAttributesEnabledAddAppInfoContext() { var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } @@ -427,7 +426,6 @@ public async void IdentifyWithAutoEnvAttributesEnabledAddsAppInfoContext() var receivedContext = dataSourceConfig.ReceivedClientContext.CurrentContext; Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_application"), out _)); - Assert.True(receivedContext.TryGetContextByKind(ContextKind.Of("ld_device"), out _)); } } From d01a865aa83c6cc699c3d2ff528ce256f169ecdc Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 18 Dec 2023 17:08:12 -0600 Subject: [PATCH 490/499] feat: adds MAUI support --- .github/actions/ci/action.yml | 99 + .github/workflows/ci.yml | 29 + .github/workflows/lint-pr-title.yml | 12 + CONTRIBUTING.md | 2 +- LaunchDarkly.ClientSdk.sln | 30 +- README.md | 12 +- contract-tests/TestService.csproj | 2 +- .../Integrations/EventProcessorBuilder.cs | 6 +- .../Integrations/HttpConfigurationBuilder.cs | 7 +- .../DefaultConnectivityStateManager.cs | 8 +- .../Internal/Events/ClientDiagnosticStore.cs | 22 +- .../Internal/SdkPackage.cs | 27 +- .../LaunchDarkly.ClientSdk.csproj | 71 +- src/LaunchDarkly.ClientSdk/LdClient.cs | 26 +- .../PlatformSpecific/AppInfo.android.cs | 97 - .../PlatformSpecific/AppInfo.ios.cs | 22 - .../PlatformSpecific/AppInfo.maui.cs | 14 + .../PlatformSpecific/AppInfo.netstandard.cs | 2 +- .../PlatformSpecific/AppInfo.shared.cs | 7 - .../AsyncScheduler.android.cs | 4 +- .../BackgroundDetection.netstandard.cs | 2 +- .../PlatformSpecific/Connectivity.android.cs | 251 - .../PlatformSpecific/Connectivity.ios.cs | 247 - .../PlatformSpecific/Connectivity.maui.cs | 64 + .../Connectivity.netstandard.cs | 23 +- .../PlatformSpecific/Connectivity.shared.cs | 121 +- .../PlatformSpecific/DeviceInfo.android.cs | 21 - .../PlatformSpecific/DeviceInfo.ios.cs | 29 - .../PlatformSpecific/DeviceInfo.maui.cs | 48 + .../DeviceInfo.netstandard.cs | 4 +- .../PlatformSpecific/DeviceInfo.shared.cs | 11 - .../PlatformSpecific/DevicePlatform.shared.cs | 60 - .../LocalStorage.netstandard.cs | 4 +- .../PlatformSpecific/Logging.android.cs | 5 +- .../PlatformSpecific/Permissions.android.cs | 204 - .../PlatformSpecific/Permissions.shared.cs | 53 - .../PlatformSpecific/Platform.android.cs | 179 - .../PlatformSpecific/Platform.shared.cs | 30 - .../PlatformSpecific/UserMetadata.android.cs | 9 - .../PlatformSpecific/UserMetadata.ios.cs | 9 - .../UserMetadata.netstandard.cs | 8 - .../PlatformSpecific/UserMetadata.shared.cs | 10 - src/LaunchDarkly.ClientSdk/PlatformType.cs | 24 - .../Properties/AssemblyInfo.cs | 3 +- ...aunchDarkly.ClientSdk.Android.Tests.csproj | 142 - .../MainActivity.cs | 29 - .../Properties/AndroidManifest.xml | 6 - .../Properties/AssemblyInfo.cs | 4 - .../Resources/Resource.designer.cs | 9575 ----------------- .../Resources/layout/activity_main.axml | 3 - .../mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../Resources/mipmap-hdpi/ic_launcher.png | Bin 1634 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 1441 -> 0 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 3552 -> 0 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 1362 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 958 -> 0 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 2413 -> 0 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 2307 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 2056 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 4858 -> 0 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 3871 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 3403 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 8001 -> 0 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 5016 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 4889 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 10893 -> 0 bytes .../Resources/values/colors.xml | 6 - .../values/ic_launcher_background.xml | 4 - .../Resources/values/strings.xml | 5 - .../XunitConsoleLoggingResultChannel.cs | 101 - ...LaunchDarkly.ClientSdk.Device.Tests.csproj | 78 + .../LaunchDarkly.ClientSdk.Device.Tests.sln | 25 + .../LdClientContextTests.cs | 0 .../MauiProgram.cs | 20 + .../Platforms/Android/AndroidManifest.xml | 6 + .../Android}/AndroidSpecificTests.cs | 6 - .../Platforms/Android/MainActivity.cs | 11 + .../Platforms/Android/MainApplication.cs | 16 + .../Android/Resources/values/colors.xml | 6 + .../Platforms/MacCatalyst/AppDelegate.cs | 10 + .../Platforms/MacCatalyst/Info.plist | 30 + .../Platforms/MacCatalyst/Program.cs | 16 + .../Platforms/Tizen/Main.cs | 17 + .../Platforms/Tizen/tizen-manifest.xml | 15 + .../Platforms/Windows/App.xaml | 8 + .../Platforms/Windows/App.xaml.cs | 25 + .../Platforms/Windows/Package.appxmanifest | 46 + .../Platforms/Windows/app.manifest | 15 + .../Platforms/iOS/AppDelegate.cs | 10 + .../Platforms/iOS}/IOsSpecificTests.cs | 12 +- .../Platforms/iOS}/Info.plist | 93 +- .../Platforms/iOS/Program.cs | 16 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Fonts/OpenSans-Regular.ttf | Bin 0 -> 107168 bytes .../Resources/Fonts/OpenSans-Semibold.ttf | Bin 0 -> 111060 bytes .../Resources/Images/dotnet_bot.svg | 93 + .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Resources/Styles/Colors.xaml | 44 + .../Resources/Styles/Styles.xaml | 405 + .../HttpConfigurationBuilderTest.cs | 2 +- .../DataSources/FeatureFlagRequestorTests.cs | 2 +- .../DataSources/StreamingDataSourceTest.cs | 138 +- .../LaunchDarkly.ClientSdk.Tests.csproj | 2 +- .../AppDelegate.cs | 33 - .../AppIcon.appiconset/Contents.json | 117 - .../AppIcon.appiconset/Icon1024.png | Bin 70429 -> 0 bytes .../AppIcon.appiconset/Icon120.png | Bin 3773 -> 0 bytes .../AppIcon.appiconset/Icon152.png | Bin 4750 -> 0 bytes .../AppIcon.appiconset/Icon167.png | Bin 4692 -> 0 bytes .../AppIcon.appiconset/Icon180.png | Bin 5192 -> 0 bytes .../AppIcon.appiconset/Icon20.png | Bin 1313 -> 0 bytes .../AppIcon.appiconset/Icon29.png | Bin 845 -> 0 bytes .../AppIcon.appiconset/Icon40.png | Bin 1101 -> 0 bytes .../AppIcon.appiconset/Icon58.png | Bin 1761 -> 0 bytes .../AppIcon.appiconset/Icon60.png | Bin 2537 -> 0 bytes .../AppIcon.appiconset/Icon76.png | Bin 2332 -> 0 bytes .../AppIcon.appiconset/Icon80.png | Bin 2454 -> 0 bytes .../AppIcon.appiconset/Icon87.png | Bin 2758 -> 0 bytes .../Entitlements.plist | 6 - .../LaunchDarkly.ClientSdk.iOS.Tests.csproj | 160 - .../LaunchScreen.storyboard | 27 - .../LaunchDarkly.ClientSdk.iOS.Tests/Main.cs | 13 - .../Main.storyboard | 27 - .../Properties/AssemblyInfo.cs | 4 - .../Resources/LaunchScreen.xib | 43 - .../XunitConsoleLoggingResultChannel.cs | 101 - 129 files changed, 1480 insertions(+), 12056 deletions(-) create mode 100644 .github/actions/ci/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/lint-pr-title.yml delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.maui.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.maui.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs create mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs delete mode 100644 src/LaunchDarkly.ClientSdk/PlatformType.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AndroidManifest.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AssemblyInfo.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/layout/activity_main.axml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/colors.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/ic_launcher_background.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/strings.xml delete mode 100644 tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.sln rename tests/{LaunchDarkly.ClientSdk.Android.Tests => LaunchDarkly.ClientSdk.Device.Tests}/LdClientContextTests.cs (100%) create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/MauiProgram.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidManifest.xml rename tests/{LaunchDarkly.ClientSdk.Android.Tests => LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android}/AndroidSpecificTests.cs (87%) create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainActivity.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainApplication.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/Resources/values/colors.xml create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/AppDelegate.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Info.plist create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Program.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/Main.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/tizen-manifest.xml create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/Package.appxmanifest create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/app.manifest create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/AppDelegate.cs rename tests/{LaunchDarkly.ClientSdk.iOS.Tests => LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS}/IOsSpecificTests.cs (85%) rename tests/{LaunchDarkly.ClientSdk.iOS.Tests => LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS}/Info.plist (51%) create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Program.cs create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appicon.svg create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appiconfg.svg create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Fonts/OpenSans-Regular.ttf create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Fonts/OpenSans-Semibold.ttf create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Images/dotnet_bot.svg create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Raw/AboutAssets.txt create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Splash/splash.svg create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Colors.xaml create mode 100644 tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Styles.xaml delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon20.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon29.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon40.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon80.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Entitlements.plist delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchScreen.storyboard delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.storyboard delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Properties/AssemblyInfo.cs delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/Resources/LaunchScreen.xib delete mode 100644 tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml new file mode 100644 index 00000000..964e166b --- /dev/null +++ b/.github/actions/ci/action.yml @@ -0,0 +1,99 @@ +name: CI Action +inputs: + run_tests: + description: 'If true, run unit tests, otherwise skip them.' + required: false + default: 'true' + +runs: + using: composite + steps: + - name: Download snk for signing assemblies + shell: bash + run: aws s3 cp s3://launchdarkly-releaser/dotnet/LaunchDarkly.ClientSdk.snk LaunchDarkly.ClientSdk.snk + + - name: Setup dotnet build tools + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0 + + - name: Install MAUI Workload + shell: bash + run: dotnet workload install maui-android maui-ios maui-windows maui-maccatalyst --ignore-failed-sources + + - name: Restore Dependencies + shell: bash + run: dotnet restore src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + # - name: Build for NetStandard2.0 + # shell: bash + # run: dotnet build /p:Configuration=release /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Build for Net7 + shell: bash + run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Build for Net7-android + shell: bash + run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-android src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Build for Net7-ios + shell: bash + run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-ios src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Build for Net7-windows + shell: bash + run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-maccatalyst src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Build for Net7-maccatalyst + shell: bash + run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-windows src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + + - name: Run Unit Tests for Net7 + run: | + dotnet restore tests/LaunchDarkly.ClientSdk.Tests + dotnet test -v=normal \ + --logger:"junit;LogFilePath=/tmp/circle-reports/unit-tests.xml" \ + tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj + + - name: Build Contract Tests + if: inputs.run_tests == 'true' + run: dotnet build contract-tests/TestService.csproj + + - name: Run Contract Tests + if: inputs.run_tests == 'true' + run: | + dotnet contract-tests/bin/release/net7.0/ContractTestService.dll > test-service.log 2>&1 & disown + curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end \ + -junit /tmp/circle-reports/contract-tests-junit.xml" sh + + - name: Build Test App + if: inputs.run_tests == 'true' + run: | + dotnet build /restore /p:Configuration=release \ + tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj + + # - name: Set up JDK 17 + # if: inputs.run_tests == 'true' + # uses: actions/setup-java@v3 + # with: + # java-version: '17' + # distribution: 'temurin' + + # - name: Setup Android Manager + # if: inputs.run_tests == 'true' + # uses: android-actions/setup-android@v3 + + # TODO: Tests are not auto executing, so this is commented out. For now this must be done manually. + # - name: Run Android Test App on Emulator + # if: inputs.run_tests == 'true' + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: 27 + # script: | + # dotnet run --framework net7.0-android --project tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj + # adb install tests/LaunchDarkly.ClientSdk.Device.Tests/bin/release/net7.0-android/com.LaunchDarkly.ClientSdk.Device.Tests-Signed.apk + # ( adb logcat DOTNET:D AndroidRuntime:D & ) | tee test-run.log | grep -q 'Tests run:' + # cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,7- + # if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6c45ab37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: Run CI +on: + push: + branches: [main, 'feat/**'] + paths-ignore: + - '**.md' # Do not need to run CI for markdown changes. + pull_request: + branches: [main, 'feat/**'] + paths-ignore: + - '**.md' + +jobs: + ci-build: + runs-on: macos-latest-large + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # If you only need the current version keep this. + + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.0 + name: Get secrets + with: + aws_assume_role: ${{ vars.AWS_ROLE_ARN }} + ssm_parameter_pairs: '/production/common/releasing/digicert/host = DIGICERT_HOST,/production/common/releasing/digicert/api_key = DIGICERT_API_KEY,/production/common/releasing/digicert/client_cert_file_b64 = DIGICERT_CLIENT_CERT_FILE_B64,/production/common/releasing/digicert/client_cert_password = DIGICERT_CLIENT_CERT_PASSWORD,/production/common/releasing/digicert/code_signing_cert_sha1_hash = DIGICERT_CODE_SIGNING_CERT_SHA1_HASH' + + - uses: ./.github/actions/ci diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 00000000..4ba79c13 --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,12 @@ +name: Lint PR title + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + lint-pr-title: + uses: launchdarkly/gh-actions/.github/workflows/lint-pr-title.yml@main diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11f6447a..48388f5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We encourage pull requests and other contributions from the community. Before su ### Prerequisites -The .NET Standard target requires only the .NET Core 2.1 SDK or higher, while the iOS and Android targets require the corresponding Xamarin SDKs. +The .NET 7 target requires only the .NET Core 2.1 SDK or higher, while the iOS, Android, MacCatalys, and Windows targets require the corresponding MAUI SDKs. ### Building diff --git a/LaunchDarkly.ClientSdk.sln b/LaunchDarkly.ClientSdk.sln index abe5b558..3095d506 100644 --- a/LaunchDarkly.ClientSdk.sln +++ b/LaunchDarkly.ClientSdk.sln @@ -4,11 +4,9 @@ VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk", "src\LaunchDarkly.ClientSdk\LaunchDarkly.ClientSdk.csproj", "{7717A2B2-9905-40A7-989F-790139D69543}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Tests", "tests\LaunchDarkly.ClientSdk.Tests\LaunchDarkly.ClientSdk.Tests.csproj", "{F6B71DFE-314C-4F27-A219-A14569C8CF48}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Device.Tests", "tests\LaunchDarkly.ClientSdk.Device.Tests\LaunchDarkly.ClientSdk.Device.Tests.csproj", "{0D88C80E-8CD8-4064-AE99-9849C7CD6E35}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.iOS.Tests", "tests\LaunchDarkly.ClientSdk.iOS.Tests\LaunchDarkly.ClientSdk.iOS.Tests.csproj", "{5EFF7561-35C1-4C62-B0BE-A76E37DCEB32}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Android.Tests", "tests\LaunchDarkly.ClientSdk.Android.Tests\LaunchDarkly.ClientSdk.Android.Tests.csproj", "{2E7720E4-01A0-403B-863C-C6C596DF5926}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Tests", "tests\LaunchDarkly.ClientSdk.Tests\LaunchDarkly.ClientSdk.Tests.csproj", "{36701E5A-EC04-4B47-8739-BACCCB673C77}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -68,6 +66,30 @@ Global {2E7720E4-01A0-403B-863C-C6C596DF5926}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhone.ActiveCfg = Debug|Any CPU {2E7720E4-01A0-403B-863C-C6C596DF5926}.Debug|iPhone.Build.0 = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|Any CPU.Build.0 = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|iPhone.ActiveCfg = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|iPhone.Build.0 = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0D88C80E-8CD8-4064-AE99-9849C7CD6E35}.Debug|iPhone.Build.0 = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|Any CPU.Build.0 = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|iPhone.ActiveCfg = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|iPhone.Build.0 = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {36701E5A-EC04-4B47-8739-BACCCB673C77}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index f29b0267..9c012119 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The LaunchDarkly Client-Side SDK for .NET is designed primarily for use by code that is deployed to an end user, such as in a desktop application or a smart device. It follows the client-side LaunchDarkly model for single-user contexts (much like our mobile or JavaScript SDKs). It is not intended for use in multi-user systems such as web servers and applications. -On supported mobile platforms (Android and iOS), the SDK uses the Xamarin framework which allows .NET code to run on those devices. For that reason, its name was previously "LaunchDarkly Xamarin SDK". However, Xamarin is not the only way to run .NET code in a client-side context (see "Supported platforms" below), so the SDK now has a more general name. +On platforms with MAUI support (Android, iOS, Mac, Windows), the SDK depends on the MAUI framework which allows .NET code to run on those devices. However, MAUI is not the only way to run .NET code in a client-side context (see "Supported platforms" below), so the SDK has a more general name. For using LaunchDarkly in *server-side* .NET applications, refer to our [Server-Side .NET SDK](https://github.com/launchdarkly/dotnet-server-sdk). @@ -20,11 +20,13 @@ For using LaunchDarkly in *server-side* .NET applications, refer to our [Server- This version of the SDK is built for the following targets: -* Xamarin Android 8.1, for use with Android 8.1 (Android API 27) and higher. -* Xamarin iOS 10, for use with iOS 10 and higher. -* .NET Standard 2.0, for use with any runtime platform that supports .NET Standard 2.0, or in portable .NET Standard library code. +* .Net 7 Android, for use with Android 5.0 (Android API 21) and higher. +* .Net 7 iOS, for use with iOS 11 and higher. +* .Net 7 macOS (using Mac Catalyst), for use with macOS 10.15 and higher. +* .Net 7 Windows (using WinUI), for Windows 11 and Windows 10 version 1809 or higher. +* .NET 7, for use with any runtime platform that supports .NET Standard 2.1, or in portable .NET Standard library code. -The .NET Standard 2.0 target does not use any Xamarin packages and has no OS-specific code. This allows the SDK to be used in a desktop .NET Framework or .NET 5.0 application, or in a Xamarin MacOS application. However, due to the lack of OS-specific integration, SDK functionality will be limited in those environments: for instance, the SDK will not be able to detect whether networking is turned on or off. +The .NET 7 target does not use any MAUI packages and has no OS-specific code. This allows the SDK to be used in a desktop .NET Framework or .NET 7.0 application. However, due to the lack of OS-specific integration, SDK functionality will be limited in those environments: for instance, the SDK will not be able to detect whether networking is turned on or off. The .NET build tools should automatically load the most appropriate build of the SDK for whatever platform your application or library is targeted to. diff --git a/contract-tests/TestService.csproj b/contract-tests/TestService.csproj index 8c1dce50..39805cf1 100644 --- a/contract-tests/TestService.csproj +++ b/contract-tests/TestService.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 $(TESTFRAMEWORK) portable ContractTestService diff --git a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs index 2819e93a..48250800 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/EventProcessorBuilder.cs @@ -51,10 +51,10 @@ public sealed class EventProcessorBuilder : IComponentConfigurer public static readonly TimeSpan DefaultFlushInterval = -#if NETSTANDARD - TimeSpan.FromSeconds(5); -#else +#if (ANDROID || IOS) TimeSpan.FromSeconds(30); +#else + TimeSpan.FromSeconds(5); #endif /// diff --git a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs index 4f138db4..3810874b 100644 --- a/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs +++ b/src/LaunchDarkly.ClientSdk/Integrations/HttpConfigurationBuilder.cs @@ -68,7 +68,7 @@ public sealed class HttpConfigurationBuilder : IDiagnosticDescription /// /// /// Not all .NET platforms support setting a connection timeout. It is supported in - /// .NET Core 2.1+, .NET 5+, and Xamarin Android, but not in Xamarin iOS. On platforms + /// .NET Core 2.1+, .NET 5+, and MAUI Android, but not in MAUI iOS. On platforms /// where it is not supported, only will be used. /// /// @@ -109,8 +109,7 @@ public HttpConfigurationBuilder CustomHeader(string name, string value) /// /// This is mainly useful for testing, to cause the SDK to use custom logic instead of actual HTTP requests, /// but can also be used to customize HTTP behavior on platforms where the default handler is not optimal. - /// The default is the usual native HTTP handler for the current platform, if any (for instance, - /// Xamarin.Android.Net.AndroidClientHandler), or else . + /// The default is the usual native HTTP handler for the current platform, else . /// /// the message handler, or null to use the platform's default handler /// the builder @@ -203,7 +202,7 @@ public HttpConfigurationBuilder ResponseStartTimeout(TimeSpan responseStartTimeo /// /// true to enable the REPORT method /// the builder -#if !MONOANDROID +#if !ANDROID public HttpConfigurationBuilder UseReport(bool useReport) #else internal HttpConfigurationBuilder UseReport(bool useReport) diff --git a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs index 698a2d9e..042e652c 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/DataSources/DefaultConnectivityStateManager.cs @@ -11,7 +11,7 @@ internal sealed class DefaultConnectivityStateManager : IConnectivityStateManage internal DefaultConnectivityStateManager() { UpdateConnectedStatus(); - Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + PlatformConnectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } bool isConnected; @@ -23,8 +23,8 @@ bool IConnectivityStateManager.IsConnected isConnected = value; } } - - void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e) + + void Connectivity_ConnectivityChanged(object sender, EventArgs e) { UpdateConnectedStatus(); ConnectionChanged?.Invoke(isConnected); @@ -32,7 +32,7 @@ void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArg private void UpdateConnectedStatus() { - isConnected = Connectivity.NetworkAccess == NetworkAccess.Internet; + isConnected = PlatformConnectivity.LdNetworkAccess == LdNetworkAccess.Internet; } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs index d5af2563..7910b2d0 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/Events/ClientDiagnosticStore.cs @@ -15,7 +15,7 @@ internal class ClientDiagnosticStore : DiagnosticStoreBase protected override string SdkKeyOrMobileKey => _context.MobileKey; protected override string SdkName => SdkPackage.Name; protected override IEnumerable ConfigProperties => GetConfigProperties(); - protected override string DotNetTargetFramework => GetDotNetTargetFramework(); + protected override string DotNetTargetFramework => SdkPackage.DotNetTargetFramework; protected override HttpProperties HttpProperties => _context.Http.HttpProperties; protected override Type TypeOfLdClient => typeof(LdClient); @@ -50,25 +50,5 @@ private IEnumerable GetConfigProperties() private LdValue GetComponentDescription(object component) => component is IDiagnosticDescription dd ? dd.DescribeConfiguration(_context) : LdValue.Null; - - internal static string GetDotNetTargetFramework() - { - // Note that this is the _target framework_ that was selected at build time based on the application's - // compatibility requirements; it doesn't tell us anything about the actual OS version. We'll need to - // update this whenever we add or remove supported target frameworks in the .csproj file. -#if NETSTANDARD2_0 - return "netstandard2.0"; -#elif MONOANDROID71 - return "monoandroid7.1"; -#elif MONOANDROID80 - return "monoandroid8.0"; -#elif MONOANDROID81 - return "monoandroid8.1"; -#elif XAMARIN_IOS10 - return "xamarinios1.0"; -#else - return "unknown"; -#endif - } } } diff --git a/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs index 2a9fa3f4..f0adcd98 100644 --- a/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs +++ b/src/LaunchDarkly.ClientSdk/Internal/SdkPackage.cs @@ -17,7 +17,7 @@ internal static class SdkPackage /// The prefix for the User-Agent header, omitting the version string. This may be different than the Name /// due to historical reasons. /// - private const string UserAgentPrefix = "XamarinClient"; + private const string UserAgentPrefix = "DotnetClientSide"; /// /// Version of the SDK. @@ -29,5 +29,30 @@ internal static class SdkPackage /// internal static string UserAgent => $"{UserAgentPrefix}/{Version}"; + /// + /// The target framework selected at build time. + /// + /// + /// This is the _target framework_ that was selected at build time based + /// on the application's compatibility requirements; it doesn't tell + /// anything about the actual OS version. + /// + internal static string DotNetTargetFramework => + // We'll need to update this whenever we add or remove supported target frameworks in the .csproj file. + // Order of these conditonals matters. Specific frameworks come before net7.0 intentionally. +#if ANDROID + "net7.0-android"; +#elif IOS + "net7.0-ios"; +#elif MACCATALYST + "net7.0-maccatalyst"; +#elif WINDOWS + "net7.0-windows"; +#elif NET7_0 + "net7.0"; +#else + "unknown"; +#endif + } } diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index b1dd1666..a7a6c67e 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -1,20 +1,23 @@ - + - 4.0.0 + 5.0.0 - netstandard2.0;xamarin.ios10;monoandroid71;monoandroid80;monoandroid81 + want to even mention the Android/iOS/Mac/Windows targets, because we're in an environment + that doesn't have the MAUI tools installed. That is currently the case in + the release phase where we build HTML documentation. --> + + + net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst;net7.0-windows $(BUILDFRAMEWORKS) + true Library LaunchDarkly.ClientSdk LaunchDarkly.ClientSdk false bin\$(Configuration)\$(Framework) - 7.3 + 8.0 False True true @@ -45,28 +48,55 @@ - - + + - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -80,15 +110,6 @@ - - - - - - - - - ../../LaunchDarkly.ClientSdk.snk true diff --git a/src/LaunchDarkly.ClientSdk/LdClient.cs b/src/LaunchDarkly.ClientSdk/LdClient.cs index 6034dfa2..7c5c8eb5 100644 --- a/src/LaunchDarkly.ClientSdk/LdClient.cs +++ b/src/LaunchDarkly.ClientSdk/LdClient.cs @@ -110,30 +110,6 @@ public sealed class LdClient : ILdClient /// public IFlagTracker FlagTracker => _flagTracker; - /// - /// Indicates which platform the SDK is built for. - /// - /// - /// - /// This property is mainly useful for debugging. It does not indicate which platform you are actually running on, - /// but rather which variant of the SDK is currently in use. - /// - /// - /// The LaunchDarkly.ClientSdk package contains assemblies for multiple target platforms. In an Android - /// or iOS application, you will normally be using the Android or iOS variant of the SDK; that is done - /// automatically when you install the NuGet package. On all other platforms, you will get the .NET Standard - /// variant. - /// - /// - /// The basic features of the SDK are the same in all of these variants; the difference is in platform-specific - /// behavior such as detecting when an application has gone into the background, detecting network connectivity, - /// and ensuring that code is executed on the UI thread if applicable for that platform. Therefore, if you find - /// that these platform-specific behaviors are not working correctly, you may want to check this property to - /// make sure you are not for some reason running the .NET Standard SDK on a phone. - /// - /// - public static PlatformType PlatformType => UserMetadata.PlatformType; - // private constructor prevents initialization of this class // without using WithConfigAnduser(config, user) LdClient() @@ -154,7 +130,7 @@ public sealed class LdClient : ILdClient _log = _clientContext.BaseLogger; _taskExecutor = _clientContext.TaskExecutor; - _log.Info("Starting LaunchDarkly Client {0}", Version); + _log.Info("Starting LaunchDarkly Client {0} built with target framework {1}", Version, SdkPackage.DotNetTargetFramework); var persistenceConfiguration = (_config.PersistenceConfigurationBuilder ?? Components.Persistence()) .Build(_clientContext); diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs deleted file mode 100644 index a4f71f11..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.android.cs +++ /dev/null @@ -1,97 +0,0 @@ -/* -Xamarin.Essentials - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -using System.Globalization; -using Android.Content; -using Android.Content.PM; -using Android.Content.Res; -using Android.Provider; -using LaunchDarkly.Sdk.EnvReporting; -#if __ANDROID_29__ -using AndroidX.Core.Content.PM; -#else -using Android.Support.V4.Content.PM; -#endif - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class AppInfo - { - static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( - PlatformGetAppId(), - PlatformGetAppName(), - PlatformGetAppVersion(), - PlatformGetAppVersionName()); - - // The following methods are added by LaunchDarkly to align with the Application Info - // required by the SDK. - static string PlatformGetAppId() => Platform.AppContext.PackageName; - static string PlatformGetAppName() => PlatformGetName(); - static string PlatformGetAppVersion() => PlatformGetBuild(); - static string PlatformGetAppVersionName() => PlatformGetVersionString(); - - // End LaunchDarkly additions. - - static string PlatformGetName() - { - var applicationInfo = Platform.AppContext.ApplicationInfo; - var packageManager = Platform.AppContext.PackageManager; - return applicationInfo.LoadLabel(packageManager); - } - - static string PlatformGetVersionString() - { - var pm = Platform.AppContext.PackageManager; - var packageName = Platform.AppContext.PackageName; -#pragma warning disable CS0618 // Type or member is obsolete - using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) -#pragma warning restore CS0618 // Type or member is obsolete - { - return info.VersionName; - } - } - - static string PlatformGetBuild() - { - var pm = Platform.AppContext.PackageManager; - var packageName = Platform.AppContext.PackageName; -#pragma warning disable CS0618 // Type or member is obsolete - using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData)) -#pragma warning restore CS0618 // Type or member is obsolete - { -#if __ANDROID_28__ - return PackageInfoCompat.GetLongVersionCode(info).ToString(CultureInfo.InvariantCulture); -#else -#pragma warning disable CS0618 // Type or member is obsolete - return info.VersionCode.ToString(CultureInfo.InvariantCulture); -#pragma warning restore CS0618 // Type or member is obsolete -#endif - } - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs deleted file mode 100644 index 1557e6dd..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.ios.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Foundation; -#if __IOS__ || __TVOS__ -using UIKit; - -#elif __MACOS__ -using AppKit; -#endif - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class AppInfo - { - static ApplicationInfo? PlatformGetApplicationInfo() => new ApplicationInfo( - GetBundleValue("CFBundleIdentifier"), - GetBundleValue("CFBundleName"), - GetBundleValue("CFBundleVersion"), - GetBundleValue("CFBundleShortString")); - - static string GetBundleValue(string key) - => NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.maui.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.maui.cs new file mode 100644 index 00000000..8c938e32 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.maui.cs @@ -0,0 +1,14 @@ +using System.Globalization; +using Microsoft.Maui.ApplicationModel; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class AppInfo + { + internal static ApplicationInfo? GetAppInfo() => new ApplicationInfo( + Microsoft.Maui.ApplicationModel.AppInfo.Current.PackageName, + Microsoft.Maui.ApplicationModel.AppInfo.Current.Name, + Microsoft.Maui.ApplicationModel.AppInfo.Current.BuildString, + Microsoft.Maui.ApplicationModel.AppInfo.Current.VersionString); + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs index c7e072b7..6ff1fd0f 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.netstandard.cs @@ -2,6 +2,6 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class AppInfo { - private static ApplicationInfo? PlatformGetApplicationInfo() => null; + internal static ApplicationInfo? GetAppInfo() => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs deleted file mode 100644 index 1573db1b..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AppInfo.shared.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class AppInfo - { - internal static ApplicationInfo? GetAppInfo() => PlatformGetApplicationInfo(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs index 352d560a..9b55f4be 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/AsyncScheduler.android.cs @@ -7,8 +7,8 @@ internal static partial class AsyncScheduler { private static void PlatformScheduleAction(Action a) { - // Note that this logic is different from the implementation of the equivalent method in Xamarin Essentials - // (https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/MainThread/MainThread.android.cs); + // Note that this logic is different from the implementation of the equivalent method in MAUI Essentials + // (https://github.com/dotnet/maui/blob/main/src/Essentials/src/MainThread/MainThread.android.cs); // it creates a new Handler object each time rather than lazily creating a static one. This avoids a potential // race condition, at the expense of creating more ephemeral objects. However, in our use case we do not // expect this method to be called very frequently since we are using it for flag change listeners only. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs index 36e69bd0..30190c98 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/BackgroundDetection.netstandard.cs @@ -2,7 +2,7 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { - // This code is not from Xamarin Essentials, though it implements the same abstraction. It is a stub + // This code is not from MAUI Essentials, though it implements the same abstraction. It is a stub // that does nothing, since in .NET Standard there is no notion of an application being in the // background or the foreground. diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs deleted file mode 100644 index 7f24efd1..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.android.cs +++ /dev/null @@ -1,251 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Android.Content; -using Android.Net; -using Android.OS; -using Debug = System.Diagnostics.Debug; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal partial class Connectivity - { - static ConnectivityBroadcastReceiver conectivityReceiver; - - static void StartListeners() - { - Permissions.EnsureDeclared(PermissionType.NetworkState); - - conectivityReceiver = new ConnectivityBroadcastReceiver(OnConnectivityChanged); - - Platform.AppContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); - } - - static void StopListeners() - { - if (conectivityReceiver == null) - return; - try - { - Platform.AppContext.UnregisterReceiver(conectivityReceiver); - } - catch (Java.Lang.IllegalArgumentException) - { - Debug.WriteLine("Connectivity receiver already unregistered. Disposing of it."); - } - conectivityReceiver.Dispose(); - conectivityReceiver = null; - } - - static NetworkAccess IsBetterAccess(NetworkAccess currentAccess, NetworkAccess newAccess) => - newAccess > currentAccess ? newAccess : currentAccess; - - static NetworkAccess PlatformNetworkAccess - { - get - { - Permissions.EnsureDeclared(PermissionType.NetworkState); - - try - { - var currentAccess = NetworkAccess.None; - var manager = Platform.ConnectivityManager; - - if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) - { - foreach (var network in manager.GetAllNetworks()) - { - try - { - var capabilities = manager.GetNetworkCapabilities(network); - - if (capabilities == null) - continue; - - var info = manager.GetNetworkInfo(network); - - if (info == null || !info.IsAvailable) - continue; - - // Check to see if it has the internet capability - if (!capabilities.HasCapability(NetCapability.Internet)) - { - // Doesn't have internet, but local is possible - currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Local); - continue; - } - - ProcessNetworkInfo(info); - } - catch - { - // there is a possibility, but don't worry - } - } - } - else - { -#pragma warning disable CS0618 // Type or member is obsolete - foreach (var info in manager.GetAllNetworkInfo()) -#pragma warning restore CS0618 // Type or member is obsolete - { - ProcessNetworkInfo(info); - } - } - - void ProcessNetworkInfo(NetworkInfo info) - { - if (info == null || !info.IsAvailable) - return; - if (info.IsConnected) - currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Internet); - else if (info.IsConnectedOrConnecting) - currentAccess = IsBetterAccess(currentAccess, NetworkAccess.ConstrainedInternet); - } - - return currentAccess; - } - catch (Exception e) - { - Debug.WriteLine("Unable to get connected state - do you have ACCESS_NETWORK_STATE permission? - error: {0}", e); - return NetworkAccess.Unknown; - } - } - } - - static IEnumerable PlatformConnectionProfiles - { - get - { - Permissions.EnsureDeclared(PermissionType.NetworkState); - - var manager = Platform.ConnectivityManager; - if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) - { - foreach (var network in manager.GetAllNetworks()) - { - NetworkInfo info = null; - try - { - info = manager.GetNetworkInfo(network); - } - catch - { - // there is a possibility, but don't worry about it - } - - var p = ProcessNetworkInfo(info); - if (p.HasValue) - yield return p.Value; - } - } - else - { -#pragma warning disable CS0618 // Type or member is obsolete - foreach (var info in manager.GetAllNetworkInfo()) -#pragma warning restore CS0618 // Type or member is obsolete - { - var p = ProcessNetworkInfo(info); - if (p.HasValue) - yield return p.Value; - } - } - - ConnectionProfile? ProcessNetworkInfo(NetworkInfo info) - { - if (info == null || !info.IsAvailable || !info.IsConnectedOrConnecting) - return null; - - return GetConnectionType(info.Type, info.TypeName); - } - } - } - - internal static ConnectionProfile GetConnectionType(ConnectivityType connectivityType, string typeName) - { - switch (connectivityType) - { - case ConnectivityType.Ethernet: - return ConnectionProfile.Ethernet; - case ConnectivityType.Wifi: - return ConnectionProfile.WiFi; - case ConnectivityType.Bluetooth: - return ConnectionProfile.Bluetooth; - case ConnectivityType.Wimax: - case ConnectivityType.Mobile: - case ConnectivityType.MobileDun: - case ConnectivityType.MobileHipri: - case ConnectivityType.MobileMms: - return ConnectionProfile.Cellular; - case ConnectivityType.Dummy: - return ConnectionProfile.Unknown; - default: - if (string.IsNullOrWhiteSpace(typeName)) - return ConnectionProfile.Unknown; - - var typeNameLower = typeName.ToLowerInvariant(); - if (typeNameLower.Contains("mobile")) - return ConnectionProfile.Cellular; - - if (typeNameLower.Contains("wimax")) - return ConnectionProfile.Cellular; - - if (typeNameLower.Contains("wifi")) - return ConnectionProfile.WiFi; - - if (typeNameLower.Contains("ethernet")) - return ConnectionProfile.Ethernet; - - if (typeNameLower.Contains("bluetooth")) - return ConnectionProfile.Bluetooth; - - return ConnectionProfile.Unknown; - } - } - } - - [BroadcastReceiver(Enabled = true, Exported = false, Label = "Essentials Connectivity Broadcast Receiver")] - class ConnectivityBroadcastReceiver : BroadcastReceiver - { - Action onChanged; - - public ConnectivityBroadcastReceiver() - { - } - - public ConnectivityBroadcastReceiver(Action onChanged) => - this.onChanged = onChanged; - - public override async void OnReceive(Android.Content.Context context, Intent intent) - { - if (intent.Action != ConnectivityManager.ConnectivityAction) - return; - - // await 500ms to ensure that the the connection manager updates - await Task.Delay(500); - onChanged?.Invoke(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs deleted file mode 100644 index f251ad67..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.ios.cs +++ /dev/null @@ -1,247 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using CoreFoundation; -using SystemConfiguration; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class Connectivity - { - static ReachabilityListener listener; - - static void StartListeners() - { - listener = new ReachabilityListener(); - listener.ReachabilityChanged += OnConnectivityChanged; - } - - static void StopListeners() - { - if (listener == null) - return; - - listener.ReachabilityChanged -= OnConnectivityChanged; - listener.Dispose(); - listener = null; - } - - static NetworkAccess PlatformNetworkAccess - { - get - { - var internetStatus = Reachability.InternetConnectionStatus(); - if (internetStatus == NetworkStatus.ReachableViaCarrierDataNetwork || internetStatus == NetworkStatus.ReachableViaWiFiNetwork) - return NetworkAccess.Internet; - - var remoteHostStatus = Reachability.RemoteHostStatus(); - if (remoteHostStatus == NetworkStatus.ReachableViaCarrierDataNetwork || remoteHostStatus == NetworkStatus.ReachableViaWiFiNetwork) - return NetworkAccess.Internet; - - return NetworkAccess.None; - } - } - - static IEnumerable PlatformConnectionProfiles - { - get - { - var statuses = Reachability.GetActiveConnectionType(); - foreach (var status in statuses) - { - switch (status) - { - case NetworkStatus.ReachableViaCarrierDataNetwork: - yield return ConnectionProfile.Cellular; - break; - case NetworkStatus.ReachableViaWiFiNetwork: - yield return ConnectionProfile.WiFi; - break; - default: - yield return ConnectionProfile.Unknown; - break; - } - } - } - } - } - - enum NetworkStatus - { - NotReachable, - ReachableViaCarrierDataNetwork, - ReachableViaWiFiNetwork - } - - internal static class Reachability - { - internal const string HostName = "www.microsoft.com"; - - internal static NetworkStatus RemoteHostStatus() - { - using (var remoteHostReachability = new NetworkReachability(HostName)) - { - var reachable = remoteHostReachability.TryGetFlags(out var flags); - - if (!reachable) - return NetworkStatus.NotReachable; - - if (!IsReachableWithoutRequiringConnection(flags)) - return NetworkStatus.NotReachable; - - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - return NetworkStatus.ReachableViaCarrierDataNetwork; - - return NetworkStatus.ReachableViaWiFiNetwork; - } - } - - internal static NetworkStatus InternetConnectionStatus() - { - var status = NetworkStatus.NotReachable; - - var defaultNetworkAvailable = IsNetworkAvailable(out var flags); - - // If it's a WWAN connection.. - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - status = NetworkStatus.ReachableViaCarrierDataNetwork; - - // If the connection is reachable and no connection is required, then assume it's WiFi - if (defaultNetworkAvailable) - { - status = NetworkStatus.ReachableViaWiFiNetwork; - } - - // If the connection is on-demand or on-traffic and no user intervention - // is required, then assume WiFi. - if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && - (flags & NetworkReachabilityFlags.InterventionRequired) == 0) - { - status = NetworkStatus.ReachableViaWiFiNetwork; - } - - return status; - } - - internal static IEnumerable GetActiveConnectionType() - { - var status = new List(); - - var defaultNetworkAvailable = IsNetworkAvailable(out var flags); - - // If it's a WWAN connection.. - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - { - status.Add(NetworkStatus.ReachableViaCarrierDataNetwork); - } - else if (defaultNetworkAvailable) - { - status.Add(NetworkStatus.ReachableViaWiFiNetwork); - } - else if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && - (flags & NetworkReachabilityFlags.InterventionRequired) == 0) - { - // If the connection is on-demand or on-traffic and no user intervention - // is required, then assume WiFi. - status.Add(NetworkStatus.ReachableViaWiFiNetwork); - } - - return status; - } - - internal static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) - { - var ip = new IPAddress(0); - using (var defaultRouteReachability = new NetworkReachability(ip)) - { - if (!defaultRouteReachability.TryGetFlags(out flags)) - return false; - - return IsReachableWithoutRequiringConnection(flags); - } - } - - internal static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags) - { - // Is it reachable with the current network configuration? - var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0; - - // Do we need a connection to reach it? - var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0; - - // Since the network stack will automatically try to get the WAN up, - // probe that - if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) - noConnectionRequired = true; - - return isReachable && noConnectionRequired; - } - } - - internal class ReachabilityListener : IDisposable - { - NetworkReachability defaultRouteReachability; - NetworkReachability remoteHostReachability; - - internal ReachabilityListener() - { - var ip = new IPAddress(0); - defaultRouteReachability = new NetworkReachability(ip); - defaultRouteReachability.SetNotification(OnChange); - defaultRouteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); - - remoteHostReachability = new NetworkReachability(Reachability.HostName); - - // Need to probe before we queue, or we wont get any meaningful values - // this only happens when you create NetworkReachability from a hostname - remoteHostReachability.TryGetFlags(out var flags); - - remoteHostReachability.SetNotification(OnChange); - remoteHostReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); - } - - internal event Action ReachabilityChanged; - - void IDisposable.Dispose() => Dispose(); - - internal void Dispose() - { - defaultRouteReachability?.Dispose(); - defaultRouteReachability = null; - remoteHostReachability?.Dispose(); - remoteHostReachability = null; - } - - async void OnChange(NetworkReachabilityFlags flags) - { - // Add in artifical delay so the connection status has time to change - // else it will return true no matter what. - await Task.Delay(100); - - ReachabilityChanged?.Invoke(); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.maui.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.maui.cs new file mode 100644 index 00000000..ef8acdde --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.maui.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Maui.Networking; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class PlatformConnectivity + { + static PlatformConnectivity() + { + Connectivity.Current.ConnectivityChanged += (sender, args) => ConnectivityChanged?.Invoke(sender, EventArgs.Empty); + } + + /// + /// Gets the current state of network access. + /// + public static LdNetworkAccess LdNetworkAccess => Convert(Connectivity.Current.NetworkAccess); + + /// + /// Gets the active connectivity types for the device. + /// + public static IEnumerable LdConnectionProfiles => Connectivity.Current.ConnectionProfiles.Distinct().Select(Convert); + + /// + /// Occurs when network access or profile has changed. This is just a signal and the + /// event args are empty. This is to avoid the need for an additional event args class + /// container for the list of . + /// + public static event EventHandler ConnectivityChanged; + + private static LdConnectionProfile Convert(ConnectionProfile mauiValue) { + switch (mauiValue) { + case ConnectionProfile.Bluetooth: + return LdConnectionProfile.Bluetooth; + case ConnectionProfile.Cellular: + return LdConnectionProfile.Cellular; + case ConnectionProfile.Ethernet: + return LdConnectionProfile.Ethernet; + case ConnectionProfile.WiFi: + return LdConnectionProfile.WiFi; + default: + return LdConnectionProfile.Unknown; + } + } + + private static LdNetworkAccess Convert(NetworkAccess mauiValue) + { + switch (mauiValue) + { + case NetworkAccess.None: + return LdNetworkAccess.None; + case NetworkAccess.Local: + return LdNetworkAccess.Local; + case NetworkAccess.ConstrainedInternet: + return LdNetworkAccess.ConstrainedInternet; + case NetworkAccess.Internet: + return LdNetworkAccess.Internet; + default: + return LdNetworkAccess.Unknown; + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs index 69098402..9c11397e 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.netstandard.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { - // This code is not from Xamarin Essentials, though it implements the same Connectivity abstraction. + // This code is not from MAUI Essentials, though it implements our Connectivity abstraction. // It is a stub that always reports that we do have network connectivity. // // Unfortunately, in .NET Standard that is the best we can do. There is (at least in 2.0) a @@ -10,25 +11,19 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific // Internet access, just whether we have a network interface (i.e. if we're running a desktop app // on a laptop, and the wi-fi is turned off, it will still return true as long as the laptop has an // Ethernet card-- even if it's not plugged in). - // - // So, in order to support connectivity detection on non-mobile platforms, we would need to add more - // platform-specific variants. For instance, here's how Xamarin Essentials does it for UWP: - // https://github.com/xamarin/Essentials/blob/master/Xamarin.Essentials/Connectivity/Connectivity.uwp.cs - internal static partial class Connectivity + internal static partial class PlatformConnectivity { - static NetworkAccess PlatformNetworkAccess => NetworkAccess.Internet; + public static LdNetworkAccess LdNetworkAccess => LdNetworkAccess.Internet; + + public static event EventHandler ConnectivityChanged; - static IEnumerable PlatformConnectionProfiles + public static IEnumerable LdConnectionProfiles { get { - yield return ConnectionProfile.Unknown; + yield return LdConnectionProfile.Unknown; } } - - static void StartListeners() { } - - static void StopListeners() { } } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs index e26ce0a9..78a56679 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Connectivity.shared.cs @@ -1,32 +1,35 @@ /* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License +The MIT License (MIT) -The MIT License(MIT) -Copyright(c) Microsoft Corporation +Copyright(c).NET Foundation and Contributors -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +All rights reserved. -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ using System; using System.Collections.Generic; -using System.Linq; namespace LaunchDarkly.Sdk.Client.PlatformSpecific { - internal enum ConnectionProfile + internal enum LdConnectionProfile { Unknown = 0, Bluetooth = 1, @@ -35,7 +38,7 @@ internal enum ConnectionProfile WiFi = 4 } - internal enum NetworkAccess + internal enum LdNetworkAccess { Unknown = 0, None = 1, @@ -43,86 +46,4 @@ internal enum NetworkAccess ConstrainedInternet = 3, Internet = 4 } - - internal static partial class Connectivity - { - static event EventHandler ConnectivityChangedInternal; - - // a cache so that events aren't fired unnecessarily - // this is mainly an issue on Android, but we can still do this everywhere - static NetworkAccess currentAccess; - static List currentProfiles; - - public static NetworkAccess NetworkAccess => PlatformNetworkAccess; - - public static IEnumerable ConnectionProfiles => PlatformConnectionProfiles.Distinct(); - - public static event EventHandler ConnectivityChanged - { - add - { - var wasRunning = ConnectivityChangedInternal != null; - - ConnectivityChangedInternal += value; - - if (!wasRunning && ConnectivityChangedInternal != null) - { - SetCurrent(); - StartListeners(); - } - } - - remove - { - var wasRunning = ConnectivityChangedInternal != null; - - ConnectivityChangedInternal -= value; - - if (wasRunning && ConnectivityChangedInternal == null) - StopListeners(); - } - } - - static void SetCurrent() - { - currentAccess = NetworkAccess; - currentProfiles = new List(ConnectionProfiles); - } - - static void OnConnectivityChanged(NetworkAccess access, IEnumerable profiles) - => OnConnectivityChanged(new ConnectivityChangedEventArgs(access, profiles)); - - static void OnConnectivityChanged() - => OnConnectivityChanged(NetworkAccess, ConnectionProfiles); - - static void OnConnectivityChanged(ConnectivityChangedEventArgs e) - { - if (currentAccess != e.NetworkAccess || !currentProfiles.SequenceEqual(e.ConnectionProfiles)) - { - SetCurrent(); - // Modified: Xamarin Essentials did this to guarantee that event handlers would always fire on the - // main thread, but that is not how event handlers normally work in .NET, and our SDK does not have - // any such requirement. - // MainThread.MainThread.BeginInvokeOnMainThread(() => ConnectivityChangedInternal?.Invoke(null, e)); - ConnectivityChangedInternal?.Invoke(null, e); - } - } - } - - internal class ConnectivityChangedEventArgs : EventArgs - { - public ConnectivityChangedEventArgs(NetworkAccess access, IEnumerable connectionProfiles) - { - NetworkAccess = access; - ConnectionProfiles = connectionProfiles; - } - - public NetworkAccess NetworkAccess { get; } - - public IEnumerable ConnectionProfiles { get; } - - public override string ToString() => - $"{nameof(NetworkAccess)}: {NetworkAccess}, " + - $"{nameof(ConnectionProfiles)}: [{string.Join(", ", ConnectionProfiles)}]"; - } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs deleted file mode 100644 index 3212c1a4..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.android.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Android.OS; -using LaunchDarkly.Sdk.EnvReporting.LayerModels; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class DeviceInfo - { - private static OsInfo? PlatformGetOsInfo() => - new OsInfo( - DevicePlatform.Android.ToString(), - DevicePlatform.Android.ToString()+Build.VERSION.SdkInt, - Build.VERSION.Release - ); - - private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => - new EnvReporting.LayerModels.DeviceInfo( - Build.Manufacturer, - Build.Model - ); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs deleted file mode 100644 index 0e5158e9..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.ios.cs +++ /dev/null @@ -1,29 +0,0 @@ -using LaunchDarkly.Sdk.EnvReporting.LayerModels; -#if __WATCHOS__ -using WatchKit; -using UIDevice = WatchKit.WKInterfaceDevice; -#else -using UIKit; -#endif - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class DeviceInfo - { - - private static OsInfo? PlatformGetOsInfo() => - new OsInfo("Apple", GetPlatform().ToString(), UIDevice.CurrentDevice.SystemVersion); - - private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => - new EnvReporting.LayerModels.DeviceInfo( - "Apple", UIDevice.CurrentDevice.Model); - static DevicePlatform GetPlatform() => -#if __IOS__ - DevicePlatform.iOS; -#elif __TVOS__ - DevicePlatform.tvOS; -#elif __WATCHOS__ - DevicePlatform.watchOS; -#endif - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs new file mode 100644 index 00000000..d2b15f44 --- /dev/null +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs @@ -0,0 +1,48 @@ +using LaunchDarkly.Sdk.EnvReporting.LayerModels; +using Devices = Microsoft.Maui.Devices; + +namespace LaunchDarkly.Sdk.Client.PlatformSpecific +{ + internal static partial class DeviceInfo + { + internal static OsInfo? GetOsInfo() => + new OsInfo( + PlatformToFamilyString(Devices.DeviceInfo.Current.Platform), + Devices.DeviceInfo.Current.Platform.ToString(), + Devices.DeviceInfo.Current.VersionString + ); + + internal static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? GetDeviceInfo() => + new EnvReporting.LayerModels.DeviceInfo( + Devices.DeviceInfo.Current.Manufacturer, + Devices.DeviceInfo.Current.Model + ); + + private static string PlatformToFamilyString(Devices.DevicePlatform platform) { + if (platform == Devices.DevicePlatform.Android) + { + return "Android"; + } + else if (platform == Devices.DevicePlatform.iOS || + platform == Devices.DevicePlatform.watchOS || + platform == Devices.DevicePlatform.tvOS || + platform == Devices.DevicePlatform.macOS || + platform == Devices.DevicePlatform.MacCatalyst) + { + return "Apple"; + } + else if(platform == Devices.DevicePlatform.UWP || platform == Devices.DevicePlatform.WinUI) + { + return "Windows"; + } + else if(platform == Devices.DevicePlatform.Tizen) + { + return "Linux"; + } + else + { + return "unknown"; + } + } + } +} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs index 80f12528..49445dd1 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.netstandard.cs @@ -4,8 +4,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific { internal static partial class DeviceInfo { - private static OsInfo? PlatformGetOsInfo() => null; + internal static OsInfo? GetOsInfo() => null; - private static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? PlatformGetDeviceInfo() => null; + internal static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? GetDeviceInfo() => null; } } diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs deleted file mode 100644 index 4f4d267b..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.shared.cs +++ /dev/null @@ -1,11 +0,0 @@ -using LaunchDarkly.Sdk.EnvReporting.LayerModels; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class DeviceInfo - { - internal static OsInfo? GetOsInfo() => PlatformGetOsInfo(); - - internal static LaunchDarkly.Sdk.EnvReporting.LayerModels.DeviceInfo? GetDeviceInfo() => PlatformGetDeviceInfo(); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs deleted file mode 100644 index dc745bb6..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DevicePlatform.shared.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal readonly struct DevicePlatform : IEquatable - { - readonly string devicePlatform; - - public static DevicePlatform Android { get; } = new DevicePlatform(nameof(Android)); - - public static DevicePlatform iOS { get; } = new DevicePlatform(nameof(iOS)); - - public static DevicePlatform macOS { get; } = new DevicePlatform(nameof(macOS)); - - public static DevicePlatform tvOS { get; } = new DevicePlatform(nameof(tvOS)); - - public static DevicePlatform Tizen { get; } = new DevicePlatform(nameof(Tizen)); - - public static DevicePlatform UWP { get; } = new DevicePlatform(nameof(UWP)); - - public static DevicePlatform watchOS { get; } = new DevicePlatform(nameof(watchOS)); - - public static DevicePlatform Unknown { get; } = new DevicePlatform(nameof(Unknown)); - - DevicePlatform(string devicePlatform) - { - if (devicePlatform == null) - throw new ArgumentNullException(nameof(devicePlatform)); - - if (devicePlatform.Length == 0) - throw new ArgumentException(nameof(devicePlatform)); - - this.devicePlatform = devicePlatform; - } - - public static DevicePlatform Create(string devicePlatform) => - new DevicePlatform(devicePlatform); - - public bool Equals(DevicePlatform other) => - Equals(other.devicePlatform); - - internal bool Equals(string other) => - string.Equals(devicePlatform, other, StringComparison.Ordinal); - - public override bool Equals(object obj) => - obj is DevicePlatform && Equals((DevicePlatform)obj); - - public override int GetHashCode() => - devicePlatform == null ? 0 : devicePlatform.GetHashCode(); - - public override string ToString() => - devicePlatform ?? string.Empty; - - public static bool operator ==(DevicePlatform left, DevicePlatform right) => - left.Equals(right); - - public static bool operator !=(DevicePlatform left, DevicePlatform right) => - !left.Equals(right); - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs index beee33e7..b2119110 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/LocalStorage.netstandard.cs @@ -10,8 +10,8 @@ namespace LaunchDarkly.Sdk.Client.PlatformSpecific // strings that are apparently based on the application and assembly name, so the data should be specific to both // the OS user account and the current app. // - // This is based on the Plugin.Settings plugin (which is what Xamarin Essentials uses for preferences), but greatly - // simplified since we only need one data type. See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs + // This is based on the Plugin.Settings plugin, but greatly simplified since we only need one data type. + // See: https://github.com/jamesmontemagno/SettingsPlugin/blob/master/src/Plugin.Settings/Settings.dotnet.cs internal sealed partial class LocalStorage : IPersistentDataStore { diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs index 7d6e220f..1767152d 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Logging.android.cs @@ -1,6 +1,5 @@ using System; using LaunchDarkly.Logging; - using AndroidLog = Android.Util.Log; namespace LaunchDarkly.Sdk.Client.PlatformSpecific @@ -16,8 +15,8 @@ internal static partial class Logging // LoggingConfigurationBuilder.BaseLoggerName). // 2. The underlying Android API is a little different: Log has methods called "d", "i", "w", // and "e" rather than Debug, Info, Warn, and Error, and they always take a message string - // rather than a format string plus variables. Xamarin.Android adds some decoration of its - // own so that we can use .NET-style format strings. + // rather than a format string plus variables. MAUI's Android layer adds some decoration of + // its own so that we can use .NET-style format strings. private sealed class AndroidLogAdapter : ILogAdapter { internal static readonly AndroidLogAdapter Instance = diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs deleted file mode 100644 index 6b287396..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.android.cs +++ /dev/null @@ -1,204 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Android; -using Android.Content.PM; -using Android.OS; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. - // Note that we are no longer using the shared Permissions abstraction at all; this Android code is being used directly - // by other Android-specific code. - - internal static partial class Permissions - { - //static readonly object locker = new object(); - //static int requestCode = 0; - - //static Dictionary tcs)> requests = - //new Dictionary)>(); - - //static void PlatformEnsureDeclared(PermissionType permission) - internal static void EnsureDeclared(PermissionType permission) - { - var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: false); - - // No actual android permissions required here, just return - if (androidPermissions == null || !androidPermissions.Any()) - return; - - var context = Platform.AppContext; - - foreach (var ap in androidPermissions) - { - var packageInfo = context.PackageManager.GetPackageInfo(context.PackageName, PackageInfoFlags.Permissions); - var requestedPermissions = packageInfo?.RequestedPermissions; - - // If the manifest is missing any of the permissions we need, throw - if (!requestedPermissions?.Any(r => r.Equals(ap, StringComparison.OrdinalIgnoreCase)) ?? false) - throw new UnauthorizedAccessException($"You need to declare the permission: `{ap}` in your AndroidManifest.xml"); - } - } - - //static Task PlatformCheckStatusAsync(PermissionType permission) - //{ - // EnsureDeclared(permission); - - // // If there are no android permissions for the given permission type - // // just return granted since we have none to ask for - // var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true); - - // if (androidPermissions == null || !androidPermissions.Any()) - // return Task.FromResult(PermissionStatus.Granted); - - // var context = Platform.Platform.AppContext; - // var targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= BuildVersionCodes.M; - - // foreach (var ap in androidPermissions) - // { - // if (targetsMOrHigher) - // { - // if (ContextCompat.CheckSelfPermission(context, ap) != Permission.Granted) - // return Task.FromResult(PermissionStatus.Denied); - // } - // else - // { - // if (PermissionChecker.CheckSelfPermission(context, ap) != PermissionChecker.PermissionGranted) - // return Task.FromResult(PermissionStatus.Denied); - // } - // } - - // return Task.FromResult(PermissionStatus.Granted); - //} - - //static async Task PlatformRequestAsync(PermissionType permission) - //{ - // // Check status before requesting first - // if (await PlatformCheckStatusAsync(permission) == PermissionStatus.Granted) - // return PermissionStatus.Granted; - - // TaskCompletionSource tcs; - // var doRequest = true; - - // lock (locker) - // { - // if (requests.ContainsKey(permission)) - // { - // tcs = requests[permission].tcs; - // doRequest = false; - // } - // else - // { - // tcs = new TaskCompletionSource(); - - // // Get new request code and wrap it around for next use if it's going to reach max - // if (++requestCode >= int.MaxValue) - // requestCode = 1; - - // requests.Add(permission, (requestCode, tcs)); - // } - // } - - // if (!doRequest) - // return await tcs.Task; - - // if (!MainThread.MainThread.IsMainThread) - // throw new System.UnauthorizedAccessException("Permission request must be invoked on main thread."); - - // var androidPermissions = permission.ToAndroidPermissions(onlyRuntimePermissions: true).ToArray(); - - // ActivityCompat.RequestPermissions(Platform.Platform.GetCurrentActivity(true), androidPermissions, requestCode); - - // return await tcs.Task; - //} - - //internal static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) - //{ - // lock (locker) - // { - // // Check our pending requests for one with a matching request code - // foreach (var kvp in requests) - // { - // if (kvp.Value.requestCode == requestCode) - // { - // var tcs = kvp.Value.tcs; - - // // Look for any denied requests, and deny the whole request if so - // // Remember, each PermissionType is tied to 1 or more android permissions - // // so if any android permissions denied the whole PermissionType is considered denied - // if (grantResults.Any(g => g == Permission.Denied)) - // tcs.TrySetResult(PermissionStatus.Denied); - // else - // tcs.TrySetResult(PermissionStatus.Granted); - // break; - // } - // } - // } - //} - } - - internal static class PermissionTypeExtensions - { - internal static IEnumerable ToAndroidPermissions(this PermissionType permissionType, bool onlyRuntimePermissions) - { - var permissions = new List<(string permission, bool runtimePermission)>(); - - switch (permissionType) - { - case PermissionType.Battery: - permissions.Add((Manifest.Permission.BatteryStats, false)); - break; - case PermissionType.Camera: - permissions.Add((Manifest.Permission.Camera, true)); - break; - case PermissionType.Flashlight: - permissions.Add((Manifest.Permission.Camera, true)); - permissions.Add((Manifest.Permission.Flashlight, false)); - break; - case PermissionType.LocationWhenInUse: - permissions.Add((Manifest.Permission.AccessFineLocation, true)); - permissions.Add((Manifest.Permission.AccessCoarseLocation, true)); - break; - case PermissionType.NetworkState: - permissions.Add((Manifest.Permission.AccessNetworkState, false)); - break; - case PermissionType.Vibrate: - permissions.Add((Manifest.Permission.Vibrate, true)); - break; - } - - if (onlyRuntimePermissions) - { - return permissions - .Where(p => p.runtimePermission) - .Select(p => p.permission); - } - - return permissions.Select(p => p.permission); - } - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs deleted file mode 100644 index c9356027..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Permissions.shared.cs +++ /dev/null @@ -1,53 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal enum PermissionStatus - { - // Denied by user - Denied, - - // Feature is disabled on device - Disabled, - - // Granted by user - Granted, - - // Restricted (only iOS) - Restricted, - - // Permission is in an unknown state - Unknown - } - - internal enum PermissionType - { - Unknown, - Battery, - Camera, - Flashlight, - LocationWhenInUse, - NetworkState, - Vibrate, - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs deleted file mode 100644 index fad87cbe..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.android.cs +++ /dev/null @@ -1,179 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Linq; -using Android.App; -using Android.Content; -using Android.Content.PM; -using Android.Hardware; -using Android.Hardware.Camera2; -using Android.Locations; -using Android.Net; -using Android.Net.Wifi; -using Android.OS; - -// All commented-out code in this file came from Xamarin Essentials but was removed because it is not used by this SDK. - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class Platform - { - //static ActivityLifecycleContextListener lifecycleListener; - - internal static Android.Content.Context AppContext => - Application.Context; - - //internal static Activity GetCurrentActivity(bool throwOnNull) - //{ - // var activity = lifecycleListener?.Activity; - // if (throwOnNull && activity == null) - // throw new NullReferenceException("The current Activity can not be detected. Ensure that you have called Init in your Activity or Application class."); - - // return activity; - //} - - //public static void Init(Application application) - //{ - // lifecycleListener = new ActivityLifecycleContextListener(); - // application.RegisterActivityLifecycleCallbacks(lifecycleListener); - //} - - //public static void Init(Activity activity, Bundle bundle) - //{ - // Init(activity.Application); - // lifecycleListener.Activity = activity; - //} - - //public static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) => - // Permissions.Permissions.OnRequestPermissionsResult(requestCode, permissions, grantResults); - - //internal static bool HasSystemFeature(string systemFeature) - //{ - // var packageManager = AppContext.PackageManager; - // foreach (var feature in packageManager.GetSystemAvailableFeatures()) - // { - // if (feature.Name.Equals(systemFeature, StringComparison.OrdinalIgnoreCase)) - // return true; - // } - // return false; - //} - - //internal static bool IsIntentSupported(Intent intent) - //{ - // var manager = AppContext.PackageManager; - // var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly); - // return activities.Any(); - //} - - internal static bool HasApiLevel(BuildVersionCodes versionCode) => - (int)Build.VERSION.SdkInt >= (int)versionCode; - - //internal static CameraManager CameraManager => - // AppContext.GetSystemService(Context.CameraService) as CameraManager; - - internal static ConnectivityManager ConnectivityManager => - AppContext.GetSystemService(Android.Content.Context.ConnectivityService) as ConnectivityManager; - - //internal static Vibrator Vibrator => - // AppContext.GetSystemService(Context.VibratorService) as Vibrator; - - //internal static WifiManager WifiManager => - // AppContext.GetSystemService(Context.WifiService) as WifiManager; - - //internal static SensorManager SensorManager => - // AppContext.GetSystemService(Context.SensorService) as SensorManager; - - //internal static ClipboardManager ClipboardManager => - // AppContext.GetSystemService(Context.ClipboardService) as ClipboardManager; - - //internal static LocationManager LocationManager => - // AppContext.GetSystemService(Context.LocationService) as LocationManager; - - //internal static PowerManager PowerManager => - //AppContext.GetSystemService(Context.PowerService) as PowerManager; - - //internal static Java.Util.Locale GetLocale() - //{ - // var resources = AppContext.Resources; - // var config = resources.Configuration; - // if (HasApiLevel(BuildVersionCodes.N)) - // return config.Locales.Get(0); - - // return config.Locale; - //} - -// internal static void SetLocale(Java.Util.Locale locale) -// { -// Java.Util.Locale.Default = locale; -// var resources = AppContext.Resources; -// var config = resources.Configuration; -// if (HasApiLevel(BuildVersionCodes.N)) -// config.SetLocale(locale); -// else -// config.Locale = locale; - -//#pragma warning disable CS0618 // Type or member is obsolete -// resources.UpdateConfiguration(config, resources.DisplayMetrics); -//#pragma warning restore CS0618 // Type or member is obsolete - //} - } - - //internal class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks - //{ - // WeakReference currentActivity = new WeakReference(null); - - // internal Context Context => - // Activity ?? Application.Context; - - // internal Activity Activity - // { - // get => currentActivity.TryGetTarget(out var a) ? a : null; - // set => currentActivity.SetTarget(value); - // } - - // void Application.IActivityLifecycleCallbacks.OnActivityCreated(Activity activity, Bundle savedInstanceState) => - // Activity = activity; - - // void Application.IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity) - // { - // } - - // void Application.IActivityLifecycleCallbacks.OnActivityPaused(Activity activity) => - // Activity = activity; - - // void Application.IActivityLifecycleCallbacks.OnActivityResumed(Activity activity) => - // Activity = activity; - - // void Application.IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState) - // { - // } - - // void Application.IActivityLifecycleCallbacks.OnActivityStarted(Activity activity) - // { - // } - - // void Application.IActivityLifecycleCallbacks.OnActivityStopped(Activity activity) - // { - // } - //} -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs deleted file mode 100644 index 13bac166..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/Platform.shared.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* -Xamarin.Essentials(https://github.com/xamarin/Essentials) code used under MIT License - -The MIT License(MIT) -Copyright(c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ -#if !NETSTANDARD - internal static partial class Platform - { - } -#endif -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs deleted file mode 100644 index 1c1b6925..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.android.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Android.OS; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class UserMetadata - { - private static PlatformType PlatformPlatformType => PlatformType.Android; - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs deleted file mode 100644 index 2bbeaa02..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.ios.cs +++ /dev/null @@ -1,9 +0,0 @@ -using UIKit; - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class UserMetadata - { - private static PlatformType PlatformPlatformType => PlatformType.IOs; - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs deleted file mode 100644 index c792015e..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.netstandard.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - internal static partial class UserMetadata - { - private static PlatformType PlatformPlatformType => PlatformType.Standard; - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs deleted file mode 100644 index 8e024056..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/UserMetadata.shared.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client.PlatformSpecific -{ - // This class and the rest of its partial class implementations are not derived from Xamarin Essentials. - - internal static partial class UserMetadata - { - internal static PlatformType PlatformType => PlatformPlatformType; - } -} diff --git a/src/LaunchDarkly.ClientSdk/PlatformType.cs b/src/LaunchDarkly.ClientSdk/PlatformType.cs deleted file mode 100644 index 731b2198..00000000 --- a/src/LaunchDarkly.ClientSdk/PlatformType.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace LaunchDarkly.Sdk.Client -{ - /// - /// Values returned by . - /// - public enum PlatformType - { - /// - /// You are using the .NET Standard version of the SDK. - /// - Standard, - - /// - /// You are using the Android version of the SDK. - /// - Android, - - /// - /// You are using the iOS version of the SDK. - /// - IOs - } -} diff --git a/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs index 4c4e7c75..1a9acf81 100644 --- a/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs +++ b/src/LaunchDarkly.ClientSdk/Properties/AssemblyInfo.cs @@ -7,6 +7,5 @@ // this assembly. [assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.iOS.Tests")] -[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Android.Tests")] +[assembly: InternalsVisibleTo("LaunchDarkly.ClientSdk.Device.Tests")] #endif diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj deleted file mode 100644 index b124682c..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} - Library - Properties - LaunchDarkly.Sdk.Client.Android.Tests - LaunchDarkly.ClientSdk.Android.Tests - 512 - True - Resources\Resource.designer.cs - Resource - Off - v8.1 - Properties\AndroidManifest.xml - Resources - Assets - true - False - {2E7720E4-01A0-403B-863C-C6C596DF5926} - False - armeabi-v7a;arm64-v8a - - - True - portable - False - bin\Debug\ - DEBUG;TRACE - prompt - 4 - None - True - armeabi-v7a;arm64-v8a;x86 - - - True - pdbonly - True - bin\Release\ - TRACE - prompt - 4 - true - SdkOnly - True - armeabi-v7a;arm64-v8a - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - {7717A2B2-9905-40A7-989F-790139D69543} - LaunchDarkly.ClientSdk - - - - - diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs deleted file mode 100644 index 81c8db69..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/MainActivity.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Reflection; -using Android.App; -using Android.OS; -using LaunchDarkly.Sdk.Client.Tests; -using Xunit.Runners.UI; -using Xunit.Sdk; - -// For more details about how this test project works, see CONTRIBUTING.md -namespace LaunchDarkly.Sdk.Client.Android.Tests -{ - [Activity(Label = "LaunchDarkly.Sdk.Client.Android.Tests", MainLauncher = true)] - public class MainActivity : RunnerActivity - { - public MainActivity() - { - ResultChannel = new XunitConsoleLoggingResultChannel(); - } - - protected override void OnCreate(Bundle bundle) - { - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - AddTestAssembly(Assembly.GetExecutingAssembly()); - - AutoStart = true; // this is necessary in order for the CI test job to work - - base.OnCreate(bundle); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AndroidManifest.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AndroidManifest.xml deleted file mode 100644 index e6e74d18..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index c4e33f39..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Xunit; - -// We must disable all parallel test running by XUnit in order for LogSink to work. -[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs deleted file mode 100644 index 7f4e9317..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/Resource.designer.cs +++ /dev/null @@ -1,9575 +0,0 @@ -#pragma warning disable 1591 -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -[assembly: global::Android.Runtime.ResourceDesignerAttribute("LaunchDarkly.Sdk.Client.Android.Tests.Resource", IsApplication=true)] - -namespace LaunchDarkly.Sdk.Client.Android.Tests -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")] - public partial class Resource - { - - static Resource() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - public static void UpdateIdValues() - { - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_fade_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_fade_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_grow_fade_in_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_grow_fade_in_from_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_enter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_popup_enter; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_popup_exit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_popup_exit; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_shrink_fade_out_from_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_shrink_fade_out_from_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_in_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_in_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_in_top; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_out_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Animation.abc_slide_out_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.abc_slide_out_top; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_bottom_sheet_slide_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_bottom_sheet_slide_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_bottom_sheet_slide_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_snackbar_in; - global::Xamarin.Forms.Platform.Android.Resource.Animation.design_snackbar_out = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.design_snackbar_out; - global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.EnterFromLeft; - global::Xamarin.Forms.Platform.Android.Resource.Animation.EnterFromRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.EnterFromRight; - global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.ExitToLeft; - global::Xamarin.Forms.Platform.Android.Resource.Animation.ExitToRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.ExitToRight; - global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_enter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.tooltip_enter; - global::Xamarin.Forms.Platform.Android.Resource.Animation.tooltip_exit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animation.tooltip_exit; - global::Xamarin.Forms.Platform.Android.Resource.Animator.design_appbar_state_list_animator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Animator.design_appbar_state_list_animator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarDivider; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarPopupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarPopupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSplitStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarSplitStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTabTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTabTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarWidgetTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionBarWidgetTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionDropDownStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionDropDownStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionMenuTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionMenuTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionMenuTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCloseButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCloseDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCloseDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCopyDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCopyDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeCutDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeCutDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeFindDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeFindDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePasteDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModePasteDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModePopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModePopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSelectAllDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeSelectAllDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeShareDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeShareDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeSplitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeSplitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionModeWebSearchDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionModeWebSearchDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionOverflowButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionOverflowMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionOverflowMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionProviderClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionProviderClass; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionViewClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.actionViewClass; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.activityChooserViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.activityChooserViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogButtonGroupStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogButtonGroupStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogCenterButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogCenterButtons; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alertDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alertDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.allowStacking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.allowStacking; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alpha; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.alphabeticModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.alphabeticModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowHeadLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.arrowHeadLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.arrowShaftLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.arrowShaftLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoCompleteTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoCompleteTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMaxTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeMaxTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeMinTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeMinTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizePresetSizes = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizePresetSizes; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeStepGranularity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeStepGranularity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.autoSizeTextType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.autoSizeTextType; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.background; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundStacked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundStacked; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.barLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.barLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_autoHide = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_autoHide; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_hideable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_hideable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_overlapTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_overlapTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_peekHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_peekHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.behavior_skipCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.behavior_skipCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.borderWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.borderlessButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.borderlessButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.bottomSheetDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.bottomSheetStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.bottomSheetStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNegativeButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarNegativeButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarNeutralButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarNeutralButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarPositiveButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarPositiveButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonPanelSideLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonPanelSideLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.buttonTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.buttonTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardBackgroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardBackgroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardCornerRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardCornerRadius; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardElevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardMaxElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardMaxElevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardPreventCornerOverlap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardPreventCornerOverlap; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.cardUseCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.cardUseCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkboxStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.checkboxStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.checkedTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.checkedTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.closeIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.closeItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.closeItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapseContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapseIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapseIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapsedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.collapsedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.collapsedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.color; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorAccent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorAccent; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorBackgroundFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorBackgroundFloating; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorButtonNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorButtonNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlActivated = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlActivated; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlHighlight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlHighlight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorControlNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorControlNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorError; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorPrimary; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorPrimaryDark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorPrimaryDark; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.colorSwitchThumbNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.colorSwitchThumbNormal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.commitIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.commitIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.contentScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.contentScrim; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.controlBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.controlBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterMaxLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterMaxLength; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterOverflowTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterOverflowTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.counterTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.counterTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.customNavigationLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.customNavigationLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.defaultQueryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.defaultQueryHint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogPreferredPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dialogPreferredPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.displayOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.displayOptions; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.divider; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerHorizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerHorizontal; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dividerVertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dividerVertical; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawableSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.drawableSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.drawerArrowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.drawerArrowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropDownListViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dropDownListViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.dropdownListPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.dropdownListPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.editTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.editTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.elevation; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.errorEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.errorTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.errorTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandActivityOverflowButtonDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandActivityOverflowButtonDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expanded; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.expandedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.expandedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fabSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fabSize; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollHorizontalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollHorizontalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollHorizontalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollVerticalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fastScrollVerticalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fastScrollVerticalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.font; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderAuthority; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderCerts; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchStrategy; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderFetchTimeout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderPackage; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontProviderQuery; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.foregroundInsidePadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.foregroundInsidePadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.gapBetweenBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.gapBetweenBars; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.goIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.goIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.headerLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.headerLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.height; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hideOnContentScroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hideOnContentScroll; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintAnimationEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintAnimationEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.hintTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.hintTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.homeLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.homeLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.icon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.iconifiedByDefault = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.iconifiedByDefault; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.imageButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.imageButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.indeterminateProgressStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.indeterminateProgressStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.initialActivityCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.initialActivityCount; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.insetForeground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.insetForeground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.isLightTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.isLightTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.keylines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.keylines; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layoutManager = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layoutManager; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_anchorGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_anchorGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_behavior; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_collapseMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_collapseParallaxMultiplier = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_collapseParallaxMultiplier; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_dodgeInsetEdges = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_dodgeInsetEdges; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_insetEdge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_insetEdge; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_keyline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_keyline; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollFlags = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_scrollFlags; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.layout_scrollInterpolator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.layout_scrollInterpolator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listChoiceBackgroundIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listChoiceBackgroundIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listDividerAlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listDividerAlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listMenuViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listMenuViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeightLarge; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemHeightSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemHeightSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.listPreferredItemPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.listPreferredItemPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.logo; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.logoDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.logoDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxActionInlineWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.maxActionInlineWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.maxButtonHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.maxButtonHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.measureWithLargestChild = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.measureWithLargestChild; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.menu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.multiChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.multiChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.navigationMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.navigationMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.numericModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.numericModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.overlapAnchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.overlapAnchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingBottomNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingBottomNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.paddingTopNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.paddingTopNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelMenuListTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.panelMenuListWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.panelMenuListWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.passwordToggleTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.passwordToggleTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.popupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.popupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.preserveIconSpacing = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.preserveIconSpacing; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.pressedTranslationZ = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.pressedTranslationZ; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.progressBarPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.progressBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.progressBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.queryBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.queryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.queryHint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.radioButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.radioButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyleIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.ratingBarStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.ratingBarStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.reverseLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.reverseLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.rippleColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.rippleColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimAnimationDuration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.scrimAnimationDuration; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.scrimVisibleHeightTrigger = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.scrimVisibleHeightTrigger; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchHintIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchHintIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.searchViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.searchViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.seekBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.seekBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.selectableItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.selectableItemBackgroundBorderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.selectableItemBackgroundBorderless; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showAsAction = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showAsAction; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showDividers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showDividers; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showText; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.singleChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.singleChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spanCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spanCount; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinBars; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerDropDownItemStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinnerDropDownItemStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.spinnerStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.spinnerStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.splitTrack = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.splitTrack; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.srcCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.srcCompat; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.stackFromEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.stackFromEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_above_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_above_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_collapsed; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.state_collapsible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.state_collapsible; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.statusBarBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.statusBarScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.statusBarScrim; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subMenuArrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subMenuArrow; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.submitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.submitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.suggestionRowLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.suggestionRowLayout; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.switchTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.switchTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabContentStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabContentStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabGravity; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabIndicatorColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabIndicatorHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabIndicatorHeight; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMaxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMaxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabSelectedTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabSelectedTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tabTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tabTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceLargePopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceLargePopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItem; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSecondary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItemSecondary; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceListItemSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceListItemSmall; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearancePopupMenuHeader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearancePopupMenuHeader; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultSubtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSearchResultSubtitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSearchResultTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSearchResultTitle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textAppearanceSmallPopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textAppearanceSmallPopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorAlertDialogListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorAlertDialogListItem; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorError; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.textColorSearchUrl = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.textColorSearchUrl; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.theme; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thickness; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTextPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTextPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.thumbTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.thumbTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMark; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMarkTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tickMarkTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tickMarkTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.title; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleMargins = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleMargins; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarId; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarNavigationButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarNavigationButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.toolbarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.toolbarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipForegroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipForegroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipFrameBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipFrameBackground; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.tooltipText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.tooltipText; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.track; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.trackTint; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.trackTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.trackTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.useCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.useCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.voiceIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.voiceIcon; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionBarOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionBarOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowActionModeOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowActionModeOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedHeightMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedHeightMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedHeightMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowFixedWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowFixedWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowMinWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowMinWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowMinWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Attribute.windowNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Attribute.windowNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_action_bar_embed_tabs = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_action_bar_embed_tabs; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_allow_stacked_button_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_allow_stacked_button_bar; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_actionMenuItemAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_actionMenuItemAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_closeDialogWhenTouchOutside = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_closeDialogWhenTouchOutside; - global::Xamarin.Forms.Platform.Android.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Boolean.abc_config_showMenuShortcutsWhenKeyboardPresent; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_background_cache_hint_selector_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_background_cache_hint_selector_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_borderless_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_btn_colored_borderless_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_btn_colored_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_btn_colored_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_color_highlight_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_color_highlight_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_hint_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_hint_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_hint_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_input_method_navigation_guard = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_input_method_navigation_guard; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_disable_only_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_disable_only_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_primary_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_primary_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_normal; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_search_url_text_selected = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_search_url_text_selected; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_secondary_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_secondary_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_secondary_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_btn_checkable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_btn_checkable; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_default = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_default; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_edittext = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_edittext; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_seek_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_seek_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_spinner; - global::Xamarin.Forms.Platform.Android.Resource.Color.abc_tint_switch_track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.abc_tint_switch_track; - global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.accent_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.accent_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.accent_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_floating_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_floating_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_floating_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.background_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.background_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_inverse_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_inverse_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_inverse_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.bright_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.bright_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.button_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.button_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.button_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_dark_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_dark_background; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_light_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_light_background; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_end_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_shadow_end_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.cardview_shadow_start_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.cardview_shadow_start_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_bottom_navigation_shadow_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_bottom_navigation_shadow_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_error; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_end_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_end_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_mid_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_mid_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_shadow_start_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_shadow_start_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_inner_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_end_inner_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_end_outer_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_end_outer_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_inner_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_top_inner_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_fab_stroke_top_outer_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_fab_stroke_top_outer_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_snackbar_background_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_snackbar_background_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.design_tint_password_toggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.design_tint_password_toggle; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.dim_foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.dim_foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.error_color_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.error_color_material; - global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.foreground_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.foreground_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.foreground_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.highlighted_text_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.highlighted_text_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.highlighted_text_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_800 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_800; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_900 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_900; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_blue_grey_950 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_blue_grey_950; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_200 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_deep_teal_200; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_deep_teal_500 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_deep_teal_500; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_100 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_100; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_300 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_300; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_50 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_50; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_600 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_600; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_800 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_800; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_850 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_850; - global::Xamarin.Forms.Platform.Android.Resource.Color.material_grey_900 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.material_grey_900; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_action_color_filter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_action_color_filter; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_icon_bg_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_icon_bg_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.notification_material_background_media_default_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.notification_material_background_media_default_color; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_dark_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_dark_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_dark_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_default_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.primary_text_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.primary_text_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.ripple_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.ripple_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_default_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_default_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.secondary_text_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.secondary_text_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_disabled_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_disabled_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_disabled_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_normal_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.switch_thumb_normal_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.switch_thumb_normal_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.tooltip_background_dark; - global::Xamarin.Forms.Platform.Android.Resource.Color.tooltip_background_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Color.tooltip_background_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_content_inset_with_nav = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_content_inset_with_nav; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_end_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_end_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_default_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_default_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_elevation_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_elevation_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_icon_vertical_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_icon_vertical_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_end_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_end_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_overflow_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_overflow_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_progress_bar_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_progress_bar_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_max_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_stacked_max_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_stacked_tab_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_stacked_tab_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_bottom_margin_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_bar_subtitle_top_margin_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_bar_subtitle_top_margin_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_width_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_action_button_min_width_overflow_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_action_button_min_width_overflow_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_alert_dialog_button_bar_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_alert_dialog_button_bar_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_inset_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_button_padding_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_cascading_menus_min_smallest_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_cascading_menus_min_smallest_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_config_prefDialogWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_config_prefDialogWidth; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_corner_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_inset_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_inset_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_control_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_control_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_height_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_height_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_fixed_width_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_fixed_width_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_list_padding_bottom_no_buttons; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_list_padding_top_no_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_list_padding_top_no_title; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_major = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_min_width_major; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_min_width_minor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_min_width_minor; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_padding_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_padding_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_padding_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dialog_title_divider_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dialog_title_divider_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_disabled_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_disabled_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_icon_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_left = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_left; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_dropdownitem_text_padding_right = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_dropdownitem_text_padding_right; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_bottom_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_bottom_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_edit_text_inset_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_edit_text_inset_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_floating_window_z = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_floating_window_z; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_list_item_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_list_item_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_panel_menu_list_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_panel_menu_list_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_progress_bar_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_progress_bar_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_search_view_preferred_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_search_view_preferred_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_search_view_preferred_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_background_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_seekbar_track_background_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_seekbar_track_progress_height_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_seekbar_track_progress_height_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_select_dialog_padding_start_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_select_dialog_padding_start_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_switch_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_switch_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_body_1_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_body_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_body_2_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_button_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_button_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_caption_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_caption_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_1_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_2_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_3_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_3_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_display_4_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_display_4_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_headline_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_headline_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_large_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_large_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_medium_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_medium_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_header_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_menu_header_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_menu_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_menu_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_small_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_small_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subhead_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_subhead_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_subtitle_material_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_subtitle_material_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.abc_text_size_title_material_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.abc_text_size_title_material_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_compat_inset_shadow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_compat_inset_shadow; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_default_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.cardview_default_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.cardview_default_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_inset_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_inset_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_horizontal_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_horizontal_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_button_padding_vertical_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_button_padding_vertical_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.compat_control_corner_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.compat_control_corner_material; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_appbar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_appbar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_item_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_active_item_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_active_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_active_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_item_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_item_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_item_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_shadow_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_shadow_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_navigation_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_navigation_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_modal_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_sheet_modal_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_bottom_sheet_peek_height_min = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_bottom_sheet_peek_height_min; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_border_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_border_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_image_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_image_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_mini = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_size_mini; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_size_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_size_normal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_fab_translation_z_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_fab_translation_z_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_icon_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_padding_bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_padding_bottom; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_navigation_separator_vertical_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_navigation_separator_vertical_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_action_inline_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_action_inline_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_background_corner_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_background_corner_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_extra_spacing_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_extra_spacing_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_padding_vertical_2lines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_padding_vertical_2lines; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_snackbar_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_snackbar_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_max_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_max_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_scrollable_min_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_scrollable_min_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.design_tab_text_size_2line = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.design_tab_text_size_2line; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.disabled_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.disabled_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.disabled_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_default_thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_default_thickness; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.fastscroll_minimum_range = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.fastscroll_minimum_range; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_colored; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.highlight_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.highlight_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_dark; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.hint_pressed_alpha_material_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.hint_pressed_alpha_material_light; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_max_drag_scroll_per_frame; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_max_velocity; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.item_touch_helper_swipe_escape_velocity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.item_touch_helper_swipe_escape_velocity; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_action_text_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_action_text_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_big_circle_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_big_circle_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_content_margin_start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_content_margin_start; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_height; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_large_icon_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_large_icon_width; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_main_column_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_main_column_padding_top; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_media_narrow_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_media_narrow_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_icon_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_icon_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_right_side_padding_top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_right_side_padding_top; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_background_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_background_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_small_icon_size_as_large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_small_icon_size_as_large; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_subtext_size = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_subtext_size; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.notification_top_pad_large_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.notification_top_pad_large_text; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_corner_radius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_corner_radius; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_horizontal_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_horizontal_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_margin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_margin; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_extra_offset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_precise_anchor_extra_offset; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_precise_anchor_threshold = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_precise_anchor_threshold; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_vertical_padding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_vertical_padding; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_non_touch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_y_offset_non_touch; - global::Xamarin.Forms.Platform.Android.Resource.Dimension.tooltip_y_offset_touch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Dimension.tooltip_y_offset_touch; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ab_share_pack_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ab_share_pack_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_action_bar_item_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_action_bar_item_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_borderless_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_borderless_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_check_to_on_mtrl_015 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_check_to_on_mtrl_015; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_colored_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_colored_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_default_mtrl_shape = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_default_mtrl_shape; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_radio_to_on_mtrl_015 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_radio_to_on_mtrl_015; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00001; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_btn_switch_to_on_mtrl_00012; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_internal_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_internal_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_top_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_cab_background_top_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_cab_background_top_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_control_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_control_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_dialog_material_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_dialog_material_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_edit_text_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_edit_text_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_ab_back_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_ab_back_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_arrow_drop_right_black_24dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_clear_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_clear_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_commit_search_api_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_go_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_go_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_copy_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_cut_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_overflow_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_overflow_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_paste_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_selectall_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_menu_share_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_menu_share_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_16dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_16dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_36dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_36dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_black_48dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_black_48dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_16dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_16dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_36dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_36dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_star_half_black_48dp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_star_half_black_48dp; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ic_voice_search_api_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ic_voice_search_api_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_item_background_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_item_background_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_item_background_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_divider_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_divider_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_focused_holo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_focused_holo; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_longpressed_holo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_longpressed_holo; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_pressed_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_pressed_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_pressed_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_background_transition_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_background_transition_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_disabled_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_disabled_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_holo_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_list_selector_holo_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_list_selector_holo_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_menu_hardkey_panel_mtrl_mult; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_popup_background_mtrl_mult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_popup_background_mtrl_mult; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_indicator_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_indicator_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_ratingbar_small_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_ratingbar_small_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_off_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_000; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_control_to_pressed_mtrl_005; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_primary_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_primary_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_scrubber_track_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_scrubber_track_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_thumb_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_thumb_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_tick_mark_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_tick_mark_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_seekbar_track_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_seekbar_track_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_mtrl_am_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_spinner_mtrl_am_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_spinner_textfield_background_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_spinner_textfield_background_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_thumb_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_switch_thumb_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_switch_track_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_switch_track_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_tab_indicator_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_tab_indicator_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_tab_indicator_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_cursor_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_cursor_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_left_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_left_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_middle_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_middle_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_text_select_handle_right_mtrl_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_text_select_handle_right_mtrl_light; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_activated_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_activated_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_default_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_default_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_activated_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_default_mtrl_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_default_mtrl_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_textfield_search_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_textfield_search_material; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.abc_vector_test = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.abc_vector_test; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_hide_password = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.avd_hide_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.avd_show_password = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.avd_show_password; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_bottom_navigation_item_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_bottom_navigation_item_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_fab_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_fab_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_ic_visibility; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_ic_visibility_off = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_ic_visibility_off; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_password_eye = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_password_eye; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.design_snackbar_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.design_snackbar_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.navigation_empty_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.navigation_empty_icon; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_action_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_action_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_normal; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_low_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_low_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_bg_normal_pressed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_bg_normal_pressed; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_icon_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_icon_background; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_template_icon_low_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_template_icon_low_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notification_tile_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notification_tile_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.notify_panel_notification_icon_bg = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.notify_panel_notification_icon_bg; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.tooltip_frame_dark; - global::Xamarin.Forms.Platform.Android.Resource.Drawable.tooltip_frame_light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Drawable.tooltip_frame_light; - global::Xamarin.Forms.Platform.Android.Resource.Id.ALT = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ALT; - global::Xamarin.Forms.Platform.Android.Resource.Id.CTRL = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.CTRL; - global::Xamarin.Forms.Platform.Android.Resource.Id.FUNCTION = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.FUNCTION; - global::Xamarin.Forms.Platform.Android.Resource.Id.META = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.META; - global::Xamarin.Forms.Platform.Android.Resource.Id.SHIFT = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.SHIFT; - global::Xamarin.Forms.Platform.Android.Resource.Id.SYM = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.SYM; - global::Xamarin.Forms.Platform.Android.Resource.Id.action0 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action0; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_activity_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_activity_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_root = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_root; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_spinner; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_bar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_bar_title; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_context_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_context_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_divider; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_image; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_menu_divider; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_menu_presenter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_menu_presenter; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_bar_stub = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_bar_stub; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_mode_close_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_mode_close_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.action_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.action_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.actions; - global::Xamarin.Forms.Platform.Android.Resource.Id.activity_chooser_view_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.activity_chooser_view_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.add = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.add; - global::Xamarin.Forms.Platform.Android.Resource.Id.alertTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.alertTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.all = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.all; - global::Xamarin.Forms.Platform.Android.Resource.Id.always = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.always; - global::Xamarin.Forms.Platform.Android.Resource.Id.async = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.async; - global::Xamarin.Forms.Platform.Android.Resource.Id.auto = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.auto; - global::Xamarin.Forms.Platform.Android.Resource.Id.beginning = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.beginning; - global::Xamarin.Forms.Platform.Android.Resource.Id.blocking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.blocking; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottom; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_navarea = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottomtab_navarea; - global::Xamarin.Forms.Platform.Android.Resource.Id.bottomtab_tabbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.bottomtab_tabbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.buttonPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.buttonPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.cancel_action; - global::Xamarin.Forms.Platform.Android.Resource.Id.center = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center; - global::Xamarin.Forms.Platform.Android.Resource.Id.center_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.center_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.center_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.checkbox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.checkbox; - global::Xamarin.Forms.Platform.Android.Resource.Id.chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.chronometer; - global::Xamarin.Forms.Platform.Android.Resource.Id.clip_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.clip_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.clip_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.clip_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.collapseActionView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.collapseActionView; - global::Xamarin.Forms.Platform.Android.Resource.Id.container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.container; - global::Xamarin.Forms.Platform.Android.Resource.Id.contentPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.contentPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.coordinator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.coordinator; - global::Xamarin.Forms.Platform.Android.Resource.Id.custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.custom; - global::Xamarin.Forms.Platform.Android.Resource.Id.customPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.customPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.decor_content_parent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.decor_content_parent; - global::Xamarin.Forms.Platform.Android.Resource.Id.default_activity_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.default_activity_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_bottom_sheet = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_bottom_sheet; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_action_area; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_action_area_stub = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_action_area_stub; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_menu_item_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_menu_item_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.design_navigation_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.design_navigation_view; - global::Xamarin.Forms.Platform.Android.Resource.Id.disableHome = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.disableHome; - global::Xamarin.Forms.Platform.Android.Resource.Id.edit_query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.edit_query; - global::Xamarin.Forms.Platform.Android.Resource.Id.end = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end; - global::Xamarin.Forms.Platform.Android.Resource.Id.end_padder = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.end_padder; - global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlways = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.enterAlways; - global::Xamarin.Forms.Platform.Android.Resource.Id.enterAlwaysCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.enterAlwaysCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Id.exitUntilCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.exitUntilCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Id.expand_activities_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.expand_activities_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.expanded_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.expanded_menu; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.fill_vertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.fill_vertical; - global::Xamarin.Forms.Platform.Android.Resource.Id.@fixed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.@fixed; - global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.flyoutcontent_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.flyoutcontent_recycler = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.flyoutcontent_recycler; - global::Xamarin.Forms.Platform.Android.Resource.Id.forever = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.forever; - global::Xamarin.Forms.Platform.Android.Resource.Id.ghost_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ghost_view; - global::Xamarin.Forms.Platform.Android.Resource.Id.home = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.home; - global::Xamarin.Forms.Platform.Android.Resource.Id.homeAsUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.homeAsUp; - global::Xamarin.Forms.Platform.Android.Resource.Id.icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.icon_group; - global::Xamarin.Forms.Platform.Android.Resource.Id.ifRoom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.ifRoom; - global::Xamarin.Forms.Platform.Android.Resource.Id.image = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.image; - global::Xamarin.Forms.Platform.Android.Resource.Id.info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.info; - global::Xamarin.Forms.Platform.Android.Resource.Id.italic = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.italic; - global::Xamarin.Forms.Platform.Android.Resource.Id.item_touch_helper_previous_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.item_touch_helper_previous_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Id.largeLabel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.largeLabel; - global::Xamarin.Forms.Platform.Android.Resource.Id.left = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.left; - global::Xamarin.Forms.Platform.Android.Resource.Id.line1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line1; - global::Xamarin.Forms.Platform.Android.Resource.Id.line3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.line3; - global::Xamarin.Forms.Platform.Android.Resource.Id.listMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.listMode; - global::Xamarin.Forms.Platform.Android.Resource.Id.list_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.list_item; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_scrollview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_scrollview; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_tablayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_tablayout; - global::Xamarin.Forms.Platform.Android.Resource.Id.main_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.main_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.masked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.masked; - global::Xamarin.Forms.Platform.Android.Resource.Id.media_actions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.media_actions; - global::Xamarin.Forms.Platform.Android.Resource.Id.message = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.message; - global::Xamarin.Forms.Platform.Android.Resource.Id.middle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.middle; - global::Xamarin.Forms.Platform.Android.Resource.Id.mini = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.mini; - global::Xamarin.Forms.Platform.Android.Resource.Id.multiply = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.multiply; - global::Xamarin.Forms.Platform.Android.Resource.Id.navigation_header_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.navigation_header_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.never = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.never; - global::Xamarin.Forms.Platform.Android.Resource.Id.none = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.none; - global::Xamarin.Forms.Platform.Android.Resource.Id.normal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.normal; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_background; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column; - global::Xamarin.Forms.Platform.Android.Resource.Id.notification_main_column_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.notification_main_column_container; - global::Xamarin.Forms.Platform.Android.Resource.Id.parallax = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parallax; - global::Xamarin.Forms.Platform.Android.Resource.Id.parentPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parentPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.parent_matrix = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.parent_matrix; - global::Xamarin.Forms.Platform.Android.Resource.Id.pin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.pin; - global::Xamarin.Forms.Platform.Android.Resource.Id.progress_circular = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.progress_circular; - global::Xamarin.Forms.Platform.Android.Resource.Id.progress_horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.progress_horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Id.radio = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.radio; - global::Xamarin.Forms.Platform.Android.Resource.Id.right = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right; - global::Xamarin.Forms.Platform.Android.Resource.Id.right_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.right_side = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.right_side; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_image_matrix = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_image_matrix; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_non_transition_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_non_transition_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Id.save_scale_type = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.save_scale_type; - global::Xamarin.Forms.Platform.Android.Resource.Id.screen = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.screen; - global::Xamarin.Forms.Platform.Android.Resource.Id.scroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scroll; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollIndicatorDown; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollIndicatorUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollIndicatorUp; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollView; - global::Xamarin.Forms.Platform.Android.Resource.Id.scrollable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.scrollable; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_badge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_badge; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_button; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_close_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_close_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_edit_frame = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_edit_frame; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_go_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_go_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_mag_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_mag_icon; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_plate = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_plate; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_src_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_src_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.search_voice_btn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.search_voice_btn; - global::Xamarin.Forms.Platform.Android.Resource.Id.select_dialog_listview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.select_dialog_listview; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_appbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_appbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_scrollview = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_scrollview; - global::Xamarin.Forms.Platform.Android.Resource.Id.shellcontent_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shellcontent_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Id.shortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.shortcut; - global::Xamarin.Forms.Platform.Android.Resource.Id.showCustom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showCustom; - global::Xamarin.Forms.Platform.Android.Resource.Id.showHome = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showHome; - global::Xamarin.Forms.Platform.Android.Resource.Id.showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.smallLabel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.smallLabel; - global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snackbar_action; - global::Xamarin.Forms.Platform.Android.Resource.Id.snackbar_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snackbar_text; - global::Xamarin.Forms.Platform.Android.Resource.Id.snap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.snap; - global::Xamarin.Forms.Platform.Android.Resource.Id.spacer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.spacer; - global::Xamarin.Forms.Platform.Android.Resource.Id.split_action_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.split_action_bar; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_atop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_atop; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_in = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_in; - global::Xamarin.Forms.Platform.Android.Resource.Id.src_over = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.src_over; - global::Xamarin.Forms.Platform.Android.Resource.Id.start = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.start; - global::Xamarin.Forms.Platform.Android.Resource.Id.status_bar_latest_event_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.status_bar_latest_event_content; - global::Xamarin.Forms.Platform.Android.Resource.Id.submenuarrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.submenuarrow; - global::Xamarin.Forms.Platform.Android.Resource.Id.submit_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.submit_area; - global::Xamarin.Forms.Platform.Android.Resource.Id.tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Id.tag_transition_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.tag_transition_group; - global::Xamarin.Forms.Platform.Android.Resource.Id.text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text; - global::Xamarin.Forms.Platform.Android.Resource.Id.text2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text2; - global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textSpacerNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Id.textSpacerNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textSpacerNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Id.text_input_password_toggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.text_input_password_toggle; - global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_counter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textinput_counter; - global::Xamarin.Forms.Platform.Android.Resource.Id.textinput_error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.textinput_error; - global::Xamarin.Forms.Platform.Android.Resource.Id.time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.time; - global::Xamarin.Forms.Platform.Android.Resource.Id.title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title; - global::Xamarin.Forms.Platform.Android.Resource.Id.titleDividerNoCustom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.titleDividerNoCustom; - global::Xamarin.Forms.Platform.Android.Resource.Id.title_template = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.title_template; - global::Xamarin.Forms.Platform.Android.Resource.Id.top = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.top; - global::Xamarin.Forms.Platform.Android.Resource.Id.topPanel = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.topPanel; - global::Xamarin.Forms.Platform.Android.Resource.Id.touch_outside = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.touch_outside; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_current_scene = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_current_scene; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_layout_save = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_layout_save; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_position = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_position; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_scene_layoutid_cache = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_scene_layoutid_cache; - global::Xamarin.Forms.Platform.Android.Resource.Id.transition_transform = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.transition_transform; - global::Xamarin.Forms.Platform.Android.Resource.Id.uniform = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.uniform; - global::Xamarin.Forms.Platform.Android.Resource.Id.up = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.up; - global::Xamarin.Forms.Platform.Android.Resource.Id.useLogo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.useLogo; - global::Xamarin.Forms.Platform.Android.Resource.Id.view_offset_helper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.view_offset_helper; - global::Xamarin.Forms.Platform.Android.Resource.Id.visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.visible; - global::Xamarin.Forms.Platform.Android.Resource.Id.withText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.withText; - global::Xamarin.Forms.Platform.Android.Resource.Id.wrap_content = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Id.wrap_content; - global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityDefaultDur = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.abc_config_activityDefaultDur; - global::Xamarin.Forms.Platform.Android.Resource.Integer.abc_config_activityShortDur = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.abc_config_activityShortDur; - global::Xamarin.Forms.Platform.Android.Resource.Integer.app_bar_elevation_anim_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.app_bar_elevation_anim_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.bottom_sheet_slide_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.bottom_sheet_slide_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.cancel_button_image_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.cancel_button_image_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Integer.config_tooltipAnimTime = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.config_tooltipAnimTime; - global::Xamarin.Forms.Platform.Android.Resource.Integer.design_snackbar_text_max_lines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.design_snackbar_text_max_lines; - global::Xamarin.Forms.Platform.Android.Resource.Integer.hide_password_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.hide_password_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.show_password_duration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.show_password_duration; - global::Xamarin.Forms.Platform.Android.Resource.Integer.status_bar_notification_info_maxnum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Integer.status_bar_notification_info_maxnum; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_title_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_bar_title_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_bar_up_container = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_bar_up_container; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_menu_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_menu_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_bar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_mode_bar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_action_mode_close_item_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_action_mode_close_item_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_activity_chooser_view; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_activity_chooser_view_list_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_activity_chooser_view_list_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_button_bar_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_button_bar_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_alert_dialog_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_alert_dialog_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_dialog_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_dialog_title_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_expanded_menu_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_expanded_menu_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_checkbox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_checkbox; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_list_menu_item_radio = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_list_menu_item_radio; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_header_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_popup_menu_header_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_popup_menu_item_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_popup_menu_item_layout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_content_include = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_content_include; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_simple; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_simple_overlay_action_mode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_simple_overlay_action_mode; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_screen_toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_screen_toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_dropdown_item_icons_2line = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_search_dropdown_item_icons_2line; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_search_view = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_search_view; - global::Xamarin.Forms.Platform.Android.Resource.Layout.abc_select_dialog_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.abc_select_dialog_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.BottomTabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.BottomTabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_navigation_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_bottom_navigation_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_bottom_sheet_dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_bottom_sheet_dialog; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_snackbar; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_snackbar_include = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_snackbar_include; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_tab_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_layout_tab_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_layout_tab_text; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_menu_item_action_area = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_menu_item_action_area; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_header; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_separator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_separator; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_item_subheader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_item_subheader; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_menu; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_navigation_menu_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_navigation_menu_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.design_text_input_password_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.design_text_input_password_icon; - global::Xamarin.Forms.Platform.Android.Resource.Layout.FlyoutContent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.FlyoutContent; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_action_tombstone = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_action_tombstone; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_media_cancel_action = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_media_cancel_action; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_big_media_narrow_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_big_media_narrow_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_custom_big = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_custom_big; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_icon_group = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_icon_group; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_lines_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_lines_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_media_custom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_media_custom; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_chronometer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_chronometer; - global::Xamarin.Forms.Platform.Android.Resource.Layout.notification_template_part_time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.notification_template_part_time; - global::Xamarin.Forms.Platform.Android.Resource.Layout.RootLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.RootLayout; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_item_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_item_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_multichoice_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_multichoice_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.select_dialog_singlechoice_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.select_dialog_singlechoice_material; - global::Xamarin.Forms.Platform.Android.Resource.Layout.ShellContent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.ShellContent; - global::Xamarin.Forms.Platform.Android.Resource.Layout.support_simple_spinner_dropdown_item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.support_simple_spinner_dropdown_item; - global::Xamarin.Forms.Platform.Android.Resource.Layout.tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Layout.tooltip; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_home_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_bar_home_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_bar_up_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_bar_up_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_menu_overflow_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_menu_overflow_description; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_action_mode_done = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_action_mode_done; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_activity_chooser_view_see_all = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_activity_chooser_view_see_all; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_activitychooserview_choose_application = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_activitychooserview_choose_application; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_off = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_capital_off; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_capital_on = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_capital_on; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_body_1_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_body_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_body_2_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_button_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_button_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_caption_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_caption_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_1_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_1_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_2_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_2_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_3_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_3_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_display_4_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_display_4_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_headline_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_headline_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_menu_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_menu_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_subhead_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_subhead_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_font_family_title_material = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_font_family_title_material; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_search_hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_search_hint; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_clear = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_clear; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_query; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_search = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_search; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_submit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_submit; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_searchview_description_voice = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_searchview_description_voice; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_shareactionprovider_share_with; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_shareactionprovider_share_with_application = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_shareactionprovider_share_with_application; - global::Xamarin.Forms.Platform.Android.Resource.String.abc_toolbar_collapse_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.abc_toolbar_collapse_description; - global::Xamarin.Forms.Platform.Android.Resource.String.appbar_scrolling_view_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.appbar_scrolling_view_behavior; - global::Xamarin.Forms.Platform.Android.Resource.String.bottom_sheet_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.bottom_sheet_behavior; - global::Xamarin.Forms.Platform.Android.Resource.String.character_counter_pattern = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.character_counter_pattern; - global::Xamarin.Forms.Platform.Android.Resource.String.password_toggle_content_description = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.password_toggle_content_description; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_strike_through = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye_mask_strike_through; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_eye_mask_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_eye_mask_visible; - global::Xamarin.Forms.Platform.Android.Resource.String.path_password_strike_through = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.path_password_strike_through; - global::Xamarin.Forms.Platform.Android.Resource.String.search_menu_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.search_menu_title; - global::Xamarin.Forms.Platform.Android.Resource.String.status_bar_notification_info_overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.String.status_bar_notification_info_overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.AlertDialog_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.AlertDialog_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.AlertDialog_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_DropDownUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_DropDownUp; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Animation_Design_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Animation_Design_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_AlertDialog_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_AlertDialog_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_DropDownUp = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_DropDownUp; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Animation_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Animation_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_CardView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitle_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_DialogWindowTitle_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_DialogWindowTitleBackground_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_DialogWindowTitleBackground_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body1; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Body2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Body2; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Caption = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Caption; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display1; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display2; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display3; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Display4 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Display4; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Headline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Headline; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Large_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Medium_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Small_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Subhead_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_ActionMode_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Button_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_DropDownItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Header; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_TextAppearance_Widget_AppCompat_Toolbar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_CompactMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_CompactMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_FixedSize; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_FixedSize; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Theme_AppCompat_Light_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dark_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V11_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V12_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V12_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V14_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V14_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V21_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V21_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V22_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V22_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V23_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V23_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V26_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V26_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_V7_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_V7_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Button_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Button_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_CheckBox; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_RadioButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_CompoundButton_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DrawerArrowToggle_Common; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ImageButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ImageButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Light_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ListView_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ListView_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_ProgressBar_Horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Indicator; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_RatingBar_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SearchView_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_SeekBar_Discrete; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Spinner_Underlined; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_AppCompat_Toolbar_Button_Navigation; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Base_Widget_Design_TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Base_Widget_Design_TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.CardView_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.CardView_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V11_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V11_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V11_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V14_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V14_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V14_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V21_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V21_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V21_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V25_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_V25_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_V25_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Platform_Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Platform_Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_DialogWindowTitle_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_ActionBar_TitleItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_DialogTitle_Icon; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_PopupMenuItem_Text; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Query; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_Search_DropDown_Text; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlOverlay_Widget_AppCompat_SearchView_MagIcon; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.RtlUnderlay_Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body1; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Body2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Body2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Caption = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Caption; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display1 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display1; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display3 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display3; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Display4 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Display4; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Headline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Headline; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Large_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Large_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Light_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Medium_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Medium_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_SearchResult_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_SearchResult_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Small_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Small_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Subhead_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Tooltip = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Tooltip; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Button_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_DropDownItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Header; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Large; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_PopupMenu_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_AppCompat_Widget_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Info_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Info_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Line2_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Line2_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Time_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Time_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Compat_Notification_Title_Media = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Compat_Notification_Title_Media; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_CollapsingToolbar_Expanded; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Counter; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Counter_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Counter_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Error = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Error; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Hint; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Snackbar_Message = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Snackbar_Message; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Design_Tab = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Design_Tab; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_ExpandedMenu_Item; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.TextAppearance_Widget_AppCompat_Toolbar_Title; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_CompactMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_CompactMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DayNight_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DayNight_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DarkActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_DarkActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_Dialog_MinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_DialogWhenLarge; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_Light_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_Light_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_AppCompat_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_AppCompat_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_BottomSheetDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light_BottomSheetDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_Light_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_Light_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Theme_Design_NoActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Theme_Design_NoActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dark_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Dialog_Alert; - global::Xamarin.Forms.Platform.Android.Resource.Style.ThemeOverlay_AppCompat_Light = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.ThemeOverlay_AppCompat_Light; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Borderless_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Borderless_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Colored = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Colored; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Button_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Button_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ButtonBar_AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_CheckBox; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_RadioButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_CompoundButton_Switch = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_CompoundButton_Switch; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_EditText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_EditText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ImageButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ImageButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_Solid_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabBar_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabText_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionBar_TabView_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_CloseMode; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionButton_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActionMode_Inverse; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_AutoCompleteTextView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_DropDownItem_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Light_Spinner_DropDown_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ListView_Menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ListView_Menu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupMenu_Overflow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupMenu_Overflow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_ProgressBar_Horizontal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Indicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Indicator; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_RatingBar_Small = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_RatingBar_Small; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SearchView_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SearchView_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_SeekBar_Discrete = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_SeekBar_Discrete; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_DropDown_ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Spinner_Underlined = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Spinner_Underlined; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_TextView_SpinnerItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_TextView_SpinnerItem; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_AppCompat_Toolbar_Button_Navigation; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionContainer = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionContainer; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Compat_NotificationActionText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Compat_NotificationActionText; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomNavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_BottomNavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_BottomSheet_Modal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_BottomSheet_Modal; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CollapsingToolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_CollapsingToolbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_CoordinatorLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_CoordinatorLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_FloatingActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_FloatingActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_NavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_NavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_ScrimInsetsFrameLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_ScrimInsetsFrameLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_Snackbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_Snackbar; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Style.Widget_Design_TextInputLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Style.Widget_Design_TextInputLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_backgroundStacked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_backgroundStacked; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_customNavigationLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_customNavigationLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_displayOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_displayOptions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_divider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_hideOnContentScroll = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_hideOnContentScroll; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_homeLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_homeLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_indeterminateProgressStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_indeterminateProgressStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_itemPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_itemPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_logo; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_navigationMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_navigationMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_progressBarPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_progressBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_progressBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBar_titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBar_titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionBarLayout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionBarLayout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuItemView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuItemView_android_minWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuItemView_android_minWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMenuView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_backgroundSplit = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_backgroundSplit; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_closeItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_closeItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_subtitleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_subtitleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActionMode_titleTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActionMode_titleTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView_expandActivityOverflowButtonDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ActivityChooserView_initialActivityCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ActivityChooserView_initialActivityCount; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_android_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_buttonPanelSideLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_buttonPanelSideLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_listItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_listLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_listLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_multiChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_multiChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_showTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_showTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AlertDialog_singleChoiceItemLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AlertDialog_singleChoiceItemLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_keyboardNavigationCluster; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_android_touchscreenBlocksFocus; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_expanded = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_expanded; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayoutStates_state_collapsible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayoutStates_state_collapsible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollFlags; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppBarLayout_Layout_layout_scrollInterpolator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_android_src = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_android_src; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_srcCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_srcCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_tint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatImageView_tintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatImageView_tintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_android_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_android_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMark; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatSeekBar_tickMarkTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_drawableTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_drawableTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextHelper_android_textAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextHelper_android_textAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_android_textAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_android_textAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMaxTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeMinTextSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizePresetSizes = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizePresetSizes; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeStepGranularity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_autoSizeTextType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_autoSizeTextType; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTextView_textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTextView_textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarPopupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarPopupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarSplitStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarSplitStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTabTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionBarWidgetTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionDropDownStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionDropDownStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionMenuTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionMenuTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCloseDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCopyDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeCutDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeCutDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeFindDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeFindDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePasteDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePasteDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModePopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSelectAllDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeShareDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeShareDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeSplitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeSplitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionModeWebSearchDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_actionOverflowMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_activityChooserViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_activityChooserViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogButtonGroupStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogCenterButtons; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_alertDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_alertDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_android_windowIsFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_android_windowIsFloating; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_autoCompleteTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_borderlessButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_borderlessButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNegativeButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarNeutralButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarPositiveButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_buttonStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_buttonStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkboxStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_checkboxStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_checkedTextViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_checkedTextViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorAccent = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorAccent; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorBackgroundFloating = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorBackgroundFloating; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorButtonNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorButtonNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlActivated = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlActivated; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlHighlight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlHighlight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorControlNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorControlNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorError; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimary; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorPrimaryDark = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorPrimaryDark; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_colorSwitchThumbNormal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_controlBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_controlBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogPreferredPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dialogPreferredPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerHorizontal = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dividerHorizontal; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dividerVertical = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dividerVertical; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropDownListViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dropDownListViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_dropdownListPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_editTextStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_editTextStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_homeAsUpIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_homeAsUpIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_imageButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_imageButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listChoiceBackgroundIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listDividerAlertDialog = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listDividerAlertDialog; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listMenuViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listMenuViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPopupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPopupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightLarge; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemHeightSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_listPreferredItemPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_panelMenuListWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_panelMenuListWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupMenuStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_popupMenuStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_popupWindowStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_popupWindowStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_radioButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_radioButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleIndicator; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_ratingBarStyleSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_searchViewStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_searchViewStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_seekBarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_seekBarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_selectableItemBackgroundBorderless; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerDropDownItemStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_spinnerStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_spinnerStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_switchStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_switchStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceLargePopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSecondary; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceListItemSmall; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearancePopupMenuHeader; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultSubtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSearchResultTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textAppearanceSmallPopupMenu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textColorAlertDialogListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_textColorSearchUrl = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_textColorSearchUrl; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarNavigationButtonStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_toolbarStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_toolbarStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipForegroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipForegroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_tooltipFrameBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_tooltipFrameBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionBarOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionBarOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowActionModeOverlay = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowActionModeOverlay; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedHeightMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowFixedWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMajor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMajor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowMinWidthMinor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowMinWidthMinor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.AppCompatTheme_windowNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.AppCompatTheme_windowNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomNavigationView_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomNavigationView_menu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_hideable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_peekHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ButtonBarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ButtonBarLayout_allowStacking = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ButtonBarLayout_allowStacking; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_android_minHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_android_minWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_android_minWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardBackgroundColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardBackgroundColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardCornerRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardCornerRadius; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardElevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardMaxElevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardMaxElevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardPreventCornerOverlap = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardPreventCornerOverlap; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_cardUseCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_cardUseCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CardView_contentPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CardView_contentPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_contentScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_contentScrim; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_expandedTitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimAnimationDuration; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_statusBarScrim; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_titleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_titleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_toolbarId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_toolbarId; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_alpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_android_alpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ColorStateListItem_android_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ColorStateListItem_android_color; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_android_button = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_android_button; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_buttonTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CompoundButton_buttonTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CompoundButton_buttonTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_keylines = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_keylines; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_statusBarBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_statusBarBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_anchorGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_behavior; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_insetEdge; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.CoordinatorLayout_Layout_layout_keyline; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetDialogTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetDialogTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_bottomSheetStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_bottomSheetStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DesignTheme_textColorError = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DesignTheme_textColorError; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowHeadLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowHeadLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_arrowShaftLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_arrowShaftLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_barLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_barLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_color = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_color; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_drawableSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_drawableSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_gapBetweenBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_gapBetweenBars; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_spinBars = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_spinBars; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.DrawerArrowToggle_thickness = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.DrawerArrowToggle_thickness; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_borderWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_borderWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_fabSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_fabSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_pressedTranslationZ = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_pressedTranslationZ; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_rippleColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_rippleColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_useCompatPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_useCompatPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderAuthority = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderAuthority; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderCerts = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderCerts; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchStrategy; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderFetchTimeout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderPackage = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderPackage; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamily_fontProviderQuery = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamily_fontProviderQuery; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_font; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_android_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_android_fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_font = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_font; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.FontFamilyFont_fontWeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.FontFamilyFont_fontWeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foreground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foreground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_android_foregroundGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ForegroundLinearLayout_foregroundInsidePadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAligned = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAligned; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_baselineAlignedChildIndex; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_orientation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_orientation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_android_weightSum = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_android_weightSum; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_divider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_divider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_dividerPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_dividerPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_measureWithLargestChild; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_showDividers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_showDividers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_height; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_weight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.LinearLayoutCompat_Layout_android_layout_width; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownHorizontalOffset; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ListPopupWindow_android_dropDownVerticalOffset; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_checkableBehavior = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_checkableBehavior; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_enabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_enabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_menuCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_menuCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_orderInCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_orderInCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuGroup_android_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuGroup_android_visible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionProviderClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionProviderClass; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_actionViewClass = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_actionViewClass; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_alphabeticModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_alphabeticModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_alphabeticShortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_alphabeticShortcut; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checkable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_checkable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_checked = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_checked; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_enabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_enabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_menuCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_menuCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_numericShortcut = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_numericShortcut; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_onClick = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_onClick; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_orderInCategory = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_orderInCategory; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_titleCondensed = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_titleCondensed; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_android_visible = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_android_visible; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_contentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_contentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_iconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_iconTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_iconTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_numericModifiers = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_numericModifiers; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_showAsAction = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_showAsAction; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuItem_tooltipText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuItem_tooltipText; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_headerBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_headerBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_horizontalDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_horizontalDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemIconDisabledAlpha = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemIconDisabledAlpha; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_verticalDivider = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_verticalDivider; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_android_windowAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_android_windowAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_preserveIconSpacing = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_preserveIconSpacing; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.MenuView_subMenuArrow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.MenuView_subMenuArrow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_fitsSystemWindows = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_fitsSystemWindows; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_headerLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_headerLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemIconTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemIconTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_itemTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_itemTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.NavigationView_menu = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.NavigationView_menu; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupAnimationStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_android_popupAnimationStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_android_popupBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_android_popupBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindow_overlapAnchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindow_overlapAnchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindowBackgroundState; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.PopupWindowBackgroundState_state_above_anchor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingBottomNoButtons = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView_paddingBottomNoButtons; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecycleListView_paddingTopNoTitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecycleListView_paddingTopNoTitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_descendantFocusability = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_android_descendantFocusability; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_android_orientation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_android_orientation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollHorizontalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalThumbDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_fastScrollVerticalTrackDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_layoutManager = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_layoutManager; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_reverseLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_reverseLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_spanCount = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_spanCount; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.RecyclerView_stackFromEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.RecyclerView_stackFromEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrimInsetsFrameLayout_insetForeground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ScrollingViewBehavior_Layout_behavior_overlapTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_focusable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_focusable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_imeOptions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_imeOptions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_inputType = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_inputType; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_closeIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_closeIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_commitIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_commitIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_defaultQueryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_defaultQueryHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_goIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_goIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_iconifiedByDefault = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_iconifiedByDefault; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_queryBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_queryHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_queryHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchHintIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_searchHintIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_searchIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_searchIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_submitBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_submitBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_suggestionRowLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_suggestionRowLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SearchView_voiceIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SearchView_voiceIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_android_maxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_android_maxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_elevation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_elevation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SnackbarLayout_maxActionInlineWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SnackbarLayout_maxActionInlineWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_dropDownWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_dropDownWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_entries = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_entries; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_popupBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_popupBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_android_prompt = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_android_prompt; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Spinner_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Spinner_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOff = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_textOff; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_textOn = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_textOn; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_android_thumb = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_android_thumb; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_showText = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_showText; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_splitTrack = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_splitTrack; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_switchTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_switchTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTextPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTextPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_thumbTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_thumbTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_track = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_track; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_trackTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.SwitchCompat_trackTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.SwitchCompat_trackTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_icon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_icon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_layout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabItem_android_text = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabItem_android_text; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabBackground = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabBackground; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabContentStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabContentStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabIndicatorHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabIndicatorHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMaxWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMaxWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMinWidth = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMinWidth; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPadding = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPadding; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabPaddingTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabPaddingTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabSelectedTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabSelectedTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TabLayout_tabTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TabLayout_tabTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDx = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDx; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowDy = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowDy; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_shadowRadius = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_shadowRadius; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColorHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textColorLink = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textColorLink; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textSize = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textSize; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_textStyle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_textStyle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_android_typeface = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_android_typeface; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_fontFamily = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_fontFamily; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextAppearance_textAllCaps = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextAppearance_textAllCaps; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_hint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_android_hint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_android_textColorHint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_android_textColorHint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterMaxLength = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterMaxLength; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterOverflowTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_counterTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_counterTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_errorEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_errorTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_errorTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintAnimationEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintAnimationEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_hintTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_hintTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleDrawable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleDrawable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleEnabled = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleEnabled; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.TextInputLayout_passwordToggleTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.TextInputLayout_passwordToggleTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_gravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_android_gravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_android_minHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_android_minHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_buttonGravity = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_buttonGravity; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_collapseContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_collapseIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_collapseIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetEndWithActions = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetEndWithActions; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetLeft = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetLeft; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetRight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetRight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_contentInsetStartWithNavigation = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_contentInsetStartWithNavigation; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logo = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_logo; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_logoDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_logoDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_maxButtonHeight = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_maxButtonHeight; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationContentDescription = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_navigationContentDescription; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_navigationIcon = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_navigationIcon; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_popupTheme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_popupTheme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitle = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitle; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_subtitleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_subtitleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_title = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_title; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargin = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMargin; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginBottom = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginBottom; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMarginTop = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMarginTop; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleMargins = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleMargins; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextAppearance = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleTextAppearance; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.Toolbar_titleTextColor = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.Toolbar_titleTextColor; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_focusable = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_android_focusable; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_android_theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_android_theme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingEnd = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_paddingEnd; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_paddingStart = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_paddingStart; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.View_theme = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.View_theme; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_android_background = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_android_background; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTint = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTint; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewBackgroundHelper_backgroundTintMode; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_id = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_id; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_inflatedId = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_inflatedId; - global::Xamarin.Forms.Platform.Android.Resource.Styleable.ViewStubCompat_android_layout = global::LaunchDarkly.Sdk.Client.Android.Tests.Resource.Styleable.ViewStubCompat_android_layout; - } - - public partial class Animation - { - - // aapt resource value: 0x7F010000 - public const int abc_fade_in = 2130771968; - - // aapt resource value: 0x7F010001 - public const int abc_fade_out = 2130771969; - - // aapt resource value: 0x7F010002 - public const int abc_grow_fade_in_from_bottom = 2130771970; - - // aapt resource value: 0x7F010003 - public const int abc_popup_enter = 2130771971; - - // aapt resource value: 0x7F010004 - public const int abc_popup_exit = 2130771972; - - // aapt resource value: 0x7F010005 - public const int abc_shrink_fade_out_from_bottom = 2130771973; - - // aapt resource value: 0x7F010006 - public const int abc_slide_in_bottom = 2130771974; - - // aapt resource value: 0x7F010007 - public const int abc_slide_in_top = 2130771975; - - // aapt resource value: 0x7F010008 - public const int abc_slide_out_bottom = 2130771976; - - // aapt resource value: 0x7F010009 - public const int abc_slide_out_top = 2130771977; - - // aapt resource value: 0x7F01000A - public const int design_bottom_sheet_slide_in = 2130771978; - - // aapt resource value: 0x7F01000B - public const int design_bottom_sheet_slide_out = 2130771979; - - // aapt resource value: 0x7F01000C - public const int design_snackbar_in = 2130771980; - - // aapt resource value: 0x7F01000D - public const int design_snackbar_out = 2130771981; - - // aapt resource value: 0x7F01000E - public const int EnterFromLeft = 2130771982; - - // aapt resource value: 0x7F01000F - public const int EnterFromRight = 2130771983; - - // aapt resource value: 0x7F010010 - public const int ExitToLeft = 2130771984; - - // aapt resource value: 0x7F010011 - public const int ExitToRight = 2130771985; - - // aapt resource value: 0x7F010012 - public const int tooltip_enter = 2130771986; - - // aapt resource value: 0x7F010013 - public const int tooltip_exit = 2130771987; - - static Animation() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Animation() - { - } - } - - public partial class Animator - { - - // aapt resource value: 0x7F020000 - public const int design_appbar_state_list_animator = 2130837504; - - static Animator() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Animator() - { - } - } - - public partial class Attribute - { - - // aapt resource value: 0x7F030000 - public const int actionBarDivider = 2130903040; - - // aapt resource value: 0x7F030001 - public const int actionBarItemBackground = 2130903041; - - // aapt resource value: 0x7F030002 - public const int actionBarPopupTheme = 2130903042; - - // aapt resource value: 0x7F030003 - public const int actionBarSize = 2130903043; - - // aapt resource value: 0x7F030004 - public const int actionBarSplitStyle = 2130903044; - - // aapt resource value: 0x7F030005 - public const int actionBarStyle = 2130903045; - - // aapt resource value: 0x7F030006 - public const int actionBarTabBarStyle = 2130903046; - - // aapt resource value: 0x7F030007 - public const int actionBarTabStyle = 2130903047; - - // aapt resource value: 0x7F030008 - public const int actionBarTabTextStyle = 2130903048; - - // aapt resource value: 0x7F030009 - public const int actionBarTheme = 2130903049; - - // aapt resource value: 0x7F03000A - public const int actionBarWidgetTheme = 2130903050; - - // aapt resource value: 0x7F03000B - public const int actionButtonStyle = 2130903051; - - // aapt resource value: 0x7F03000C - public const int actionDropDownStyle = 2130903052; - - // aapt resource value: 0x7F03000D - public const int actionLayout = 2130903053; - - // aapt resource value: 0x7F03000E - public const int actionMenuTextAppearance = 2130903054; - - // aapt resource value: 0x7F03000F - public const int actionMenuTextColor = 2130903055; - - // aapt resource value: 0x7F030010 - public const int actionModeBackground = 2130903056; - - // aapt resource value: 0x7F030011 - public const int actionModeCloseButtonStyle = 2130903057; - - // aapt resource value: 0x7F030012 - public const int actionModeCloseDrawable = 2130903058; - - // aapt resource value: 0x7F030013 - public const int actionModeCopyDrawable = 2130903059; - - // aapt resource value: 0x7F030014 - public const int actionModeCutDrawable = 2130903060; - - // aapt resource value: 0x7F030015 - public const int actionModeFindDrawable = 2130903061; - - // aapt resource value: 0x7F030016 - public const int actionModePasteDrawable = 2130903062; - - // aapt resource value: 0x7F030017 - public const int actionModePopupWindowStyle = 2130903063; - - // aapt resource value: 0x7F030018 - public const int actionModeSelectAllDrawable = 2130903064; - - // aapt resource value: 0x7F030019 - public const int actionModeShareDrawable = 2130903065; - - // aapt resource value: 0x7F03001A - public const int actionModeSplitBackground = 2130903066; - - // aapt resource value: 0x7F03001B - public const int actionModeStyle = 2130903067; - - // aapt resource value: 0x7F03001C - public const int actionModeWebSearchDrawable = 2130903068; - - // aapt resource value: 0x7F03001D - public const int actionOverflowButtonStyle = 2130903069; - - // aapt resource value: 0x7F03001E - public const int actionOverflowMenuStyle = 2130903070; - - // aapt resource value: 0x7F03001F - public const int actionProviderClass = 2130903071; - - // aapt resource value: 0x7F030020 - public const int actionViewClass = 2130903072; - - // aapt resource value: 0x7F030021 - public const int activityChooserViewStyle = 2130903073; - - // aapt resource value: 0x7F030022 - public const int alertDialogButtonGroupStyle = 2130903074; - - // aapt resource value: 0x7F030023 - public const int alertDialogCenterButtons = 2130903075; - - // aapt resource value: 0x7F030024 - public const int alertDialogStyle = 2130903076; - - // aapt resource value: 0x7F030025 - public const int alertDialogTheme = 2130903077; - - // aapt resource value: 0x7F030026 - public const int allowStacking = 2130903078; - - // aapt resource value: 0x7F030027 - public const int alpha = 2130903079; - - // aapt resource value: 0x7F030028 - public const int alphabeticModifiers = 2130903080; - - // aapt resource value: 0x7F030029 - public const int arrowHeadLength = 2130903081; - - // aapt resource value: 0x7F03002A - public const int arrowShaftLength = 2130903082; - - // aapt resource value: 0x7F03002B - public const int autoCompleteTextViewStyle = 2130903083; - - // aapt resource value: 0x7F03002C - public const int autoSizeMaxTextSize = 2130903084; - - // aapt resource value: 0x7F03002D - public const int autoSizeMinTextSize = 2130903085; - - // aapt resource value: 0x7F03002E - public const int autoSizePresetSizes = 2130903086; - - // aapt resource value: 0x7F03002F - public const int autoSizeStepGranularity = 2130903087; - - // aapt resource value: 0x7F030030 - public const int autoSizeTextType = 2130903088; - - // aapt resource value: 0x7F030031 - public const int background = 2130903089; - - // aapt resource value: 0x7F030032 - public const int backgroundSplit = 2130903090; - - // aapt resource value: 0x7F030033 - public const int backgroundStacked = 2130903091; - - // aapt resource value: 0x7F030034 - public const int backgroundTint = 2130903092; - - // aapt resource value: 0x7F030035 - public const int backgroundTintMode = 2130903093; - - // aapt resource value: 0x7F030036 - public const int barLength = 2130903094; - - // aapt resource value: 0x7F030037 - public const int behavior_autoHide = 2130903095; - - // aapt resource value: 0x7F030038 - public const int behavior_hideable = 2130903096; - - // aapt resource value: 0x7F030039 - public const int behavior_overlapTop = 2130903097; - - // aapt resource value: 0x7F03003A - public const int behavior_peekHeight = 2130903098; - - // aapt resource value: 0x7F03003B - public const int behavior_skipCollapsed = 2130903099; - - // aapt resource value: 0x7F03003D - public const int borderlessButtonStyle = 2130903101; - - // aapt resource value: 0x7F03003C - public const int borderWidth = 2130903100; - - // aapt resource value: 0x7F03003E - public const int bottomSheetDialogTheme = 2130903102; - - // aapt resource value: 0x7F03003F - public const int bottomSheetStyle = 2130903103; - - // aapt resource value: 0x7F030040 - public const int buttonBarButtonStyle = 2130903104; - - // aapt resource value: 0x7F030041 - public const int buttonBarNegativeButtonStyle = 2130903105; - - // aapt resource value: 0x7F030042 - public const int buttonBarNeutralButtonStyle = 2130903106; - - // aapt resource value: 0x7F030043 - public const int buttonBarPositiveButtonStyle = 2130903107; - - // aapt resource value: 0x7F030044 - public const int buttonBarStyle = 2130903108; - - // aapt resource value: 0x7F030045 - public const int buttonGravity = 2130903109; - - // aapt resource value: 0x7F030046 - public const int buttonPanelSideLayout = 2130903110; - - // aapt resource value: 0x7F030047 - public const int buttonStyle = 2130903111; - - // aapt resource value: 0x7F030048 - public const int buttonStyleSmall = 2130903112; - - // aapt resource value: 0x7F030049 - public const int buttonTint = 2130903113; - - // aapt resource value: 0x7F03004A - public const int buttonTintMode = 2130903114; - - // aapt resource value: 0x7F03004B - public const int cardBackgroundColor = 2130903115; - - // aapt resource value: 0x7F03004C - public const int cardCornerRadius = 2130903116; - - // aapt resource value: 0x7F03004D - public const int cardElevation = 2130903117; - - // aapt resource value: 0x7F03004E - public const int cardMaxElevation = 2130903118; - - // aapt resource value: 0x7F03004F - public const int cardPreventCornerOverlap = 2130903119; - - // aapt resource value: 0x7F030050 - public const int cardUseCompatPadding = 2130903120; - - // aapt resource value: 0x7F030051 - public const int checkboxStyle = 2130903121; - - // aapt resource value: 0x7F030052 - public const int checkedTextViewStyle = 2130903122; - - // aapt resource value: 0x7F030053 - public const int closeIcon = 2130903123; - - // aapt resource value: 0x7F030054 - public const int closeItemLayout = 2130903124; - - // aapt resource value: 0x7F030055 - public const int collapseContentDescription = 2130903125; - - // aapt resource value: 0x7F030057 - public const int collapsedTitleGravity = 2130903127; - - // aapt resource value: 0x7F030058 - public const int collapsedTitleTextAppearance = 2130903128; - - // aapt resource value: 0x7F030056 - public const int collapseIcon = 2130903126; - - // aapt resource value: 0x7F030059 - public const int color = 2130903129; - - // aapt resource value: 0x7F03005A - public const int colorAccent = 2130903130; - - // aapt resource value: 0x7F03005B - public const int colorBackgroundFloating = 2130903131; - - // aapt resource value: 0x7F03005C - public const int colorButtonNormal = 2130903132; - - // aapt resource value: 0x7F03005D - public const int colorControlActivated = 2130903133; - - // aapt resource value: 0x7F03005E - public const int colorControlHighlight = 2130903134; - - // aapt resource value: 0x7F03005F - public const int colorControlNormal = 2130903135; - - // aapt resource value: 0x7F030060 - public const int colorError = 2130903136; - - // aapt resource value: 0x7F030061 - public const int colorPrimary = 2130903137; - - // aapt resource value: 0x7F030062 - public const int colorPrimaryDark = 2130903138; - - // aapt resource value: 0x7F030063 - public const int colorSwitchThumbNormal = 2130903139; - - // aapt resource value: 0x7F030064 - public const int commitIcon = 2130903140; - - // aapt resource value: 0x7F030065 - public const int contentDescription = 2130903141; - - // aapt resource value: 0x7F030066 - public const int contentInsetEnd = 2130903142; - - // aapt resource value: 0x7F030067 - public const int contentInsetEndWithActions = 2130903143; - - // aapt resource value: 0x7F030068 - public const int contentInsetLeft = 2130903144; - - // aapt resource value: 0x7F030069 - public const int contentInsetRight = 2130903145; - - // aapt resource value: 0x7F03006A - public const int contentInsetStart = 2130903146; - - // aapt resource value: 0x7F03006B - public const int contentInsetStartWithNavigation = 2130903147; - - // aapt resource value: 0x7F03006C - public const int contentPadding = 2130903148; - - // aapt resource value: 0x7F03006D - public const int contentPaddingBottom = 2130903149; - - // aapt resource value: 0x7F03006E - public const int contentPaddingLeft = 2130903150; - - // aapt resource value: 0x7F03006F - public const int contentPaddingRight = 2130903151; - - // aapt resource value: 0x7F030070 - public const int contentPaddingTop = 2130903152; - - // aapt resource value: 0x7F030071 - public const int contentScrim = 2130903153; - - // aapt resource value: 0x7F030072 - public const int controlBackground = 2130903154; - - // aapt resource value: 0x7F030073 - public const int counterEnabled = 2130903155; - - // aapt resource value: 0x7F030074 - public const int counterMaxLength = 2130903156; - - // aapt resource value: 0x7F030075 - public const int counterOverflowTextAppearance = 2130903157; - - // aapt resource value: 0x7F030076 - public const int counterTextAppearance = 2130903158; - - // aapt resource value: 0x7F030077 - public const int customNavigationLayout = 2130903159; - - // aapt resource value: 0x7F030078 - public const int defaultQueryHint = 2130903160; - - // aapt resource value: 0x7F030079 - public const int dialogPreferredPadding = 2130903161; - - // aapt resource value: 0x7F03007A - public const int dialogTheme = 2130903162; - - // aapt resource value: 0x7F03007B - public const int displayOptions = 2130903163; - - // aapt resource value: 0x7F03007C - public const int divider = 2130903164; - - // aapt resource value: 0x7F03007D - public const int dividerHorizontal = 2130903165; - - // aapt resource value: 0x7F03007E - public const int dividerPadding = 2130903166; - - // aapt resource value: 0x7F03007F - public const int dividerVertical = 2130903167; - - // aapt resource value: 0x7F030080 - public const int drawableSize = 2130903168; - - // aapt resource value: 0x7F030081 - public const int drawerArrowStyle = 2130903169; - - // aapt resource value: 0x7F030083 - public const int dropdownListPreferredItemHeight = 2130903171; - - // aapt resource value: 0x7F030082 - public const int dropDownListViewStyle = 2130903170; - - // aapt resource value: 0x7F030084 - public const int editTextBackground = 2130903172; - - // aapt resource value: 0x7F030085 - public const int editTextColor = 2130903173; - - // aapt resource value: 0x7F030086 - public const int editTextStyle = 2130903174; - - // aapt resource value: 0x7F030087 - public const int elevation = 2130903175; - - // aapt resource value: 0x7F030088 - public const int errorEnabled = 2130903176; - - // aapt resource value: 0x7F030089 - public const int errorTextAppearance = 2130903177; - - // aapt resource value: 0x7F03008A - public const int expandActivityOverflowButtonDrawable = 2130903178; - - // aapt resource value: 0x7F03008B - public const int expanded = 2130903179; - - // aapt resource value: 0x7F03008C - public const int expandedTitleGravity = 2130903180; - - // aapt resource value: 0x7F03008D - public const int expandedTitleMargin = 2130903181; - - // aapt resource value: 0x7F03008E - public const int expandedTitleMarginBottom = 2130903182; - - // aapt resource value: 0x7F03008F - public const int expandedTitleMarginEnd = 2130903183; - - // aapt resource value: 0x7F030090 - public const int expandedTitleMarginStart = 2130903184; - - // aapt resource value: 0x7F030091 - public const int expandedTitleMarginTop = 2130903185; - - // aapt resource value: 0x7F030092 - public const int expandedTitleTextAppearance = 2130903186; - - // aapt resource value: 0x7F030093 - public const int externalRouteEnabledDrawable = 2130903187; - - // aapt resource value: 0x7F030094 - public const int fabSize = 2130903188; - - // aapt resource value: 0x7F030095 - public const int fastScrollEnabled = 2130903189; - - // aapt resource value: 0x7F030096 - public const int fastScrollHorizontalThumbDrawable = 2130903190; - - // aapt resource value: 0x7F030097 - public const int fastScrollHorizontalTrackDrawable = 2130903191; - - // aapt resource value: 0x7F030098 - public const int fastScrollVerticalThumbDrawable = 2130903192; - - // aapt resource value: 0x7F030099 - public const int fastScrollVerticalTrackDrawable = 2130903193; - - // aapt resource value: 0x7F03009A - public const int font = 2130903194; - - // aapt resource value: 0x7F03009B - public const int fontFamily = 2130903195; - - // aapt resource value: 0x7F03009C - public const int fontProviderAuthority = 2130903196; - - // aapt resource value: 0x7F03009D - public const int fontProviderCerts = 2130903197; - - // aapt resource value: 0x7F03009E - public const int fontProviderFetchStrategy = 2130903198; - - // aapt resource value: 0x7F03009F - public const int fontProviderFetchTimeout = 2130903199; - - // aapt resource value: 0x7F0300A0 - public const int fontProviderPackage = 2130903200; - - // aapt resource value: 0x7F0300A1 - public const int fontProviderQuery = 2130903201; - - // aapt resource value: 0x7F0300A2 - public const int fontStyle = 2130903202; - - // aapt resource value: 0x7F0300A3 - public const int fontWeight = 2130903203; - - // aapt resource value: 0x7F0300A4 - public const int foregroundInsidePadding = 2130903204; - - // aapt resource value: 0x7F0300A5 - public const int gapBetweenBars = 2130903205; - - // aapt resource value: 0x7F0300A6 - public const int goIcon = 2130903206; - - // aapt resource value: 0x7F0300A7 - public const int headerLayout = 2130903207; - - // aapt resource value: 0x7F0300A8 - public const int height = 2130903208; - - // aapt resource value: 0x7F0300A9 - public const int hideOnContentScroll = 2130903209; - - // aapt resource value: 0x7F0300AA - public const int hintAnimationEnabled = 2130903210; - - // aapt resource value: 0x7F0300AB - public const int hintEnabled = 2130903211; - - // aapt resource value: 0x7F0300AC - public const int hintTextAppearance = 2130903212; - - // aapt resource value: 0x7F0300AD - public const int homeAsUpIndicator = 2130903213; - - // aapt resource value: 0x7F0300AE - public const int homeLayout = 2130903214; - - // aapt resource value: 0x7F0300AF - public const int icon = 2130903215; - - // aapt resource value: 0x7F0300B2 - public const int iconifiedByDefault = 2130903218; - - // aapt resource value: 0x7F0300B0 - public const int iconTint = 2130903216; - - // aapt resource value: 0x7F0300B1 - public const int iconTintMode = 2130903217; - - // aapt resource value: 0x7F0300B3 - public const int imageButtonStyle = 2130903219; - - // aapt resource value: 0x7F0300B4 - public const int indeterminateProgressStyle = 2130903220; - - // aapt resource value: 0x7F0300B5 - public const int initialActivityCount = 2130903221; - - // aapt resource value: 0x7F0300B6 - public const int insetForeground = 2130903222; - - // aapt resource value: 0x7F0300B7 - public const int isLightTheme = 2130903223; - - // aapt resource value: 0x7F0300B8 - public const int itemBackground = 2130903224; - - // aapt resource value: 0x7F0300B9 - public const int itemIconTint = 2130903225; - - // aapt resource value: 0x7F0300BA - public const int itemPadding = 2130903226; - - // aapt resource value: 0x7F0300BB - public const int itemTextAppearance = 2130903227; - - // aapt resource value: 0x7F0300BC - public const int itemTextColor = 2130903228; - - // aapt resource value: 0x7F0300BD - public const int keylines = 2130903229; - - // aapt resource value: 0x7F0300BE - public const int layout = 2130903230; - - // aapt resource value: 0x7F0300BF - public const int layoutManager = 2130903231; - - // aapt resource value: 0x7F0300C0 - public const int layout_anchor = 2130903232; - - // aapt resource value: 0x7F0300C1 - public const int layout_anchorGravity = 2130903233; - - // aapt resource value: 0x7F0300C2 - public const int layout_behavior = 2130903234; - - // aapt resource value: 0x7F0300C3 - public const int layout_collapseMode = 2130903235; - - // aapt resource value: 0x7F0300C4 - public const int layout_collapseParallaxMultiplier = 2130903236; - - // aapt resource value: 0x7F0300C5 - public const int layout_dodgeInsetEdges = 2130903237; - - // aapt resource value: 0x7F0300C6 - public const int layout_insetEdge = 2130903238; - - // aapt resource value: 0x7F0300C7 - public const int layout_keyline = 2130903239; - - // aapt resource value: 0x7F0300C8 - public const int layout_scrollFlags = 2130903240; - - // aapt resource value: 0x7F0300C9 - public const int layout_scrollInterpolator = 2130903241; - - // aapt resource value: 0x7F0300CA - public const int listChoiceBackgroundIndicator = 2130903242; - - // aapt resource value: 0x7F0300CB - public const int listDividerAlertDialog = 2130903243; - - // aapt resource value: 0x7F0300CC - public const int listItemLayout = 2130903244; - - // aapt resource value: 0x7F0300CD - public const int listLayout = 2130903245; - - // aapt resource value: 0x7F0300CE - public const int listMenuViewStyle = 2130903246; - - // aapt resource value: 0x7F0300CF - public const int listPopupWindowStyle = 2130903247; - - // aapt resource value: 0x7F0300D0 - public const int listPreferredItemHeight = 2130903248; - - // aapt resource value: 0x7F0300D1 - public const int listPreferredItemHeightLarge = 2130903249; - - // aapt resource value: 0x7F0300D2 - public const int listPreferredItemHeightSmall = 2130903250; - - // aapt resource value: 0x7F0300D3 - public const int listPreferredItemPaddingLeft = 2130903251; - - // aapt resource value: 0x7F0300D4 - public const int listPreferredItemPaddingRight = 2130903252; - - // aapt resource value: 0x7F0300D5 - public const int logo = 2130903253; - - // aapt resource value: 0x7F0300D6 - public const int logoDescription = 2130903254; - - // aapt resource value: 0x7F0300D7 - public const int maxActionInlineWidth = 2130903255; - - // aapt resource value: 0x7F0300D8 - public const int maxButtonHeight = 2130903256; - - // aapt resource value: 0x7F0300D9 - public const int measureWithLargestChild = 2130903257; - - // aapt resource value: 0x7F0300DA - public const int mediaRouteAudioTrackDrawable = 2130903258; - - // aapt resource value: 0x7F0300DB - public const int mediaRouteButtonStyle = 2130903259; - - // aapt resource value: 0x7F0300DC - public const int mediaRouteButtonTint = 2130903260; - - // aapt resource value: 0x7F0300DD - public const int mediaRouteCloseDrawable = 2130903261; - - // aapt resource value: 0x7F0300DE - public const int mediaRouteControlPanelThemeOverlay = 2130903262; - - // aapt resource value: 0x7F0300DF - public const int mediaRouteDefaultIconDrawable = 2130903263; - - // aapt resource value: 0x7F0300E0 - public const int mediaRoutePauseDrawable = 2130903264; - - // aapt resource value: 0x7F0300E1 - public const int mediaRoutePlayDrawable = 2130903265; - - // aapt resource value: 0x7F0300E2 - public const int mediaRouteSpeakerGroupIconDrawable = 2130903266; - - // aapt resource value: 0x7F0300E3 - public const int mediaRouteSpeakerIconDrawable = 2130903267; - - // aapt resource value: 0x7F0300E4 - public const int mediaRouteStopDrawable = 2130903268; - - // aapt resource value: 0x7F0300E5 - public const int mediaRouteTheme = 2130903269; - - // aapt resource value: 0x7F0300E6 - public const int mediaRouteTvIconDrawable = 2130903270; - - // aapt resource value: 0x7F0300E7 - public const int menu = 2130903271; - - // aapt resource value: 0x7F0300E8 - public const int multiChoiceItemLayout = 2130903272; - - // aapt resource value: 0x7F0300E9 - public const int navigationContentDescription = 2130903273; - - // aapt resource value: 0x7F0300EA - public const int navigationIcon = 2130903274; - - // aapt resource value: 0x7F0300EB - public const int navigationMode = 2130903275; - - // aapt resource value: 0x7F0300EC - public const int numericModifiers = 2130903276; - - // aapt resource value: 0x7F0300ED - public const int overlapAnchor = 2130903277; - - // aapt resource value: 0x7F0300EE - public const int paddingBottomNoButtons = 2130903278; - - // aapt resource value: 0x7F0300EF - public const int paddingEnd = 2130903279; - - // aapt resource value: 0x7F0300F0 - public const int paddingStart = 2130903280; - - // aapt resource value: 0x7F0300F1 - public const int paddingTopNoTitle = 2130903281; - - // aapt resource value: 0x7F0300F2 - public const int panelBackground = 2130903282; - - // aapt resource value: 0x7F0300F3 - public const int panelMenuListTheme = 2130903283; - - // aapt resource value: 0x7F0300F4 - public const int panelMenuListWidth = 2130903284; - - // aapt resource value: 0x7F0300F5 - public const int passwordToggleContentDescription = 2130903285; - - // aapt resource value: 0x7F0300F6 - public const int passwordToggleDrawable = 2130903286; - - // aapt resource value: 0x7F0300F7 - public const int passwordToggleEnabled = 2130903287; - - // aapt resource value: 0x7F0300F8 - public const int passwordToggleTint = 2130903288; - - // aapt resource value: 0x7F0300F9 - public const int passwordToggleTintMode = 2130903289; - - // aapt resource value: 0x7F0300FA - public const int popupMenuStyle = 2130903290; - - // aapt resource value: 0x7F0300FB - public const int popupTheme = 2130903291; - - // aapt resource value: 0x7F0300FC - public const int popupWindowStyle = 2130903292; - - // aapt resource value: 0x7F0300FD - public const int preserveIconSpacing = 2130903293; - - // aapt resource value: 0x7F0300FE - public const int pressedTranslationZ = 2130903294; - - // aapt resource value: 0x7F0300FF - public const int progressBarPadding = 2130903295; - - // aapt resource value: 0x7F030100 - public const int progressBarStyle = 2130903296; - - // aapt resource value: 0x7F030101 - public const int queryBackground = 2130903297; - - // aapt resource value: 0x7F030102 - public const int queryHint = 2130903298; - - // aapt resource value: 0x7F030103 - public const int radioButtonStyle = 2130903299; - - // aapt resource value: 0x7F030104 - public const int ratingBarStyle = 2130903300; - - // aapt resource value: 0x7F030105 - public const int ratingBarStyleIndicator = 2130903301; - - // aapt resource value: 0x7F030106 - public const int ratingBarStyleSmall = 2130903302; - - // aapt resource value: 0x7F030107 - public const int reverseLayout = 2130903303; - - // aapt resource value: 0x7F030108 - public const int rippleColor = 2130903304; - - // aapt resource value: 0x7F030109 - public const int scrimAnimationDuration = 2130903305; - - // aapt resource value: 0x7F03010A - public const int scrimVisibleHeightTrigger = 2130903306; - - // aapt resource value: 0x7F03010B - public const int searchHintIcon = 2130903307; - - // aapt resource value: 0x7F03010C - public const int searchIcon = 2130903308; - - // aapt resource value: 0x7F03010D - public const int searchViewStyle = 2130903309; - - // aapt resource value: 0x7F03010E - public const int seekBarStyle = 2130903310; - - // aapt resource value: 0x7F03010F - public const int selectableItemBackground = 2130903311; - - // aapt resource value: 0x7F030110 - public const int selectableItemBackgroundBorderless = 2130903312; - - // aapt resource value: 0x7F030111 - public const int showAsAction = 2130903313; - - // aapt resource value: 0x7F030112 - public const int showDividers = 2130903314; - - // aapt resource value: 0x7F030113 - public const int showText = 2130903315; - - // aapt resource value: 0x7F030114 - public const int showTitle = 2130903316; - - // aapt resource value: 0x7F030115 - public const int singleChoiceItemLayout = 2130903317; - - // aapt resource value: 0x7F030116 - public const int spanCount = 2130903318; - - // aapt resource value: 0x7F030117 - public const int spinBars = 2130903319; - - // aapt resource value: 0x7F030118 - public const int spinnerDropDownItemStyle = 2130903320; - - // aapt resource value: 0x7F030119 - public const int spinnerStyle = 2130903321; - - // aapt resource value: 0x7F03011A - public const int splitTrack = 2130903322; - - // aapt resource value: 0x7F03011B - public const int srcCompat = 2130903323; - - // aapt resource value: 0x7F03011C - public const int stackFromEnd = 2130903324; - - // aapt resource value: 0x7F03011D - public const int state_above_anchor = 2130903325; - - // aapt resource value: 0x7F03011E - public const int state_collapsed = 2130903326; - - // aapt resource value: 0x7F03011F - public const int state_collapsible = 2130903327; - - // aapt resource value: 0x7F030120 - public const int statusBarBackground = 2130903328; - - // aapt resource value: 0x7F030121 - public const int statusBarScrim = 2130903329; - - // aapt resource value: 0x7F030122 - public const int subMenuArrow = 2130903330; - - // aapt resource value: 0x7F030123 - public const int submitBackground = 2130903331; - - // aapt resource value: 0x7F030124 - public const int subtitle = 2130903332; - - // aapt resource value: 0x7F030125 - public const int subtitleTextAppearance = 2130903333; - - // aapt resource value: 0x7F030126 - public const int subtitleTextColor = 2130903334; - - // aapt resource value: 0x7F030127 - public const int subtitleTextStyle = 2130903335; - - // aapt resource value: 0x7F030128 - public const int suggestionRowLayout = 2130903336; - - // aapt resource value: 0x7F030129 - public const int switchMinWidth = 2130903337; - - // aapt resource value: 0x7F03012A - public const int switchPadding = 2130903338; - - // aapt resource value: 0x7F03012B - public const int switchStyle = 2130903339; - - // aapt resource value: 0x7F03012C - public const int switchTextAppearance = 2130903340; - - // aapt resource value: 0x7F03012D - public const int tabBackground = 2130903341; - - // aapt resource value: 0x7F03012E - public const int tabContentStart = 2130903342; - - // aapt resource value: 0x7F03012F - public const int tabGravity = 2130903343; - - // aapt resource value: 0x7F030130 - public const int tabIndicatorColor = 2130903344; - - // aapt resource value: 0x7F030131 - public const int tabIndicatorHeight = 2130903345; - - // aapt resource value: 0x7F030132 - public const int tabMaxWidth = 2130903346; - - // aapt resource value: 0x7F030133 - public const int tabMinWidth = 2130903347; - - // aapt resource value: 0x7F030134 - public const int tabMode = 2130903348; - - // aapt resource value: 0x7F030135 - public const int tabPadding = 2130903349; - - // aapt resource value: 0x7F030136 - public const int tabPaddingBottom = 2130903350; - - // aapt resource value: 0x7F030137 - public const int tabPaddingEnd = 2130903351; - - // aapt resource value: 0x7F030138 - public const int tabPaddingStart = 2130903352; - - // aapt resource value: 0x7F030139 - public const int tabPaddingTop = 2130903353; - - // aapt resource value: 0x7F03013A - public const int tabSelectedTextColor = 2130903354; - - // aapt resource value: 0x7F03013B - public const int tabTextAppearance = 2130903355; - - // aapt resource value: 0x7F03013C - public const int tabTextColor = 2130903356; - - // aapt resource value: 0x7F03013D - public const int textAllCaps = 2130903357; - - // aapt resource value: 0x7F03013E - public const int textAppearanceLargePopupMenu = 2130903358; - - // aapt resource value: 0x7F03013F - public const int textAppearanceListItem = 2130903359; - - // aapt resource value: 0x7F030140 - public const int textAppearanceListItemSecondary = 2130903360; - - // aapt resource value: 0x7F030141 - public const int textAppearanceListItemSmall = 2130903361; - - // aapt resource value: 0x7F030142 - public const int textAppearancePopupMenuHeader = 2130903362; - - // aapt resource value: 0x7F030143 - public const int textAppearanceSearchResultSubtitle = 2130903363; - - // aapt resource value: 0x7F030144 - public const int textAppearanceSearchResultTitle = 2130903364; - - // aapt resource value: 0x7F030145 - public const int textAppearanceSmallPopupMenu = 2130903365; - - // aapt resource value: 0x7F030146 - public const int textColorAlertDialogListItem = 2130903366; - - // aapt resource value: 0x7F030147 - public const int textColorError = 2130903367; - - // aapt resource value: 0x7F030148 - public const int textColorSearchUrl = 2130903368; - - // aapt resource value: 0x7F030149 - public const int theme = 2130903369; - - // aapt resource value: 0x7F03014A - public const int thickness = 2130903370; - - // aapt resource value: 0x7F03014B - public const int thumbTextPadding = 2130903371; - - // aapt resource value: 0x7F03014C - public const int thumbTint = 2130903372; - - // aapt resource value: 0x7F03014D - public const int thumbTintMode = 2130903373; - - // aapt resource value: 0x7F03014E - public const int tickMark = 2130903374; - - // aapt resource value: 0x7F03014F - public const int tickMarkTint = 2130903375; - - // aapt resource value: 0x7F030150 - public const int tickMarkTintMode = 2130903376; - - // aapt resource value: 0x7F030151 - public const int tint = 2130903377; - - // aapt resource value: 0x7F030152 - public const int tintMode = 2130903378; - - // aapt resource value: 0x7F030153 - public const int title = 2130903379; - - // aapt resource value: 0x7F030154 - public const int titleEnabled = 2130903380; - - // aapt resource value: 0x7F030155 - public const int titleMargin = 2130903381; - - // aapt resource value: 0x7F030156 - public const int titleMarginBottom = 2130903382; - - // aapt resource value: 0x7F030157 - public const int titleMarginEnd = 2130903383; - - // aapt resource value: 0x7F03015A - public const int titleMargins = 2130903386; - - // aapt resource value: 0x7F030158 - public const int titleMarginStart = 2130903384; - - // aapt resource value: 0x7F030159 - public const int titleMarginTop = 2130903385; - - // aapt resource value: 0x7F03015B - public const int titleTextAppearance = 2130903387; - - // aapt resource value: 0x7F03015C - public const int titleTextColor = 2130903388; - - // aapt resource value: 0x7F03015D - public const int titleTextStyle = 2130903389; - - // aapt resource value: 0x7F03015E - public const int toolbarId = 2130903390; - - // aapt resource value: 0x7F03015F - public const int toolbarNavigationButtonStyle = 2130903391; - - // aapt resource value: 0x7F030160 - public const int toolbarStyle = 2130903392; - - // aapt resource value: 0x7F030161 - public const int tooltipForegroundColor = 2130903393; - - // aapt resource value: 0x7F030162 - public const int tooltipFrameBackground = 2130903394; - - // aapt resource value: 0x7F030163 - public const int tooltipText = 2130903395; - - // aapt resource value: 0x7F030164 - public const int track = 2130903396; - - // aapt resource value: 0x7F030165 - public const int trackTint = 2130903397; - - // aapt resource value: 0x7F030166 - public const int trackTintMode = 2130903398; - - // aapt resource value: 0x7F030167 - public const int useCompatPadding = 2130903399; - - // aapt resource value: 0x7F030168 - public const int voiceIcon = 2130903400; - - // aapt resource value: 0x7F030169 - public const int windowActionBar = 2130903401; - - // aapt resource value: 0x7F03016A - public const int windowActionBarOverlay = 2130903402; - - // aapt resource value: 0x7F03016B - public const int windowActionModeOverlay = 2130903403; - - // aapt resource value: 0x7F03016C - public const int windowFixedHeightMajor = 2130903404; - - // aapt resource value: 0x7F03016D - public const int windowFixedHeightMinor = 2130903405; - - // aapt resource value: 0x7F03016E - public const int windowFixedWidthMajor = 2130903406; - - // aapt resource value: 0x7F03016F - public const int windowFixedWidthMinor = 2130903407; - - // aapt resource value: 0x7F030170 - public const int windowMinWidthMajor = 2130903408; - - // aapt resource value: 0x7F030171 - public const int windowMinWidthMinor = 2130903409; - - // aapt resource value: 0x7F030172 - public const int windowNoTitle = 2130903410; - - static Attribute() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Attribute() - { - } - } - - public partial class Boolean - { - - // aapt resource value: 0x7F040000 - public const int abc_action_bar_embed_tabs = 2130968576; - - // aapt resource value: 0x7F040001 - public const int abc_allow_stacked_button_bar = 2130968577; - - // aapt resource value: 0x7F040002 - public const int abc_config_actionMenuItemAllCaps = 2130968578; - - // aapt resource value: 0x7F040003 - public const int abc_config_closeDialogWhenTouchOutside = 2130968579; - - // aapt resource value: 0x7F040004 - public const int abc_config_showMenuShortcutsWhenKeyboardPresent = 2130968580; - - static Boolean() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Boolean() - { - } - } - - public partial class Color - { - - // aapt resource value: 0x7F050000 - public const int abc_background_cache_hint_selector_material_dark = 2131034112; - - // aapt resource value: 0x7F050001 - public const int abc_background_cache_hint_selector_material_light = 2131034113; - - // aapt resource value: 0x7F050002 - public const int abc_btn_colored_borderless_text_material = 2131034114; - - // aapt resource value: 0x7F050003 - public const int abc_btn_colored_text_material = 2131034115; - - // aapt resource value: 0x7F050004 - public const int abc_color_highlight_material = 2131034116; - - // aapt resource value: 0x7F050005 - public const int abc_hint_foreground_material_dark = 2131034117; - - // aapt resource value: 0x7F050006 - public const int abc_hint_foreground_material_light = 2131034118; - - // aapt resource value: 0x7F050007 - public const int abc_input_method_navigation_guard = 2131034119; - - // aapt resource value: 0x7F050008 - public const int abc_primary_text_disable_only_material_dark = 2131034120; - - // aapt resource value: 0x7F050009 - public const int abc_primary_text_disable_only_material_light = 2131034121; - - // aapt resource value: 0x7F05000A - public const int abc_primary_text_material_dark = 2131034122; - - // aapt resource value: 0x7F05000B - public const int abc_primary_text_material_light = 2131034123; - - // aapt resource value: 0x7F05000C - public const int abc_search_url_text = 2131034124; - - // aapt resource value: 0x7F05000D - public const int abc_search_url_text_normal = 2131034125; - - // aapt resource value: 0x7F05000E - public const int abc_search_url_text_pressed = 2131034126; - - // aapt resource value: 0x7F05000F - public const int abc_search_url_text_selected = 2131034127; - - // aapt resource value: 0x7F050010 - public const int abc_secondary_text_material_dark = 2131034128; - - // aapt resource value: 0x7F050011 - public const int abc_secondary_text_material_light = 2131034129; - - // aapt resource value: 0x7F050012 - public const int abc_tint_btn_checkable = 2131034130; - - // aapt resource value: 0x7F050013 - public const int abc_tint_default = 2131034131; - - // aapt resource value: 0x7F050014 - public const int abc_tint_edittext = 2131034132; - - // aapt resource value: 0x7F050015 - public const int abc_tint_seek_thumb = 2131034133; - - // aapt resource value: 0x7F050016 - public const int abc_tint_spinner = 2131034134; - - // aapt resource value: 0x7F050017 - public const int abc_tint_switch_track = 2131034135; - - // aapt resource value: 0x7F050018 - public const int accent_material_dark = 2131034136; - - // aapt resource value: 0x7F050019 - public const int accent_material_light = 2131034137; - - // aapt resource value: 0x7F05001A - public const int background_floating_material_dark = 2131034138; - - // aapt resource value: 0x7F05001B - public const int background_floating_material_light = 2131034139; - - // aapt resource value: 0x7F05001C - public const int background_material_dark = 2131034140; - - // aapt resource value: 0x7F05001D - public const int background_material_light = 2131034141; - - // aapt resource value: 0x7F05001E - public const int bright_foreground_disabled_material_dark = 2131034142; - - // aapt resource value: 0x7F05001F - public const int bright_foreground_disabled_material_light = 2131034143; - - // aapt resource value: 0x7F050020 - public const int bright_foreground_inverse_material_dark = 2131034144; - - // aapt resource value: 0x7F050021 - public const int bright_foreground_inverse_material_light = 2131034145; - - // aapt resource value: 0x7F050022 - public const int bright_foreground_material_dark = 2131034146; - - // aapt resource value: 0x7F050023 - public const int bright_foreground_material_light = 2131034147; - - // aapt resource value: 0x7F050024 - public const int button_material_dark = 2131034148; - - // aapt resource value: 0x7F050025 - public const int button_material_light = 2131034149; - - // aapt resource value: 0x7F050026 - public const int cardview_dark_background = 2131034150; - - // aapt resource value: 0x7F050027 - public const int cardview_light_background = 2131034151; - - // aapt resource value: 0x7F050028 - public const int cardview_shadow_end_color = 2131034152; - - // aapt resource value: 0x7F050029 - public const int cardview_shadow_start_color = 2131034153; - - // aapt resource value: 0x7F05002A - public const int colorAccent = 2131034154; - - // aapt resource value: 0x7F05002B - public const int colorPrimary = 2131034155; - - // aapt resource value: 0x7F05002C - public const int colorPrimaryDark = 2131034156; - - // aapt resource value: 0x7F05002D - public const int design_bottom_navigation_shadow_color = 2131034157; - - // aapt resource value: 0x7F05002E - public const int design_error = 2131034158; - - // aapt resource value: 0x7F05002F - public const int design_fab_shadow_end_color = 2131034159; - - // aapt resource value: 0x7F050030 - public const int design_fab_shadow_mid_color = 2131034160; - - // aapt resource value: 0x7F050031 - public const int design_fab_shadow_start_color = 2131034161; - - // aapt resource value: 0x7F050032 - public const int design_fab_stroke_end_inner_color = 2131034162; - - // aapt resource value: 0x7F050033 - public const int design_fab_stroke_end_outer_color = 2131034163; - - // aapt resource value: 0x7F050034 - public const int design_fab_stroke_top_inner_color = 2131034164; - - // aapt resource value: 0x7F050035 - public const int design_fab_stroke_top_outer_color = 2131034165; - - // aapt resource value: 0x7F050036 - public const int design_snackbar_background_color = 2131034166; - - // aapt resource value: 0x7F050037 - public const int design_tint_password_toggle = 2131034167; - - // aapt resource value: 0x7F050038 - public const int dim_foreground_disabled_material_dark = 2131034168; - - // aapt resource value: 0x7F050039 - public const int dim_foreground_disabled_material_light = 2131034169; - - // aapt resource value: 0x7F05003A - public const int dim_foreground_material_dark = 2131034170; - - // aapt resource value: 0x7F05003B - public const int dim_foreground_material_light = 2131034171; - - // aapt resource value: 0x7F05003C - public const int error_color_material = 2131034172; - - // aapt resource value: 0x7F05003D - public const int foreground_material_dark = 2131034173; - - // aapt resource value: 0x7F05003E - public const int foreground_material_light = 2131034174; - - // aapt resource value: 0x7F05003F - public const int highlighted_text_material_dark = 2131034175; - - // aapt resource value: 0x7F050040 - public const int highlighted_text_material_light = 2131034176; - - // aapt resource value: 0x7F050041 - public const int ic_launcher_background = 2131034177; - - // aapt resource value: 0x7F050042 - public const int material_blue_grey_800 = 2131034178; - - // aapt resource value: 0x7F050043 - public const int material_blue_grey_900 = 2131034179; - - // aapt resource value: 0x7F050044 - public const int material_blue_grey_950 = 2131034180; - - // aapt resource value: 0x7F050045 - public const int material_deep_teal_200 = 2131034181; - - // aapt resource value: 0x7F050046 - public const int material_deep_teal_500 = 2131034182; - - // aapt resource value: 0x7F050047 - public const int material_grey_100 = 2131034183; - - // aapt resource value: 0x7F050048 - public const int material_grey_300 = 2131034184; - - // aapt resource value: 0x7F050049 - public const int material_grey_50 = 2131034185; - - // aapt resource value: 0x7F05004A - public const int material_grey_600 = 2131034186; - - // aapt resource value: 0x7F05004B - public const int material_grey_800 = 2131034187; - - // aapt resource value: 0x7F05004C - public const int material_grey_850 = 2131034188; - - // aapt resource value: 0x7F05004D - public const int material_grey_900 = 2131034189; - - // aapt resource value: 0x7F05004E - public const int notification_action_color_filter = 2131034190; - - // aapt resource value: 0x7F05004F - public const int notification_icon_bg_color = 2131034191; - - // aapt resource value: 0x7F050050 - public const int notification_material_background_media_default_color = 2131034192; - - // aapt resource value: 0x7F050051 - public const int primary_dark_material_dark = 2131034193; - - // aapt resource value: 0x7F050052 - public const int primary_dark_material_light = 2131034194; - - // aapt resource value: 0x7F050053 - public const int primary_material_dark = 2131034195; - - // aapt resource value: 0x7F050054 - public const int primary_material_light = 2131034196; - - // aapt resource value: 0x7F050055 - public const int primary_text_default_material_dark = 2131034197; - - // aapt resource value: 0x7F050056 - public const int primary_text_default_material_light = 2131034198; - - // aapt resource value: 0x7F050057 - public const int primary_text_disabled_material_dark = 2131034199; - - // aapt resource value: 0x7F050058 - public const int primary_text_disabled_material_light = 2131034200; - - // aapt resource value: 0x7F050059 - public const int ripple_material_dark = 2131034201; - - // aapt resource value: 0x7F05005A - public const int ripple_material_light = 2131034202; - - // aapt resource value: 0x7F05005B - public const int secondary_text_default_material_dark = 2131034203; - - // aapt resource value: 0x7F05005C - public const int secondary_text_default_material_light = 2131034204; - - // aapt resource value: 0x7F05005D - public const int secondary_text_disabled_material_dark = 2131034205; - - // aapt resource value: 0x7F05005E - public const int secondary_text_disabled_material_light = 2131034206; - - // aapt resource value: 0x7F05005F - public const int switch_thumb_disabled_material_dark = 2131034207; - - // aapt resource value: 0x7F050060 - public const int switch_thumb_disabled_material_light = 2131034208; - - // aapt resource value: 0x7F050061 - public const int switch_thumb_material_dark = 2131034209; - - // aapt resource value: 0x7F050062 - public const int switch_thumb_material_light = 2131034210; - - // aapt resource value: 0x7F050063 - public const int switch_thumb_normal_material_dark = 2131034211; - - // aapt resource value: 0x7F050064 - public const int switch_thumb_normal_material_light = 2131034212; - - // aapt resource value: 0x7F050065 - public const int tooltip_background_dark = 2131034213; - - // aapt resource value: 0x7F050066 - public const int tooltip_background_light = 2131034214; - - static Color() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Color() - { - } - } - - public partial class Dimension - { - - // aapt resource value: 0x7F060000 - public const int abc_action_bar_content_inset_material = 2131099648; - - // aapt resource value: 0x7F060001 - public const int abc_action_bar_content_inset_with_nav = 2131099649; - - // aapt resource value: 0x7F060002 - public const int abc_action_bar_default_height_material = 2131099650; - - // aapt resource value: 0x7F060003 - public const int abc_action_bar_default_padding_end_material = 2131099651; - - // aapt resource value: 0x7F060004 - public const int abc_action_bar_default_padding_start_material = 2131099652; - - // aapt resource value: 0x7F060005 - public const int abc_action_bar_elevation_material = 2131099653; - - // aapt resource value: 0x7F060006 - public const int abc_action_bar_icon_vertical_padding_material = 2131099654; - - // aapt resource value: 0x7F060007 - public const int abc_action_bar_overflow_padding_end_material = 2131099655; - - // aapt resource value: 0x7F060008 - public const int abc_action_bar_overflow_padding_start_material = 2131099656; - - // aapt resource value: 0x7F060009 - public const int abc_action_bar_progress_bar_size = 2131099657; - - // aapt resource value: 0x7F06000A - public const int abc_action_bar_stacked_max_height = 2131099658; - - // aapt resource value: 0x7F06000B - public const int abc_action_bar_stacked_tab_max_width = 2131099659; - - // aapt resource value: 0x7F06000C - public const int abc_action_bar_subtitle_bottom_margin_material = 2131099660; - - // aapt resource value: 0x7F06000D - public const int abc_action_bar_subtitle_top_margin_material = 2131099661; - - // aapt resource value: 0x7F06000E - public const int abc_action_button_min_height_material = 2131099662; - - // aapt resource value: 0x7F06000F - public const int abc_action_button_min_width_material = 2131099663; - - // aapt resource value: 0x7F060010 - public const int abc_action_button_min_width_overflow_material = 2131099664; - - // aapt resource value: 0x7F060011 - public const int abc_alert_dialog_button_bar_height = 2131099665; - - // aapt resource value: 0x7F060012 - public const int abc_button_inset_horizontal_material = 2131099666; - - // aapt resource value: 0x7F060013 - public const int abc_button_inset_vertical_material = 2131099667; - - // aapt resource value: 0x7F060014 - public const int abc_button_padding_horizontal_material = 2131099668; - - // aapt resource value: 0x7F060015 - public const int abc_button_padding_vertical_material = 2131099669; - - // aapt resource value: 0x7F060016 - public const int abc_cascading_menus_min_smallest_width = 2131099670; - - // aapt resource value: 0x7F060017 - public const int abc_config_prefDialogWidth = 2131099671; - - // aapt resource value: 0x7F060018 - public const int abc_control_corner_material = 2131099672; - - // aapt resource value: 0x7F060019 - public const int abc_control_inset_material = 2131099673; - - // aapt resource value: 0x7F06001A - public const int abc_control_padding_material = 2131099674; - - // aapt resource value: 0x7F06001B - public const int abc_dialog_fixed_height_major = 2131099675; - - // aapt resource value: 0x7F06001C - public const int abc_dialog_fixed_height_minor = 2131099676; - - // aapt resource value: 0x7F06001D - public const int abc_dialog_fixed_width_major = 2131099677; - - // aapt resource value: 0x7F06001E - public const int abc_dialog_fixed_width_minor = 2131099678; - - // aapt resource value: 0x7F06001F - public const int abc_dialog_list_padding_bottom_no_buttons = 2131099679; - - // aapt resource value: 0x7F060020 - public const int abc_dialog_list_padding_top_no_title = 2131099680; - - // aapt resource value: 0x7F060021 - public const int abc_dialog_min_width_major = 2131099681; - - // aapt resource value: 0x7F060022 - public const int abc_dialog_min_width_minor = 2131099682; - - // aapt resource value: 0x7F060023 - public const int abc_dialog_padding_material = 2131099683; - - // aapt resource value: 0x7F060024 - public const int abc_dialog_padding_top_material = 2131099684; - - // aapt resource value: 0x7F060025 - public const int abc_dialog_title_divider_material = 2131099685; - - // aapt resource value: 0x7F060026 - public const int abc_disabled_alpha_material_dark = 2131099686; - - // aapt resource value: 0x7F060027 - public const int abc_disabled_alpha_material_light = 2131099687; - - // aapt resource value: 0x7F060028 - public const int abc_dropdownitem_icon_width = 2131099688; - - // aapt resource value: 0x7F060029 - public const int abc_dropdownitem_text_padding_left = 2131099689; - - // aapt resource value: 0x7F06002A - public const int abc_dropdownitem_text_padding_right = 2131099690; - - // aapt resource value: 0x7F06002B - public const int abc_edit_text_inset_bottom_material = 2131099691; - - // aapt resource value: 0x7F06002C - public const int abc_edit_text_inset_horizontal_material = 2131099692; - - // aapt resource value: 0x7F06002D - public const int abc_edit_text_inset_top_material = 2131099693; - - // aapt resource value: 0x7F06002E - public const int abc_floating_window_z = 2131099694; - - // aapt resource value: 0x7F06002F - public const int abc_list_item_padding_horizontal_material = 2131099695; - - // aapt resource value: 0x7F060030 - public const int abc_panel_menu_list_width = 2131099696; - - // aapt resource value: 0x7F060031 - public const int abc_progress_bar_height_material = 2131099697; - - // aapt resource value: 0x7F060032 - public const int abc_search_view_preferred_height = 2131099698; - - // aapt resource value: 0x7F060033 - public const int abc_search_view_preferred_width = 2131099699; - - // aapt resource value: 0x7F060034 - public const int abc_seekbar_track_background_height_material = 2131099700; - - // aapt resource value: 0x7F060035 - public const int abc_seekbar_track_progress_height_material = 2131099701; - - // aapt resource value: 0x7F060036 - public const int abc_select_dialog_padding_start_material = 2131099702; - - // aapt resource value: 0x7F060037 - public const int abc_switch_padding = 2131099703; - - // aapt resource value: 0x7F060038 - public const int abc_text_size_body_1_material = 2131099704; - - // aapt resource value: 0x7F060039 - public const int abc_text_size_body_2_material = 2131099705; - - // aapt resource value: 0x7F06003A - public const int abc_text_size_button_material = 2131099706; - - // aapt resource value: 0x7F06003B - public const int abc_text_size_caption_material = 2131099707; - - // aapt resource value: 0x7F06003C - public const int abc_text_size_display_1_material = 2131099708; - - // aapt resource value: 0x7F06003D - public const int abc_text_size_display_2_material = 2131099709; - - // aapt resource value: 0x7F06003E - public const int abc_text_size_display_3_material = 2131099710; - - // aapt resource value: 0x7F06003F - public const int abc_text_size_display_4_material = 2131099711; - - // aapt resource value: 0x7F060040 - public const int abc_text_size_headline_material = 2131099712; - - // aapt resource value: 0x7F060041 - public const int abc_text_size_large_material = 2131099713; - - // aapt resource value: 0x7F060042 - public const int abc_text_size_medium_material = 2131099714; - - // aapt resource value: 0x7F060043 - public const int abc_text_size_menu_header_material = 2131099715; - - // aapt resource value: 0x7F060044 - public const int abc_text_size_menu_material = 2131099716; - - // aapt resource value: 0x7F060045 - public const int abc_text_size_small_material = 2131099717; - - // aapt resource value: 0x7F060046 - public const int abc_text_size_subhead_material = 2131099718; - - // aapt resource value: 0x7F060047 - public const int abc_text_size_subtitle_material_toolbar = 2131099719; - - // aapt resource value: 0x7F060048 - public const int abc_text_size_title_material = 2131099720; - - // aapt resource value: 0x7F060049 - public const int abc_text_size_title_material_toolbar = 2131099721; - - // aapt resource value: 0x7F06004A - public const int cardview_compat_inset_shadow = 2131099722; - - // aapt resource value: 0x7F06004B - public const int cardview_default_elevation = 2131099723; - - // aapt resource value: 0x7F06004C - public const int cardview_default_radius = 2131099724; - - // aapt resource value: 0x7F06004D - public const int compat_button_inset_horizontal_material = 2131099725; - - // aapt resource value: 0x7F06004E - public const int compat_button_inset_vertical_material = 2131099726; - - // aapt resource value: 0x7F06004F - public const int compat_button_padding_horizontal_material = 2131099727; - - // aapt resource value: 0x7F060050 - public const int compat_button_padding_vertical_material = 2131099728; - - // aapt resource value: 0x7F060051 - public const int compat_control_corner_material = 2131099729; - - // aapt resource value: 0x7F060052 - public const int design_appbar_elevation = 2131099730; - - // aapt resource value: 0x7F060053 - public const int design_bottom_navigation_active_item_max_width = 2131099731; - - // aapt resource value: 0x7F060054 - public const int design_bottom_navigation_active_text_size = 2131099732; - - // aapt resource value: 0x7F060055 - public const int design_bottom_navigation_elevation = 2131099733; - - // aapt resource value: 0x7F060056 - public const int design_bottom_navigation_height = 2131099734; - - // aapt resource value: 0x7F060057 - public const int design_bottom_navigation_item_max_width = 2131099735; - - // aapt resource value: 0x7F060058 - public const int design_bottom_navigation_item_min_width = 2131099736; - - // aapt resource value: 0x7F060059 - public const int design_bottom_navigation_margin = 2131099737; - - // aapt resource value: 0x7F06005A - public const int design_bottom_navigation_shadow_height = 2131099738; - - // aapt resource value: 0x7F06005B - public const int design_bottom_navigation_text_size = 2131099739; - - // aapt resource value: 0x7F06005C - public const int design_bottom_sheet_modal_elevation = 2131099740; - - // aapt resource value: 0x7F06005D - public const int design_bottom_sheet_peek_height_min = 2131099741; - - // aapt resource value: 0x7F06005E - public const int design_fab_border_width = 2131099742; - - // aapt resource value: 0x7F06005F - public const int design_fab_elevation = 2131099743; - - // aapt resource value: 0x7F060060 - public const int design_fab_image_size = 2131099744; - - // aapt resource value: 0x7F060061 - public const int design_fab_size_mini = 2131099745; - - // aapt resource value: 0x7F060062 - public const int design_fab_size_normal = 2131099746; - - // aapt resource value: 0x7F060063 - public const int design_fab_translation_z_pressed = 2131099747; - - // aapt resource value: 0x7F060064 - public const int design_navigation_elevation = 2131099748; - - // aapt resource value: 0x7F060065 - public const int design_navigation_icon_padding = 2131099749; - - // aapt resource value: 0x7F060066 - public const int design_navigation_icon_size = 2131099750; - - // aapt resource value: 0x7F060067 - public const int design_navigation_max_width = 2131099751; - - // aapt resource value: 0x7F060068 - public const int design_navigation_padding_bottom = 2131099752; - - // aapt resource value: 0x7F060069 - public const int design_navigation_separator_vertical_padding = 2131099753; - - // aapt resource value: 0x7F06006A - public const int design_snackbar_action_inline_max_width = 2131099754; - - // aapt resource value: 0x7F06006B - public const int design_snackbar_background_corner_radius = 2131099755; - - // aapt resource value: 0x7F06006C - public const int design_snackbar_elevation = 2131099756; - - // aapt resource value: 0x7F06006D - public const int design_snackbar_extra_spacing_horizontal = 2131099757; - - // aapt resource value: 0x7F06006E - public const int design_snackbar_max_width = 2131099758; - - // aapt resource value: 0x7F06006F - public const int design_snackbar_min_width = 2131099759; - - // aapt resource value: 0x7F060070 - public const int design_snackbar_padding_horizontal = 2131099760; - - // aapt resource value: 0x7F060071 - public const int design_snackbar_padding_vertical = 2131099761; - - // aapt resource value: 0x7F060072 - public const int design_snackbar_padding_vertical_2lines = 2131099762; - - // aapt resource value: 0x7F060073 - public const int design_snackbar_text_size = 2131099763; - - // aapt resource value: 0x7F060074 - public const int design_tab_max_width = 2131099764; - - // aapt resource value: 0x7F060075 - public const int design_tab_scrollable_min_width = 2131099765; - - // aapt resource value: 0x7F060076 - public const int design_tab_text_size = 2131099766; - - // aapt resource value: 0x7F060077 - public const int design_tab_text_size_2line = 2131099767; - - // aapt resource value: 0x7F060078 - public const int disabled_alpha_material_dark = 2131099768; - - // aapt resource value: 0x7F060079 - public const int disabled_alpha_material_light = 2131099769; - - // aapt resource value: 0x7F06007A - public const int fastscroll_default_thickness = 2131099770; - - // aapt resource value: 0x7F06007B - public const int fastscroll_margin = 2131099771; - - // aapt resource value: 0x7F06007C - public const int fastscroll_minimum_range = 2131099772; - - // aapt resource value: 0x7F06007D - public const int highlight_alpha_material_colored = 2131099773; - - // aapt resource value: 0x7F06007E - public const int highlight_alpha_material_dark = 2131099774; - - // aapt resource value: 0x7F06007F - public const int highlight_alpha_material_light = 2131099775; - - // aapt resource value: 0x7F060080 - public const int hint_alpha_material_dark = 2131099776; - - // aapt resource value: 0x7F060081 - public const int hint_alpha_material_light = 2131099777; - - // aapt resource value: 0x7F060082 - public const int hint_pressed_alpha_material_dark = 2131099778; - - // aapt resource value: 0x7F060083 - public const int hint_pressed_alpha_material_light = 2131099779; - - // aapt resource value: 0x7F060084 - public const int item_touch_helper_max_drag_scroll_per_frame = 2131099780; - - // aapt resource value: 0x7F060085 - public const int item_touch_helper_swipe_escape_max_velocity = 2131099781; - - // aapt resource value: 0x7F060086 - public const int item_touch_helper_swipe_escape_velocity = 2131099782; - - // aapt resource value: 0x7F060087 - public const int mr_controller_volume_group_list_item_height = 2131099783; - - // aapt resource value: 0x7F060088 - public const int mr_controller_volume_group_list_item_icon_size = 2131099784; - - // aapt resource value: 0x7F060089 - public const int mr_controller_volume_group_list_max_height = 2131099785; - - // aapt resource value: 0x7F06008A - public const int mr_controller_volume_group_list_padding_top = 2131099786; - - // aapt resource value: 0x7F06008B - public const int mr_dialog_fixed_width_major = 2131099787; - - // aapt resource value: 0x7F06008C - public const int mr_dialog_fixed_width_minor = 2131099788; - - // aapt resource value: 0x7F06008D - public const int notification_action_icon_size = 2131099789; - - // aapt resource value: 0x7F06008E - public const int notification_action_text_size = 2131099790; - - // aapt resource value: 0x7F06008F - public const int notification_big_circle_margin = 2131099791; - - // aapt resource value: 0x7F060090 - public const int notification_content_margin_start = 2131099792; - - // aapt resource value: 0x7F060091 - public const int notification_large_icon_height = 2131099793; - - // aapt resource value: 0x7F060092 - public const int notification_large_icon_width = 2131099794; - - // aapt resource value: 0x7F060093 - public const int notification_main_column_padding_top = 2131099795; - - // aapt resource value: 0x7F060094 - public const int notification_media_narrow_margin = 2131099796; - - // aapt resource value: 0x7F060095 - public const int notification_right_icon_size = 2131099797; - - // aapt resource value: 0x7F060096 - public const int notification_right_side_padding_top = 2131099798; - - // aapt resource value: 0x7F060097 - public const int notification_small_icon_background_padding = 2131099799; - - // aapt resource value: 0x7F060098 - public const int notification_small_icon_size_as_large = 2131099800; - - // aapt resource value: 0x7F060099 - public const int notification_subtext_size = 2131099801; - - // aapt resource value: 0x7F06009A - public const int notification_top_pad = 2131099802; - - // aapt resource value: 0x7F06009B - public const int notification_top_pad_large_text = 2131099803; - - // aapt resource value: 0x7F06009C - public const int tooltip_corner_radius = 2131099804; - - // aapt resource value: 0x7F06009D - public const int tooltip_horizontal_padding = 2131099805; - - // aapt resource value: 0x7F06009E - public const int tooltip_margin = 2131099806; - - // aapt resource value: 0x7F06009F - public const int tooltip_precise_anchor_extra_offset = 2131099807; - - // aapt resource value: 0x7F0600A0 - public const int tooltip_precise_anchor_threshold = 2131099808; - - // aapt resource value: 0x7F0600A1 - public const int tooltip_vertical_padding = 2131099809; - - // aapt resource value: 0x7F0600A2 - public const int tooltip_y_offset_non_touch = 2131099810; - - // aapt resource value: 0x7F0600A3 - public const int tooltip_y_offset_touch = 2131099811; - - static Dimension() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Dimension() - { - } - } - - public partial class Drawable - { - - // aapt resource value: 0x7F070006 - public const int abc_ab_share_pack_mtrl_alpha = 2131165190; - - // aapt resource value: 0x7F070007 - public const int abc_action_bar_item_background_material = 2131165191; - - // aapt resource value: 0x7F070008 - public const int abc_btn_borderless_material = 2131165192; - - // aapt resource value: 0x7F070009 - public const int abc_btn_check_material = 2131165193; - - // aapt resource value: 0x7F07000A - public const int abc_btn_check_to_on_mtrl_000 = 2131165194; - - // aapt resource value: 0x7F07000B - public const int abc_btn_check_to_on_mtrl_015 = 2131165195; - - // aapt resource value: 0x7F07000C - public const int abc_btn_colored_material = 2131165196; - - // aapt resource value: 0x7F07000D - public const int abc_btn_default_mtrl_shape = 2131165197; - - // aapt resource value: 0x7F07000E - public const int abc_btn_radio_material = 2131165198; - - // aapt resource value: 0x7F07000F - public const int abc_btn_radio_to_on_mtrl_000 = 2131165199; - - // aapt resource value: 0x7F070010 - public const int abc_btn_radio_to_on_mtrl_015 = 2131165200; - - // aapt resource value: 0x7F070011 - public const int abc_btn_switch_to_on_mtrl_00001 = 2131165201; - - // aapt resource value: 0x7F070012 - public const int abc_btn_switch_to_on_mtrl_00012 = 2131165202; - - // aapt resource value: 0x7F070013 - public const int abc_cab_background_internal_bg = 2131165203; - - // aapt resource value: 0x7F070014 - public const int abc_cab_background_top_material = 2131165204; - - // aapt resource value: 0x7F070015 - public const int abc_cab_background_top_mtrl_alpha = 2131165205; - - // aapt resource value: 0x7F070016 - public const int abc_control_background_material = 2131165206; - - // aapt resource value: 0x7F070017 - public const int abc_dialog_material_background = 2131165207; - - // aapt resource value: 0x7F070018 - public const int abc_edit_text_material = 2131165208; - - // aapt resource value: 0x7F070019 - public const int abc_ic_ab_back_material = 2131165209; - - // aapt resource value: 0x7F07001A - public const int abc_ic_arrow_drop_right_black_24dp = 2131165210; - - // aapt resource value: 0x7F07001B - public const int abc_ic_clear_material = 2131165211; - - // aapt resource value: 0x7F07001C - public const int abc_ic_commit_search_api_mtrl_alpha = 2131165212; - - // aapt resource value: 0x7F07001D - public const int abc_ic_go_search_api_material = 2131165213; - - // aapt resource value: 0x7F07001E - public const int abc_ic_menu_copy_mtrl_am_alpha = 2131165214; - - // aapt resource value: 0x7F07001F - public const int abc_ic_menu_cut_mtrl_alpha = 2131165215; - - // aapt resource value: 0x7F070020 - public const int abc_ic_menu_overflow_material = 2131165216; - - // aapt resource value: 0x7F070021 - public const int abc_ic_menu_paste_mtrl_am_alpha = 2131165217; - - // aapt resource value: 0x7F070022 - public const int abc_ic_menu_selectall_mtrl_alpha = 2131165218; - - // aapt resource value: 0x7F070023 - public const int abc_ic_menu_share_mtrl_alpha = 2131165219; - - // aapt resource value: 0x7F070024 - public const int abc_ic_search_api_material = 2131165220; - - // aapt resource value: 0x7F070025 - public const int abc_ic_star_black_16dp = 2131165221; - - // aapt resource value: 0x7F070026 - public const int abc_ic_star_black_36dp = 2131165222; - - // aapt resource value: 0x7F070027 - public const int abc_ic_star_black_48dp = 2131165223; - - // aapt resource value: 0x7F070028 - public const int abc_ic_star_half_black_16dp = 2131165224; - - // aapt resource value: 0x7F070029 - public const int abc_ic_star_half_black_36dp = 2131165225; - - // aapt resource value: 0x7F07002A - public const int abc_ic_star_half_black_48dp = 2131165226; - - // aapt resource value: 0x7F07002B - public const int abc_ic_voice_search_api_material = 2131165227; - - // aapt resource value: 0x7F07002C - public const int abc_item_background_holo_dark = 2131165228; - - // aapt resource value: 0x7F07002D - public const int abc_item_background_holo_light = 2131165229; - - // aapt resource value: 0x7F07002E - public const int abc_list_divider_mtrl_alpha = 2131165230; - - // aapt resource value: 0x7F07002F - public const int abc_list_focused_holo = 2131165231; - - // aapt resource value: 0x7F070030 - public const int abc_list_longpressed_holo = 2131165232; - - // aapt resource value: 0x7F070031 - public const int abc_list_pressed_holo_dark = 2131165233; - - // aapt resource value: 0x7F070032 - public const int abc_list_pressed_holo_light = 2131165234; - - // aapt resource value: 0x7F070033 - public const int abc_list_selector_background_transition_holo_dark = 2131165235; - - // aapt resource value: 0x7F070034 - public const int abc_list_selector_background_transition_holo_light = 2131165236; - - // aapt resource value: 0x7F070035 - public const int abc_list_selector_disabled_holo_dark = 2131165237; - - // aapt resource value: 0x7F070036 - public const int abc_list_selector_disabled_holo_light = 2131165238; - - // aapt resource value: 0x7F070037 - public const int abc_list_selector_holo_dark = 2131165239; - - // aapt resource value: 0x7F070038 - public const int abc_list_selector_holo_light = 2131165240; - - // aapt resource value: 0x7F070039 - public const int abc_menu_hardkey_panel_mtrl_mult = 2131165241; - - // aapt resource value: 0x7F07003A - public const int abc_popup_background_mtrl_mult = 2131165242; - - // aapt resource value: 0x7F07003B - public const int abc_ratingbar_indicator_material = 2131165243; - - // aapt resource value: 0x7F07003C - public const int abc_ratingbar_material = 2131165244; - - // aapt resource value: 0x7F07003D - public const int abc_ratingbar_small_material = 2131165245; - - // aapt resource value: 0x7F07003E - public const int abc_scrubber_control_off_mtrl_alpha = 2131165246; - - // aapt resource value: 0x7F07003F - public const int abc_scrubber_control_to_pressed_mtrl_000 = 2131165247; - - // aapt resource value: 0x7F070040 - public const int abc_scrubber_control_to_pressed_mtrl_005 = 2131165248; - - // aapt resource value: 0x7F070041 - public const int abc_scrubber_primary_mtrl_alpha = 2131165249; - - // aapt resource value: 0x7F070042 - public const int abc_scrubber_track_mtrl_alpha = 2131165250; - - // aapt resource value: 0x7F070043 - public const int abc_seekbar_thumb_material = 2131165251; - - // aapt resource value: 0x7F070044 - public const int abc_seekbar_tick_mark_material = 2131165252; - - // aapt resource value: 0x7F070045 - public const int abc_seekbar_track_material = 2131165253; - - // aapt resource value: 0x7F070046 - public const int abc_spinner_mtrl_am_alpha = 2131165254; - - // aapt resource value: 0x7F070047 - public const int abc_spinner_textfield_background_material = 2131165255; - - // aapt resource value: 0x7F070048 - public const int abc_switch_thumb_material = 2131165256; - - // aapt resource value: 0x7F070049 - public const int abc_switch_track_mtrl_alpha = 2131165257; - - // aapt resource value: 0x7F07004A - public const int abc_tab_indicator_material = 2131165258; - - // aapt resource value: 0x7F07004B - public const int abc_tab_indicator_mtrl_alpha = 2131165259; - - // aapt resource value: 0x7F070053 - public const int abc_textfield_activated_mtrl_alpha = 2131165267; - - // aapt resource value: 0x7F070054 - public const int abc_textfield_default_mtrl_alpha = 2131165268; - - // aapt resource value: 0x7F070055 - public const int abc_textfield_search_activated_mtrl_alpha = 2131165269; - - // aapt resource value: 0x7F070056 - public const int abc_textfield_search_default_mtrl_alpha = 2131165270; - - // aapt resource value: 0x7F070057 - public const int abc_textfield_search_material = 2131165271; - - // aapt resource value: 0x7F07004C - public const int abc_text_cursor_material = 2131165260; - - // aapt resource value: 0x7F07004D - public const int abc_text_select_handle_left_mtrl_dark = 2131165261; - - // aapt resource value: 0x7F07004E - public const int abc_text_select_handle_left_mtrl_light = 2131165262; - - // aapt resource value: 0x7F07004F - public const int abc_text_select_handle_middle_mtrl_dark = 2131165263; - - // aapt resource value: 0x7F070050 - public const int abc_text_select_handle_middle_mtrl_light = 2131165264; - - // aapt resource value: 0x7F070051 - public const int abc_text_select_handle_right_mtrl_dark = 2131165265; - - // aapt resource value: 0x7F070052 - public const int abc_text_select_handle_right_mtrl_light = 2131165266; - - // aapt resource value: 0x7F070058 - public const int abc_vector_test = 2131165272; - - // aapt resource value: 0x7F070059 - public const int avd_hide_password = 2131165273; - - // aapt resource value: 0x7F07005A - public const int avd_show_password = 2131165274; - - // aapt resource value: 0x7F07005B - public const int design_bottom_navigation_item_background = 2131165275; - - // aapt resource value: 0x7F07005C - public const int design_fab_background = 2131165276; - - // aapt resource value: 0x7F07005D - public const int design_ic_visibility = 2131165277; - - // aapt resource value: 0x7F07005E - public const int design_ic_visibility_off = 2131165278; - - // aapt resource value: 0x7F07005F - public const int design_password_eye = 2131165279; - - // aapt resource value: 0x7F070060 - public const int design_snackbar_background = 2131165280; - - // aapt resource value: 0x7F070061 - public const int ic_audiotrack_dark = 2131165281; - - // aapt resource value: 0x7F070062 - public const int ic_audiotrack_light = 2131165282; - - // aapt resource value: 0x7F070063 - public const int ic_dialog_close_dark = 2131165283; - - // aapt resource value: 0x7F070064 - public const int ic_dialog_close_light = 2131165284; - - // aapt resource value: 0x7F070065 - public const int ic_group_collapse_00 = 2131165285; - - // aapt resource value: 0x7F070066 - public const int ic_group_collapse_01 = 2131165286; - - // aapt resource value: 0x7F070067 - public const int ic_group_collapse_02 = 2131165287; - - // aapt resource value: 0x7F070068 - public const int ic_group_collapse_03 = 2131165288; - - // aapt resource value: 0x7F070069 - public const int ic_group_collapse_04 = 2131165289; - - // aapt resource value: 0x7F07006A - public const int ic_group_collapse_05 = 2131165290; - - // aapt resource value: 0x7F07006B - public const int ic_group_collapse_06 = 2131165291; - - // aapt resource value: 0x7F07006C - public const int ic_group_collapse_07 = 2131165292; - - // aapt resource value: 0x7F07006D - public const int ic_group_collapse_08 = 2131165293; - - // aapt resource value: 0x7F07006E - public const int ic_group_collapse_09 = 2131165294; - - // aapt resource value: 0x7F07006F - public const int ic_group_collapse_10 = 2131165295; - - // aapt resource value: 0x7F070070 - public const int ic_group_collapse_11 = 2131165296; - - // aapt resource value: 0x7F070071 - public const int ic_group_collapse_12 = 2131165297; - - // aapt resource value: 0x7F070072 - public const int ic_group_collapse_13 = 2131165298; - - // aapt resource value: 0x7F070073 - public const int ic_group_collapse_14 = 2131165299; - - // aapt resource value: 0x7F070074 - public const int ic_group_collapse_15 = 2131165300; - - // aapt resource value: 0x7F070075 - public const int ic_group_expand_00 = 2131165301; - - // aapt resource value: 0x7F070076 - public const int ic_group_expand_01 = 2131165302; - - // aapt resource value: 0x7F070077 - public const int ic_group_expand_02 = 2131165303; - - // aapt resource value: 0x7F070078 - public const int ic_group_expand_03 = 2131165304; - - // aapt resource value: 0x7F070079 - public const int ic_group_expand_04 = 2131165305; - - // aapt resource value: 0x7F07007A - public const int ic_group_expand_05 = 2131165306; - - // aapt resource value: 0x7F07007B - public const int ic_group_expand_06 = 2131165307; - - // aapt resource value: 0x7F07007C - public const int ic_group_expand_07 = 2131165308; - - // aapt resource value: 0x7F07007D - public const int ic_group_expand_08 = 2131165309; - - // aapt resource value: 0x7F07007E - public const int ic_group_expand_09 = 2131165310; - - // aapt resource value: 0x7F07007F - public const int ic_group_expand_10 = 2131165311; - - // aapt resource value: 0x7F070080 - public const int ic_group_expand_11 = 2131165312; - - // aapt resource value: 0x7F070081 - public const int ic_group_expand_12 = 2131165313; - - // aapt resource value: 0x7F070082 - public const int ic_group_expand_13 = 2131165314; - - // aapt resource value: 0x7F070083 - public const int ic_group_expand_14 = 2131165315; - - // aapt resource value: 0x7F070084 - public const int ic_group_expand_15 = 2131165316; - - // aapt resource value: 0x7F070085 - public const int ic_media_pause_dark = 2131165317; - - // aapt resource value: 0x7F070086 - public const int ic_media_pause_light = 2131165318; - - // aapt resource value: 0x7F070087 - public const int ic_media_play_dark = 2131165319; - - // aapt resource value: 0x7F070088 - public const int ic_media_play_light = 2131165320; - - // aapt resource value: 0x7F070089 - public const int ic_media_stop_dark = 2131165321; - - // aapt resource value: 0x7F07008A - public const int ic_media_stop_light = 2131165322; - - // aapt resource value: 0x7F07008B - public const int ic_mr_button_connected_00_dark = 2131165323; - - // aapt resource value: 0x7F07008C - public const int ic_mr_button_connected_00_light = 2131165324; - - // aapt resource value: 0x7F07008D - public const int ic_mr_button_connected_01_dark = 2131165325; - - // aapt resource value: 0x7F07008E - public const int ic_mr_button_connected_01_light = 2131165326; - - // aapt resource value: 0x7F07008F - public const int ic_mr_button_connected_02_dark = 2131165327; - - // aapt resource value: 0x7F070090 - public const int ic_mr_button_connected_02_light = 2131165328; - - // aapt resource value: 0x7F070091 - public const int ic_mr_button_connected_03_dark = 2131165329; - - // aapt resource value: 0x7F070092 - public const int ic_mr_button_connected_03_light = 2131165330; - - // aapt resource value: 0x7F070093 - public const int ic_mr_button_connected_04_dark = 2131165331; - - // aapt resource value: 0x7F070094 - public const int ic_mr_button_connected_04_light = 2131165332; - - // aapt resource value: 0x7F070095 - public const int ic_mr_button_connected_05_dark = 2131165333; - - // aapt resource value: 0x7F070096 - public const int ic_mr_button_connected_05_light = 2131165334; - - // aapt resource value: 0x7F070097 - public const int ic_mr_button_connected_06_dark = 2131165335; - - // aapt resource value: 0x7F070098 - public const int ic_mr_button_connected_06_light = 2131165336; - - // aapt resource value: 0x7F070099 - public const int ic_mr_button_connected_07_dark = 2131165337; - - // aapt resource value: 0x7F07009A - public const int ic_mr_button_connected_07_light = 2131165338; - - // aapt resource value: 0x7F07009B - public const int ic_mr_button_connected_08_dark = 2131165339; - - // aapt resource value: 0x7F07009C - public const int ic_mr_button_connected_08_light = 2131165340; - - // aapt resource value: 0x7F07009D - public const int ic_mr_button_connected_09_dark = 2131165341; - - // aapt resource value: 0x7F07009E - public const int ic_mr_button_connected_09_light = 2131165342; - - // aapt resource value: 0x7F07009F - public const int ic_mr_button_connected_10_dark = 2131165343; - - // aapt resource value: 0x7F0700A0 - public const int ic_mr_button_connected_10_light = 2131165344; - - // aapt resource value: 0x7F0700A1 - public const int ic_mr_button_connected_11_dark = 2131165345; - - // aapt resource value: 0x7F0700A2 - public const int ic_mr_button_connected_11_light = 2131165346; - - // aapt resource value: 0x7F0700A3 - public const int ic_mr_button_connected_12_dark = 2131165347; - - // aapt resource value: 0x7F0700A4 - public const int ic_mr_button_connected_12_light = 2131165348; - - // aapt resource value: 0x7F0700A5 - public const int ic_mr_button_connected_13_dark = 2131165349; - - // aapt resource value: 0x7F0700A6 - public const int ic_mr_button_connected_13_light = 2131165350; - - // aapt resource value: 0x7F0700A7 - public const int ic_mr_button_connected_14_dark = 2131165351; - - // aapt resource value: 0x7F0700A8 - public const int ic_mr_button_connected_14_light = 2131165352; - - // aapt resource value: 0x7F0700A9 - public const int ic_mr_button_connected_15_dark = 2131165353; - - // aapt resource value: 0x7F0700AA - public const int ic_mr_button_connected_15_light = 2131165354; - - // aapt resource value: 0x7F0700AB - public const int ic_mr_button_connected_16_dark = 2131165355; - - // aapt resource value: 0x7F0700AC - public const int ic_mr_button_connected_16_light = 2131165356; - - // aapt resource value: 0x7F0700AD - public const int ic_mr_button_connected_17_dark = 2131165357; - - // aapt resource value: 0x7F0700AE - public const int ic_mr_button_connected_17_light = 2131165358; - - // aapt resource value: 0x7F0700AF - public const int ic_mr_button_connected_18_dark = 2131165359; - - // aapt resource value: 0x7F0700B0 - public const int ic_mr_button_connected_18_light = 2131165360; - - // aapt resource value: 0x7F0700B1 - public const int ic_mr_button_connected_19_dark = 2131165361; - - // aapt resource value: 0x7F0700B2 - public const int ic_mr_button_connected_19_light = 2131165362; - - // aapt resource value: 0x7F0700B3 - public const int ic_mr_button_connected_20_dark = 2131165363; - - // aapt resource value: 0x7F0700B4 - public const int ic_mr_button_connected_20_light = 2131165364; - - // aapt resource value: 0x7F0700B5 - public const int ic_mr_button_connected_21_dark = 2131165365; - - // aapt resource value: 0x7F0700B6 - public const int ic_mr_button_connected_21_light = 2131165366; - - // aapt resource value: 0x7F0700B7 - public const int ic_mr_button_connected_22_dark = 2131165367; - - // aapt resource value: 0x7F0700B8 - public const int ic_mr_button_connected_22_light = 2131165368; - - // aapt resource value: 0x7F0700B9 - public const int ic_mr_button_connected_23_dark = 2131165369; - - // aapt resource value: 0x7F0700BA - public const int ic_mr_button_connected_23_light = 2131165370; - - // aapt resource value: 0x7F0700BB - public const int ic_mr_button_connected_24_dark = 2131165371; - - // aapt resource value: 0x7F0700BC - public const int ic_mr_button_connected_24_light = 2131165372; - - // aapt resource value: 0x7F0700BD - public const int ic_mr_button_connected_25_dark = 2131165373; - - // aapt resource value: 0x7F0700BE - public const int ic_mr_button_connected_25_light = 2131165374; - - // aapt resource value: 0x7F0700BF - public const int ic_mr_button_connected_26_dark = 2131165375; - - // aapt resource value: 0x7F0700C0 - public const int ic_mr_button_connected_26_light = 2131165376; - - // aapt resource value: 0x7F0700C1 - public const int ic_mr_button_connected_27_dark = 2131165377; - - // aapt resource value: 0x7F0700C2 - public const int ic_mr_button_connected_27_light = 2131165378; - - // aapt resource value: 0x7F0700C3 - public const int ic_mr_button_connected_28_dark = 2131165379; - - // aapt resource value: 0x7F0700C4 - public const int ic_mr_button_connected_28_light = 2131165380; - - // aapt resource value: 0x7F0700C5 - public const int ic_mr_button_connected_29_dark = 2131165381; - - // aapt resource value: 0x7F0700C6 - public const int ic_mr_button_connected_29_light = 2131165382; - - // aapt resource value: 0x7F0700C7 - public const int ic_mr_button_connected_30_dark = 2131165383; - - // aapt resource value: 0x7F0700C8 - public const int ic_mr_button_connected_30_light = 2131165384; - - // aapt resource value: 0x7F0700C9 - public const int ic_mr_button_connecting_00_dark = 2131165385; - - // aapt resource value: 0x7F0700CA - public const int ic_mr_button_connecting_00_light = 2131165386; - - // aapt resource value: 0x7F0700CB - public const int ic_mr_button_connecting_01_dark = 2131165387; - - // aapt resource value: 0x7F0700CC - public const int ic_mr_button_connecting_01_light = 2131165388; - - // aapt resource value: 0x7F0700CD - public const int ic_mr_button_connecting_02_dark = 2131165389; - - // aapt resource value: 0x7F0700CE - public const int ic_mr_button_connecting_02_light = 2131165390; - - // aapt resource value: 0x7F0700CF - public const int ic_mr_button_connecting_03_dark = 2131165391; - - // aapt resource value: 0x7F0700D0 - public const int ic_mr_button_connecting_03_light = 2131165392; - - // aapt resource value: 0x7F0700D1 - public const int ic_mr_button_connecting_04_dark = 2131165393; - - // aapt resource value: 0x7F0700D2 - public const int ic_mr_button_connecting_04_light = 2131165394; - - // aapt resource value: 0x7F0700D3 - public const int ic_mr_button_connecting_05_dark = 2131165395; - - // aapt resource value: 0x7F0700D4 - public const int ic_mr_button_connecting_05_light = 2131165396; - - // aapt resource value: 0x7F0700D5 - public const int ic_mr_button_connecting_06_dark = 2131165397; - - // aapt resource value: 0x7F0700D6 - public const int ic_mr_button_connecting_06_light = 2131165398; - - // aapt resource value: 0x7F0700D7 - public const int ic_mr_button_connecting_07_dark = 2131165399; - - // aapt resource value: 0x7F0700D8 - public const int ic_mr_button_connecting_07_light = 2131165400; - - // aapt resource value: 0x7F0700D9 - public const int ic_mr_button_connecting_08_dark = 2131165401; - - // aapt resource value: 0x7F0700DA - public const int ic_mr_button_connecting_08_light = 2131165402; - - // aapt resource value: 0x7F0700DB - public const int ic_mr_button_connecting_09_dark = 2131165403; - - // aapt resource value: 0x7F0700DC - public const int ic_mr_button_connecting_09_light = 2131165404; - - // aapt resource value: 0x7F0700DD - public const int ic_mr_button_connecting_10_dark = 2131165405; - - // aapt resource value: 0x7F0700DE - public const int ic_mr_button_connecting_10_light = 2131165406; - - // aapt resource value: 0x7F0700DF - public const int ic_mr_button_connecting_11_dark = 2131165407; - - // aapt resource value: 0x7F0700E0 - public const int ic_mr_button_connecting_11_light = 2131165408; - - // aapt resource value: 0x7F0700E1 - public const int ic_mr_button_connecting_12_dark = 2131165409; - - // aapt resource value: 0x7F0700E2 - public const int ic_mr_button_connecting_12_light = 2131165410; - - // aapt resource value: 0x7F0700E3 - public const int ic_mr_button_connecting_13_dark = 2131165411; - - // aapt resource value: 0x7F0700E4 - public const int ic_mr_button_connecting_13_light = 2131165412; - - // aapt resource value: 0x7F0700E5 - public const int ic_mr_button_connecting_14_dark = 2131165413; - - // aapt resource value: 0x7F0700E6 - public const int ic_mr_button_connecting_14_light = 2131165414; - - // aapt resource value: 0x7F0700E7 - public const int ic_mr_button_connecting_15_dark = 2131165415; - - // aapt resource value: 0x7F0700E8 - public const int ic_mr_button_connecting_15_light = 2131165416; - - // aapt resource value: 0x7F0700E9 - public const int ic_mr_button_connecting_16_dark = 2131165417; - - // aapt resource value: 0x7F0700EA - public const int ic_mr_button_connecting_16_light = 2131165418; - - // aapt resource value: 0x7F0700EB - public const int ic_mr_button_connecting_17_dark = 2131165419; - - // aapt resource value: 0x7F0700EC - public const int ic_mr_button_connecting_17_light = 2131165420; - - // aapt resource value: 0x7F0700ED - public const int ic_mr_button_connecting_18_dark = 2131165421; - - // aapt resource value: 0x7F0700EE - public const int ic_mr_button_connecting_18_light = 2131165422; - - // aapt resource value: 0x7F0700EF - public const int ic_mr_button_connecting_19_dark = 2131165423; - - // aapt resource value: 0x7F0700F0 - public const int ic_mr_button_connecting_19_light = 2131165424; - - // aapt resource value: 0x7F0700F1 - public const int ic_mr_button_connecting_20_dark = 2131165425; - - // aapt resource value: 0x7F0700F2 - public const int ic_mr_button_connecting_20_light = 2131165426; - - // aapt resource value: 0x7F0700F3 - public const int ic_mr_button_connecting_21_dark = 2131165427; - - // aapt resource value: 0x7F0700F4 - public const int ic_mr_button_connecting_21_light = 2131165428; - - // aapt resource value: 0x7F0700F5 - public const int ic_mr_button_connecting_22_dark = 2131165429; - - // aapt resource value: 0x7F0700F6 - public const int ic_mr_button_connecting_22_light = 2131165430; - - // aapt resource value: 0x7F0700F7 - public const int ic_mr_button_connecting_23_dark = 2131165431; - - // aapt resource value: 0x7F0700F8 - public const int ic_mr_button_connecting_23_light = 2131165432; - - // aapt resource value: 0x7F0700F9 - public const int ic_mr_button_connecting_24_dark = 2131165433; - - // aapt resource value: 0x7F0700FA - public const int ic_mr_button_connecting_24_light = 2131165434; - - // aapt resource value: 0x7F0700FB - public const int ic_mr_button_connecting_25_dark = 2131165435; - - // aapt resource value: 0x7F0700FC - public const int ic_mr_button_connecting_25_light = 2131165436; - - // aapt resource value: 0x7F0700FD - public const int ic_mr_button_connecting_26_dark = 2131165437; - - // aapt resource value: 0x7F0700FE - public const int ic_mr_button_connecting_26_light = 2131165438; - - // aapt resource value: 0x7F0700FF - public const int ic_mr_button_connecting_27_dark = 2131165439; - - // aapt resource value: 0x7F070100 - public const int ic_mr_button_connecting_27_light = 2131165440; - - // aapt resource value: 0x7F070101 - public const int ic_mr_button_connecting_28_dark = 2131165441; - - // aapt resource value: 0x7F070102 - public const int ic_mr_button_connecting_28_light = 2131165442; - - // aapt resource value: 0x7F070103 - public const int ic_mr_button_connecting_29_dark = 2131165443; - - // aapt resource value: 0x7F070104 - public const int ic_mr_button_connecting_29_light = 2131165444; - - // aapt resource value: 0x7F070105 - public const int ic_mr_button_connecting_30_dark = 2131165445; - - // aapt resource value: 0x7F070106 - public const int ic_mr_button_connecting_30_light = 2131165446; - - // aapt resource value: 0x7F070107 - public const int ic_mr_button_disabled_dark = 2131165447; - - // aapt resource value: 0x7F070108 - public const int ic_mr_button_disabled_light = 2131165448; - - // aapt resource value: 0x7F070109 - public const int ic_mr_button_disconnected_dark = 2131165449; - - // aapt resource value: 0x7F07010A - public const int ic_mr_button_disconnected_light = 2131165450; - - // aapt resource value: 0x7F07010B - public const int ic_mr_button_grey = 2131165451; - - // aapt resource value: 0x7F07010C - public const int ic_vol_type_speaker_dark = 2131165452; - - // aapt resource value: 0x7F07010D - public const int ic_vol_type_speaker_group_dark = 2131165453; - - // aapt resource value: 0x7F07010E - public const int ic_vol_type_speaker_group_light = 2131165454; - - // aapt resource value: 0x7F07010F - public const int ic_vol_type_speaker_light = 2131165455; - - // aapt resource value: 0x7F070110 - public const int ic_vol_type_tv_dark = 2131165456; - - // aapt resource value: 0x7F070111 - public const int ic_vol_type_tv_light = 2131165457; - - // aapt resource value: 0x7F070112 - public const int mr_button_connected_dark = 2131165458; - - // aapt resource value: 0x7F070113 - public const int mr_button_connected_light = 2131165459; - - // aapt resource value: 0x7F070114 - public const int mr_button_connecting_dark = 2131165460; - - // aapt resource value: 0x7F070115 - public const int mr_button_connecting_light = 2131165461; - - // aapt resource value: 0x7F070116 - public const int mr_button_dark = 2131165462; - - // aapt resource value: 0x7F070117 - public const int mr_button_light = 2131165463; - - // aapt resource value: 0x7F070118 - public const int mr_dialog_close_dark = 2131165464; - - // aapt resource value: 0x7F070119 - public const int mr_dialog_close_light = 2131165465; - - // aapt resource value: 0x7F07011A - public const int mr_dialog_material_background_dark = 2131165466; - - // aapt resource value: 0x7F07011B - public const int mr_dialog_material_background_light = 2131165467; - - // aapt resource value: 0x7F07011C - public const int mr_group_collapse = 2131165468; - - // aapt resource value: 0x7F07011D - public const int mr_group_expand = 2131165469; - - // aapt resource value: 0x7F07011E - public const int mr_media_pause_dark = 2131165470; - - // aapt resource value: 0x7F07011F - public const int mr_media_pause_light = 2131165471; - - // aapt resource value: 0x7F070120 - public const int mr_media_play_dark = 2131165472; - - // aapt resource value: 0x7F070121 - public const int mr_media_play_light = 2131165473; - - // aapt resource value: 0x7F070122 - public const int mr_media_stop_dark = 2131165474; - - // aapt resource value: 0x7F070123 - public const int mr_media_stop_light = 2131165475; - - // aapt resource value: 0x7F070124 - public const int mr_vol_type_audiotrack_dark = 2131165476; - - // aapt resource value: 0x7F070125 - public const int mr_vol_type_audiotrack_light = 2131165477; - - // aapt resource value: 0x7F070126 - public const int navigation_empty_icon = 2131165478; - - // aapt resource value: 0x7F070127 - public const int notification_action_background = 2131165479; - - // aapt resource value: 0x7F070128 - public const int notification_bg = 2131165480; - - // aapt resource value: 0x7F070129 - public const int notification_bg_low = 2131165481; - - // aapt resource value: 0x7F07012A - public const int notification_bg_low_normal = 2131165482; - - // aapt resource value: 0x7F07012B - public const int notification_bg_low_pressed = 2131165483; - - // aapt resource value: 0x7F07012C - public const int notification_bg_normal = 2131165484; - - // aapt resource value: 0x7F07012D - public const int notification_bg_normal_pressed = 2131165485; - - // aapt resource value: 0x7F07012E - public const int notification_icon_background = 2131165486; - - // aapt resource value: 0x7F07012F - public const int notification_template_icon_bg = 2131165487; - - // aapt resource value: 0x7F070130 - public const int notification_template_icon_low_bg = 2131165488; - - // aapt resource value: 0x7F070131 - public const int notification_tile_bg = 2131165489; - - // aapt resource value: 0x7F070132 - public const int notify_panel_notification_icon_bg = 2131165490; - - // aapt resource value: 0x7F070133 - public const int tooltip_frame_dark = 2131165491; - - // aapt resource value: 0x7F070134 - public const int tooltip_frame_light = 2131165492; - - static Drawable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Drawable() - { - } - } - - public partial class Id - { - - // aapt resource value: 0x7F080006 - public const int action0 = 2131230726; - - // aapt resource value: 0x7F080018 - public const int actions = 2131230744; - - // aapt resource value: 0x7F080007 - public const int action_bar = 2131230727; - - // aapt resource value: 0x7F080008 - public const int action_bar_activity_content = 2131230728; - - // aapt resource value: 0x7F080009 - public const int action_bar_container = 2131230729; - - // aapt resource value: 0x7F08000A - public const int action_bar_root = 2131230730; - - // aapt resource value: 0x7F08000B - public const int action_bar_spinner = 2131230731; - - // aapt resource value: 0x7F08000C - public const int action_bar_subtitle = 2131230732; - - // aapt resource value: 0x7F08000D - public const int action_bar_title = 2131230733; - - // aapt resource value: 0x7F08000E - public const int action_container = 2131230734; - - // aapt resource value: 0x7F08000F - public const int action_context_bar = 2131230735; - - // aapt resource value: 0x7F080010 - public const int action_divider = 2131230736; - - // aapt resource value: 0x7F080011 - public const int action_image = 2131230737; - - // aapt resource value: 0x7F080012 - public const int action_menu_divider = 2131230738; - - // aapt resource value: 0x7F080013 - public const int action_menu_presenter = 2131230739; - - // aapt resource value: 0x7F080014 - public const int action_mode_bar = 2131230740; - - // aapt resource value: 0x7F080015 - public const int action_mode_bar_stub = 2131230741; - - // aapt resource value: 0x7F080016 - public const int action_mode_close_button = 2131230742; - - // aapt resource value: 0x7F080017 - public const int action_text = 2131230743; - - // aapt resource value: 0x7F080019 - public const int activity_chooser_view_content = 2131230745; - - // aapt resource value: 0x7F08001A - public const int add = 2131230746; - - // aapt resource value: 0x7F08001B - public const int alertTitle = 2131230747; - - // aapt resource value: 0x7F08001C - public const int all = 2131230748; - - // aapt resource value: 0x7F080000 - public const int ALT = 2131230720; - - // aapt resource value: 0x7F08001D - public const int always = 2131230749; - - // aapt resource value: 0x7F08001E - public const int async = 2131230750; - - // aapt resource value: 0x7F08001F - public const int auto = 2131230751; - - // aapt resource value: 0x7F080020 - public const int beginning = 2131230752; - - // aapt resource value: 0x7F080021 - public const int blocking = 2131230753; - - // aapt resource value: 0x7F080022 - public const int bottom = 2131230754; - - // aapt resource value: 0x7F080023 - public const int bottomtab_navarea = 2131230755; - - // aapt resource value: 0x7F080024 - public const int bottomtab_tabbar = 2131230756; - - // aapt resource value: 0x7F080025 - public const int buttonPanel = 2131230757; - - // aapt resource value: 0x7F080026 - public const int cancel_action = 2131230758; - - // aapt resource value: 0x7F080027 - public const int center = 2131230759; - - // aapt resource value: 0x7F080028 - public const int center_horizontal = 2131230760; - - // aapt resource value: 0x7F080029 - public const int center_vertical = 2131230761; - - // aapt resource value: 0x7F08002A - public const int checkbox = 2131230762; - - // aapt resource value: 0x7F08002B - public const int chronometer = 2131230763; - - // aapt resource value: 0x7F08002C - public const int clip_horizontal = 2131230764; - - // aapt resource value: 0x7F08002D - public const int clip_vertical = 2131230765; - - // aapt resource value: 0x7F08002E - public const int collapseActionView = 2131230766; - - // aapt resource value: 0x7F08002F - public const int container = 2131230767; - - // aapt resource value: 0x7F080030 - public const int contentPanel = 2131230768; - - // aapt resource value: 0x7F080031 - public const int coordinator = 2131230769; - - // aapt resource value: 0x7F080001 - public const int CTRL = 2131230721; - - // aapt resource value: 0x7F080032 - public const int custom = 2131230770; - - // aapt resource value: 0x7F080033 - public const int customPanel = 2131230771; - - // aapt resource value: 0x7F080034 - public const int decor_content_parent = 2131230772; - - // aapt resource value: 0x7F080035 - public const int default_activity_button = 2131230773; - - // aapt resource value: 0x7F080036 - public const int design_bottom_sheet = 2131230774; - - // aapt resource value: 0x7F080037 - public const int design_menu_item_action_area = 2131230775; - - // aapt resource value: 0x7F080038 - public const int design_menu_item_action_area_stub = 2131230776; - - // aapt resource value: 0x7F080039 - public const int design_menu_item_text = 2131230777; - - // aapt resource value: 0x7F08003A - public const int design_navigation_view = 2131230778; - - // aapt resource value: 0x7F08003B - public const int disableHome = 2131230779; - - // aapt resource value: 0x7F08003C - public const int edit_query = 2131230780; - - // aapt resource value: 0x7F08003D - public const int end = 2131230781; - - // aapt resource value: 0x7F08003E - public const int end_padder = 2131230782; - - // aapt resource value: 0x7F08003F - public const int enterAlways = 2131230783; - - // aapt resource value: 0x7F080040 - public const int enterAlwaysCollapsed = 2131230784; - - // aapt resource value: 0x7F080041 - public const int exitUntilCollapsed = 2131230785; - - // aapt resource value: 0x7F080043 - public const int expanded_menu = 2131230787; - - // aapt resource value: 0x7F080042 - public const int expand_activities_button = 2131230786; - - // aapt resource value: 0x7F080044 - public const int fill = 2131230788; - - // aapt resource value: 0x7F080045 - public const int fill_horizontal = 2131230789; - - // aapt resource value: 0x7F080046 - public const int fill_vertical = 2131230790; - - // aapt resource value: 0x7F080047 - public const int @fixed = 2131230791; - - // aapt resource value: 0x7F080048 - public const int flyoutcontent_appbar = 2131230792; - - // aapt resource value: 0x7F080049 - public const int flyoutcontent_recycler = 2131230793; - - // aapt resource value: 0x7F08004A - public const int forever = 2131230794; - - // aapt resource value: 0x7F080002 - public const int FUNCTION = 2131230722; - - // aapt resource value: 0x7F08004B - public const int ghost_view = 2131230795; - - // aapt resource value: 0x7F08004C - public const int home = 2131230796; - - // aapt resource value: 0x7F08004D - public const int homeAsUp = 2131230797; - - // aapt resource value: 0x7F08004E - public const int icon = 2131230798; - - // aapt resource value: 0x7F08004F - public const int icon_group = 2131230799; - - // aapt resource value: 0x7F080050 - public const int ifRoom = 2131230800; - - // aapt resource value: 0x7F080051 - public const int image = 2131230801; - - // aapt resource value: 0x7F080052 - public const int info = 2131230802; - - // aapt resource value: 0x7F080053 - public const int italic = 2131230803; - - // aapt resource value: 0x7F080054 - public const int item_touch_helper_previous_elevation = 2131230804; - - // aapt resource value: 0x7F080055 - public const int largeLabel = 2131230805; - - // aapt resource value: 0x7F080056 - public const int left = 2131230806; - - // aapt resource value: 0x7F080057 - public const int line1 = 2131230807; - - // aapt resource value: 0x7F080058 - public const int line3 = 2131230808; - - // aapt resource value: 0x7F080059 - public const int listMode = 2131230809; - - // aapt resource value: 0x7F08005A - public const int list_item = 2131230810; - - // aapt resource value: 0x7F08005B - public const int main_appbar = 2131230811; - - // aapt resource value: 0x7F08005C - public const int main_scrollview = 2131230812; - - // aapt resource value: 0x7F08005D - public const int main_tablayout = 2131230813; - - // aapt resource value: 0x7F08005E - public const int main_toolbar = 2131230814; - - // aapt resource value: 0x7F08005F - public const int masked = 2131230815; - - // aapt resource value: 0x7F080060 - public const int media_actions = 2131230816; - - // aapt resource value: 0x7F080061 - public const int message = 2131230817; - - // aapt resource value: 0x7F080003 - public const int META = 2131230723; - - // aapt resource value: 0x7F080062 - public const int middle = 2131230818; - - // aapt resource value: 0x7F080063 - public const int mini = 2131230819; - - // aapt resource value: 0x7F080064 - public const int mr_art = 2131230820; - - // aapt resource value: 0x7F080065 - public const int mr_chooser_list = 2131230821; - - // aapt resource value: 0x7F080066 - public const int mr_chooser_route_desc = 2131230822; - - // aapt resource value: 0x7F080067 - public const int mr_chooser_route_icon = 2131230823; - - // aapt resource value: 0x7F080068 - public const int mr_chooser_route_name = 2131230824; - - // aapt resource value: 0x7F080069 - public const int mr_chooser_title = 2131230825; - - // aapt resource value: 0x7F08006A - public const int mr_close = 2131230826; - - // aapt resource value: 0x7F08006B - public const int mr_control_divider = 2131230827; - - // aapt resource value: 0x7F08006C - public const int mr_control_playback_ctrl = 2131230828; - - // aapt resource value: 0x7F08006D - public const int mr_control_subtitle = 2131230829; - - // aapt resource value: 0x7F08006E - public const int mr_control_title = 2131230830; - - // aapt resource value: 0x7F08006F - public const int mr_control_title_container = 2131230831; - - // aapt resource value: 0x7F080070 - public const int mr_custom_control = 2131230832; - - // aapt resource value: 0x7F080071 - public const int mr_default_control = 2131230833; - - // aapt resource value: 0x7F080072 - public const int mr_dialog_area = 2131230834; - - // aapt resource value: 0x7F080073 - public const int mr_expandable_area = 2131230835; - - // aapt resource value: 0x7F080074 - public const int mr_group_expand_collapse = 2131230836; - - // aapt resource value: 0x7F080075 - public const int mr_media_main_control = 2131230837; - - // aapt resource value: 0x7F080076 - public const int mr_name = 2131230838; - - // aapt resource value: 0x7F080077 - public const int mr_playback_control = 2131230839; - - // aapt resource value: 0x7F080078 - public const int mr_title_bar = 2131230840; - - // aapt resource value: 0x7F080079 - public const int mr_volume_control = 2131230841; - - // aapt resource value: 0x7F08007A - public const int mr_volume_group_list = 2131230842; - - // aapt resource value: 0x7F08007B - public const int mr_volume_item_icon = 2131230843; - - // aapt resource value: 0x7F08007C - public const int mr_volume_slider = 2131230844; - - // aapt resource value: 0x7F08007D - public const int multiply = 2131230845; - - // aapt resource value: 0x7F08007E - public const int navigation_header_container = 2131230846; - - // aapt resource value: 0x7F08007F - public const int never = 2131230847; - - // aapt resource value: 0x7F080080 - public const int none = 2131230848; - - // aapt resource value: 0x7F080081 - public const int normal = 2131230849; - - // aapt resource value: 0x7F080082 - public const int notification_background = 2131230850; - - // aapt resource value: 0x7F080083 - public const int notification_main_column = 2131230851; - - // aapt resource value: 0x7F080084 - public const int notification_main_column_container = 2131230852; - - // aapt resource value: 0x7F080085 - public const int parallax = 2131230853; - - // aapt resource value: 0x7F080086 - public const int parentPanel = 2131230854; - - // aapt resource value: 0x7F080087 - public const int parent_matrix = 2131230855; - - // aapt resource value: 0x7F080088 - public const int pin = 2131230856; - - // aapt resource value: 0x7F080089 - public const int progress_circular = 2131230857; - - // aapt resource value: 0x7F08008A - public const int progress_horizontal = 2131230858; - - // aapt resource value: 0x7F08008B - public const int radio = 2131230859; - - // aapt resource value: 0x7F08008C - public const int right = 2131230860; - - // aapt resource value: 0x7F08008D - public const int right_icon = 2131230861; - - // aapt resource value: 0x7F08008E - public const int right_side = 2131230862; - - // aapt resource value: 0x7F08008F - public const int save_image_matrix = 2131230863; - - // aapt resource value: 0x7F080090 - public const int save_non_transition_alpha = 2131230864; - - // aapt resource value: 0x7F080091 - public const int save_scale_type = 2131230865; - - // aapt resource value: 0x7F080092 - public const int screen = 2131230866; - - // aapt resource value: 0x7F080093 - public const int scroll = 2131230867; - - // aapt resource value: 0x7F080097 - public const int scrollable = 2131230871; - - // aapt resource value: 0x7F080094 - public const int scrollIndicatorDown = 2131230868; - - // aapt resource value: 0x7F080095 - public const int scrollIndicatorUp = 2131230869; - - // aapt resource value: 0x7F080096 - public const int scrollView = 2131230870; - - // aapt resource value: 0x7F080098 - public const int search_badge = 2131230872; - - // aapt resource value: 0x7F080099 - public const int search_bar = 2131230873; - - // aapt resource value: 0x7F08009A - public const int search_button = 2131230874; - - // aapt resource value: 0x7F08009B - public const int search_close_btn = 2131230875; - - // aapt resource value: 0x7F08009C - public const int search_edit_frame = 2131230876; - - // aapt resource value: 0x7F08009D - public const int search_go_btn = 2131230877; - - // aapt resource value: 0x7F08009E - public const int search_mag_icon = 2131230878; - - // aapt resource value: 0x7F08009F - public const int search_plate = 2131230879; - - // aapt resource value: 0x7F0800A0 - public const int search_src_text = 2131230880; - - // aapt resource value: 0x7F0800A1 - public const int search_voice_btn = 2131230881; - - // aapt resource value: 0x7F0800A2 - public const int select_dialog_listview = 2131230882; - - // aapt resource value: 0x7F0800A3 - public const int shellcontent_appbar = 2131230883; - - // aapt resource value: 0x7F0800A4 - public const int shellcontent_scrollview = 2131230884; - - // aapt resource value: 0x7F0800A5 - public const int shellcontent_toolbar = 2131230885; - - // aapt resource value: 0x7F080004 - public const int SHIFT = 2131230724; - - // aapt resource value: 0x7F0800A6 - public const int shortcut = 2131230886; - - // aapt resource value: 0x7F0800A7 - public const int showCustom = 2131230887; - - // aapt resource value: 0x7F0800A8 - public const int showHome = 2131230888; - - // aapt resource value: 0x7F0800A9 - public const int showTitle = 2131230889; - - // aapt resource value: 0x7F0800AA - public const int smallLabel = 2131230890; - - // aapt resource value: 0x7F0800AB - public const int snackbar_action = 2131230891; - - // aapt resource value: 0x7F0800AC - public const int snackbar_text = 2131230892; - - // aapt resource value: 0x7F0800AD - public const int snap = 2131230893; - - // aapt resource value: 0x7F0800AE - public const int spacer = 2131230894; - - // aapt resource value: 0x7F0800AF - public const int split_action_bar = 2131230895; - - // aapt resource value: 0x7F0800B0 - public const int src_atop = 2131230896; - - // aapt resource value: 0x7F0800B1 - public const int src_in = 2131230897; - - // aapt resource value: 0x7F0800B2 - public const int src_over = 2131230898; - - // aapt resource value: 0x7F0800B3 - public const int start = 2131230899; - - // aapt resource value: 0x7F0800B4 - public const int status_bar_latest_event_content = 2131230900; - - // aapt resource value: 0x7F0800B5 - public const int submenuarrow = 2131230901; - - // aapt resource value: 0x7F0800B6 - public const int submit_area = 2131230902; - - // aapt resource value: 0x7F080005 - public const int SYM = 2131230725; - - // aapt resource value: 0x7F0800B7 - public const int tabMode = 2131230903; - - // aapt resource value: 0x7F0800B8 - public const int tag_transition_group = 2131230904; - - // aapt resource value: 0x7F0800B9 - public const int text = 2131230905; - - // aapt resource value: 0x7F0800BA - public const int text2 = 2131230906; - - // aapt resource value: 0x7F0800BE - public const int textinput_counter = 2131230910; - - // aapt resource value: 0x7F0800BF - public const int textinput_error = 2131230911; - - // aapt resource value: 0x7F0800BB - public const int textSpacerNoButtons = 2131230907; - - // aapt resource value: 0x7F0800BC - public const int textSpacerNoTitle = 2131230908; - - // aapt resource value: 0x7F0800BD - public const int text_input_password_toggle = 2131230909; - - // aapt resource value: 0x7F0800C0 - public const int time = 2131230912; - - // aapt resource value: 0x7F0800C1 - public const int title = 2131230913; - - // aapt resource value: 0x7F0800C2 - public const int titleDividerNoCustom = 2131230914; - - // aapt resource value: 0x7F0800C3 - public const int title_template = 2131230915; - - // aapt resource value: 0x7F0800C4 - public const int top = 2131230916; - - // aapt resource value: 0x7F0800C5 - public const int topPanel = 2131230917; - - // aapt resource value: 0x7F0800C6 - public const int touch_outside = 2131230918; - - // aapt resource value: 0x7F0800C7 - public const int transition_current_scene = 2131230919; - - // aapt resource value: 0x7F0800C8 - public const int transition_layout_save = 2131230920; - - // aapt resource value: 0x7F0800C9 - public const int transition_position = 2131230921; - - // aapt resource value: 0x7F0800CA - public const int transition_scene_layoutid_cache = 2131230922; - - // aapt resource value: 0x7F0800CB - public const int transition_transform = 2131230923; - - // aapt resource value: 0x7F0800CC - public const int uniform = 2131230924; - - // aapt resource value: 0x7F0800CD - public const int up = 2131230925; - - // aapt resource value: 0x7F0800CE - public const int useLogo = 2131230926; - - // aapt resource value: 0x7F0800CF - public const int view_offset_helper = 2131230927; - - // aapt resource value: 0x7F0800D0 - public const int visible = 2131230928; - - // aapt resource value: 0x7F0800D1 - public const int volume_item_container = 2131230929; - - // aapt resource value: 0x7F0800D2 - public const int withText = 2131230930; - - // aapt resource value: 0x7F0800D3 - public const int wrap_content = 2131230931; - - static Id() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Id() - { - } - } - - public partial class Integer - { - - // aapt resource value: 0x7F090000 - public const int abc_config_activityDefaultDur = 2131296256; - - // aapt resource value: 0x7F090001 - public const int abc_config_activityShortDur = 2131296257; - - // aapt resource value: 0x7F090002 - public const int app_bar_elevation_anim_duration = 2131296258; - - // aapt resource value: 0x7F090003 - public const int bottom_sheet_slide_duration = 2131296259; - - // aapt resource value: 0x7F090004 - public const int cancel_button_image_alpha = 2131296260; - - // aapt resource value: 0x7F090005 - public const int config_tooltipAnimTime = 2131296261; - - // aapt resource value: 0x7F090006 - public const int design_snackbar_text_max_lines = 2131296262; - - // aapt resource value: 0x7F090007 - public const int hide_password_duration = 2131296263; - - // aapt resource value: 0x7F090008 - public const int mr_controller_volume_group_list_animation_duration_ms = 2131296264; - - // aapt resource value: 0x7F090009 - public const int mr_controller_volume_group_list_fade_in_duration_ms = 2131296265; - - // aapt resource value: 0x7F09000A - public const int mr_controller_volume_group_list_fade_out_duration_ms = 2131296266; - - // aapt resource value: 0x7F09000B - public const int show_password_duration = 2131296267; - - // aapt resource value: 0x7F09000C - public const int status_bar_notification_info_maxnum = 2131296268; - - static Integer() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Integer() - { - } - } - - public partial class Interpolator - { - - // aapt resource value: 0x7F0A0000 - public const int mr_fast_out_slow_in = 2131361792; - - // aapt resource value: 0x7F0A0001 - public const int mr_linear_out_slow_in = 2131361793; - - static Interpolator() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Interpolator() - { - } - } - - public partial class Layout - { - - // aapt resource value: 0x7F0B0000 - public const int abc_action_bar_title_item = 2131427328; - - // aapt resource value: 0x7F0B0001 - public const int abc_action_bar_up_container = 2131427329; - - // aapt resource value: 0x7F0B0002 - public const int abc_action_menu_item_layout = 2131427330; - - // aapt resource value: 0x7F0B0003 - public const int abc_action_menu_layout = 2131427331; - - // aapt resource value: 0x7F0B0004 - public const int abc_action_mode_bar = 2131427332; - - // aapt resource value: 0x7F0B0005 - public const int abc_action_mode_close_item_material = 2131427333; - - // aapt resource value: 0x7F0B0006 - public const int abc_activity_chooser_view = 2131427334; - - // aapt resource value: 0x7F0B0007 - public const int abc_activity_chooser_view_list_item = 2131427335; - - // aapt resource value: 0x7F0B0008 - public const int abc_alert_dialog_button_bar_material = 2131427336; - - // aapt resource value: 0x7F0B0009 - public const int abc_alert_dialog_material = 2131427337; - - // aapt resource value: 0x7F0B000A - public const int abc_alert_dialog_title_material = 2131427338; - - // aapt resource value: 0x7F0B000B - public const int abc_dialog_title_material = 2131427339; - - // aapt resource value: 0x7F0B000C - public const int abc_expanded_menu_layout = 2131427340; - - // aapt resource value: 0x7F0B000D - public const int abc_list_menu_item_checkbox = 2131427341; - - // aapt resource value: 0x7F0B000E - public const int abc_list_menu_item_icon = 2131427342; - - // aapt resource value: 0x7F0B000F - public const int abc_list_menu_item_layout = 2131427343; - - // aapt resource value: 0x7F0B0010 - public const int abc_list_menu_item_radio = 2131427344; - - // aapt resource value: 0x7F0B0011 - public const int abc_popup_menu_header_item_layout = 2131427345; - - // aapt resource value: 0x7F0B0012 - public const int abc_popup_menu_item_layout = 2131427346; - - // aapt resource value: 0x7F0B0013 - public const int abc_screen_content_include = 2131427347; - - // aapt resource value: 0x7F0B0014 - public const int abc_screen_simple = 2131427348; - - // aapt resource value: 0x7F0B0015 - public const int abc_screen_simple_overlay_action_mode = 2131427349; - - // aapt resource value: 0x7F0B0016 - public const int abc_screen_toolbar = 2131427350; - - // aapt resource value: 0x7F0B0017 - public const int abc_search_dropdown_item_icons_2line = 2131427351; - - // aapt resource value: 0x7F0B0018 - public const int abc_search_view = 2131427352; - - // aapt resource value: 0x7F0B0019 - public const int abc_select_dialog_material = 2131427353; - - // aapt resource value: 0x7F0B001A - public const int activity_main = 2131427354; - - // aapt resource value: 0x7F0B001B - public const int BottomTabLayout = 2131427355; - - // aapt resource value: 0x7F0B001C - public const int design_bottom_navigation_item = 2131427356; - - // aapt resource value: 0x7F0B001D - public const int design_bottom_sheet_dialog = 2131427357; - - // aapt resource value: 0x7F0B001E - public const int design_layout_snackbar = 2131427358; - - // aapt resource value: 0x7F0B001F - public const int design_layout_snackbar_include = 2131427359; - - // aapt resource value: 0x7F0B0020 - public const int design_layout_tab_icon = 2131427360; - - // aapt resource value: 0x7F0B0021 - public const int design_layout_tab_text = 2131427361; - - // aapt resource value: 0x7F0B0022 - public const int design_menu_item_action_area = 2131427362; - - // aapt resource value: 0x7F0B0023 - public const int design_navigation_item = 2131427363; - - // aapt resource value: 0x7F0B0024 - public const int design_navigation_item_header = 2131427364; - - // aapt resource value: 0x7F0B0025 - public const int design_navigation_item_separator = 2131427365; - - // aapt resource value: 0x7F0B0026 - public const int design_navigation_item_subheader = 2131427366; - - // aapt resource value: 0x7F0B0027 - public const int design_navigation_menu = 2131427367; - - // aapt resource value: 0x7F0B0028 - public const int design_navigation_menu_item = 2131427368; - - // aapt resource value: 0x7F0B0029 - public const int design_text_input_password_icon = 2131427369; - - // aapt resource value: 0x7F0B002A - public const int FlyoutContent = 2131427370; - - // aapt resource value: 0x7F0B002B - public const int mr_chooser_dialog = 2131427371; - - // aapt resource value: 0x7F0B002C - public const int mr_chooser_list_item = 2131427372; - - // aapt resource value: 0x7F0B002D - public const int mr_controller_material_dialog_b = 2131427373; - - // aapt resource value: 0x7F0B002E - public const int mr_controller_volume_item = 2131427374; - - // aapt resource value: 0x7F0B002F - public const int mr_playback_control = 2131427375; - - // aapt resource value: 0x7F0B0030 - public const int mr_volume_control = 2131427376; - - // aapt resource value: 0x7F0B0031 - public const int notification_action = 2131427377; - - // aapt resource value: 0x7F0B0032 - public const int notification_action_tombstone = 2131427378; - - // aapt resource value: 0x7F0B0033 - public const int notification_media_action = 2131427379; - - // aapt resource value: 0x7F0B0034 - public const int notification_media_cancel_action = 2131427380; - - // aapt resource value: 0x7F0B0035 - public const int notification_template_big_media = 2131427381; - - // aapt resource value: 0x7F0B0036 - public const int notification_template_big_media_custom = 2131427382; - - // aapt resource value: 0x7F0B0037 - public const int notification_template_big_media_narrow = 2131427383; - - // aapt resource value: 0x7F0B0038 - public const int notification_template_big_media_narrow_custom = 2131427384; - - // aapt resource value: 0x7F0B0039 - public const int notification_template_custom_big = 2131427385; - - // aapt resource value: 0x7F0B003A - public const int notification_template_icon_group = 2131427386; - - // aapt resource value: 0x7F0B003B - public const int notification_template_lines_media = 2131427387; - - // aapt resource value: 0x7F0B003C - public const int notification_template_media = 2131427388; - - // aapt resource value: 0x7F0B003D - public const int notification_template_media_custom = 2131427389; - - // aapt resource value: 0x7F0B003E - public const int notification_template_part_chronometer = 2131427390; - - // aapt resource value: 0x7F0B003F - public const int notification_template_part_time = 2131427391; - - // aapt resource value: 0x7F0B0040 - public const int RootLayout = 2131427392; - - // aapt resource value: 0x7F0B0041 - public const int select_dialog_item_material = 2131427393; - - // aapt resource value: 0x7F0B0042 - public const int select_dialog_multichoice_material = 2131427394; - - // aapt resource value: 0x7F0B0043 - public const int select_dialog_singlechoice_material = 2131427395; - - // aapt resource value: 0x7F0B0044 - public const int ShellContent = 2131427396; - - // aapt resource value: 0x7F0B0045 - public const int support_simple_spinner_dropdown_item = 2131427397; - - // aapt resource value: 0x7F0B0046 - public const int tooltip = 2131427398; - - static Layout() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Layout() - { - } - } - - public partial class Mipmap - { - - // aapt resource value: 0x7F0C0000 - public const int ic_launcher = 2131492864; - - // aapt resource value: 0x7F0C0001 - public const int ic_launcher_foreground = 2131492865; - - // aapt resource value: 0x7F0C0002 - public const int ic_launcher_round = 2131492866; - - static Mipmap() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Mipmap() - { - } - } - - public partial class String - { - - // aapt resource value: 0x7F0D0000 - public const int abc_action_bar_home_description = 2131558400; - - // aapt resource value: 0x7F0D0001 - public const int abc_action_bar_up_description = 2131558401; - - // aapt resource value: 0x7F0D0002 - public const int abc_action_menu_overflow_description = 2131558402; - - // aapt resource value: 0x7F0D0003 - public const int abc_action_mode_done = 2131558403; - - // aapt resource value: 0x7F0D0005 - public const int abc_activitychooserview_choose_application = 2131558405; - - // aapt resource value: 0x7F0D0004 - public const int abc_activity_chooser_view_see_all = 2131558404; - - // aapt resource value: 0x7F0D0006 - public const int abc_capital_off = 2131558406; - - // aapt resource value: 0x7F0D0007 - public const int abc_capital_on = 2131558407; - - // aapt resource value: 0x7F0D0008 - public const int abc_font_family_body_1_material = 2131558408; - - // aapt resource value: 0x7F0D0009 - public const int abc_font_family_body_2_material = 2131558409; - - // aapt resource value: 0x7F0D000A - public const int abc_font_family_button_material = 2131558410; - - // aapt resource value: 0x7F0D000B - public const int abc_font_family_caption_material = 2131558411; - - // aapt resource value: 0x7F0D000C - public const int abc_font_family_display_1_material = 2131558412; - - // aapt resource value: 0x7F0D000D - public const int abc_font_family_display_2_material = 2131558413; - - // aapt resource value: 0x7F0D000E - public const int abc_font_family_display_3_material = 2131558414; - - // aapt resource value: 0x7F0D000F - public const int abc_font_family_display_4_material = 2131558415; - - // aapt resource value: 0x7F0D0010 - public const int abc_font_family_headline_material = 2131558416; - - // aapt resource value: 0x7F0D0011 - public const int abc_font_family_menu_material = 2131558417; - - // aapt resource value: 0x7F0D0012 - public const int abc_font_family_subhead_material = 2131558418; - - // aapt resource value: 0x7F0D0013 - public const int abc_font_family_title_material = 2131558419; - - // aapt resource value: 0x7F0D0015 - public const int abc_searchview_description_clear = 2131558421; - - // aapt resource value: 0x7F0D0016 - public const int abc_searchview_description_query = 2131558422; - - // aapt resource value: 0x7F0D0017 - public const int abc_searchview_description_search = 2131558423; - - // aapt resource value: 0x7F0D0018 - public const int abc_searchview_description_submit = 2131558424; - - // aapt resource value: 0x7F0D0019 - public const int abc_searchview_description_voice = 2131558425; - - // aapt resource value: 0x7F0D0014 - public const int abc_search_hint = 2131558420; - - // aapt resource value: 0x7F0D001A - public const int abc_shareactionprovider_share_with = 2131558426; - - // aapt resource value: 0x7F0D001B - public const int abc_shareactionprovider_share_with_application = 2131558427; - - // aapt resource value: 0x7F0D001C - public const int abc_toolbar_collapse_description = 2131558428; - - // aapt resource value: 0x7F0D001D - public const int action_settings = 2131558429; - - // aapt resource value: 0x7F0D001F - public const int appbar_scrolling_view_behavior = 2131558431; - - // aapt resource value: 0x7F0D001E - public const int app_name = 2131558430; - - // aapt resource value: 0x7F0D0020 - public const int bottom_sheet_behavior = 2131558432; - - // aapt resource value: 0x7F0D0021 - public const int character_counter_pattern = 2131558433; - - // aapt resource value: 0x7F0D0022 - public const int mr_button_content_description = 2131558434; - - // aapt resource value: 0x7F0D0023 - public const int mr_cast_button_connected = 2131558435; - - // aapt resource value: 0x7F0D0024 - public const int mr_cast_button_connecting = 2131558436; - - // aapt resource value: 0x7F0D0025 - public const int mr_cast_button_disconnected = 2131558437; - - // aapt resource value: 0x7F0D0026 - public const int mr_chooser_searching = 2131558438; - - // aapt resource value: 0x7F0D0027 - public const int mr_chooser_title = 2131558439; - - // aapt resource value: 0x7F0D0028 - public const int mr_controller_album_art = 2131558440; - - // aapt resource value: 0x7F0D0029 - public const int mr_controller_casting_screen = 2131558441; - - // aapt resource value: 0x7F0D002A - public const int mr_controller_close_description = 2131558442; - - // aapt resource value: 0x7F0D002B - public const int mr_controller_collapse_group = 2131558443; - - // aapt resource value: 0x7F0D002C - public const int mr_controller_disconnect = 2131558444; - - // aapt resource value: 0x7F0D002D - public const int mr_controller_expand_group = 2131558445; - - // aapt resource value: 0x7F0D002E - public const int mr_controller_no_info_available = 2131558446; - - // aapt resource value: 0x7F0D002F - public const int mr_controller_no_media_selected = 2131558447; - - // aapt resource value: 0x7F0D0030 - public const int mr_controller_pause = 2131558448; - - // aapt resource value: 0x7F0D0031 - public const int mr_controller_play = 2131558449; - - // aapt resource value: 0x7F0D0032 - public const int mr_controller_stop = 2131558450; - - // aapt resource value: 0x7F0D0033 - public const int mr_controller_stop_casting = 2131558451; - - // aapt resource value: 0x7F0D0034 - public const int mr_controller_volume_slider = 2131558452; - - // aapt resource value: 0x7F0D0035 - public const int mr_system_route_name = 2131558453; - - // aapt resource value: 0x7F0D0036 - public const int mr_user_route_category_name = 2131558454; - - // aapt resource value: 0x7F0D0037 - public const int password_toggle_content_description = 2131558455; - - // aapt resource value: 0x7F0D0038 - public const int path_password_eye = 2131558456; - - // aapt resource value: 0x7F0D0039 - public const int path_password_eye_mask_strike_through = 2131558457; - - // aapt resource value: 0x7F0D003A - public const int path_password_eye_mask_visible = 2131558458; - - // aapt resource value: 0x7F0D003B - public const int path_password_strike_through = 2131558459; - - // aapt resource value: 0x7F0D003C - public const int search_menu_title = 2131558460; - - // aapt resource value: 0x7F0D003D - public const int status_bar_notification_info_overflow = 2131558461; - - static String() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private String() - { - } - } - - public partial class Style - { - - // aapt resource value: 0x7F0E0000 - public const int AlertDialog_AppCompat = 2131623936; - - // aapt resource value: 0x7F0E0001 - public const int AlertDialog_AppCompat_Light = 2131623937; - - // aapt resource value: 0x7F0E0002 - public const int Animation_AppCompat_Dialog = 2131623938; - - // aapt resource value: 0x7F0E0003 - public const int Animation_AppCompat_DropDownUp = 2131623939; - - // aapt resource value: 0x7F0E0004 - public const int Animation_AppCompat_Tooltip = 2131623940; - - // aapt resource value: 0x7F0E0005 - public const int Animation_Design_BottomSheetDialog = 2131623941; - - // aapt resource value: 0x7F0E0006 - public const int Base_AlertDialog_AppCompat = 2131623942; - - // aapt resource value: 0x7F0E0007 - public const int Base_AlertDialog_AppCompat_Light = 2131623943; - - // aapt resource value: 0x7F0E0008 - public const int Base_Animation_AppCompat_Dialog = 2131623944; - - // aapt resource value: 0x7F0E0009 - public const int Base_Animation_AppCompat_DropDownUp = 2131623945; - - // aapt resource value: 0x7F0E000A - public const int Base_Animation_AppCompat_Tooltip = 2131623946; - - // aapt resource value: 0x7F0E000B - public const int Base_CardView = 2131623947; - - // aapt resource value: 0x7F0E000D - public const int Base_DialogWindowTitleBackground_AppCompat = 2131623949; - - // aapt resource value: 0x7F0E000C - public const int Base_DialogWindowTitle_AppCompat = 2131623948; - - // aapt resource value: 0x7F0E000E - public const int Base_TextAppearance_AppCompat = 2131623950; - - // aapt resource value: 0x7F0E000F - public const int Base_TextAppearance_AppCompat_Body1 = 2131623951; - - // aapt resource value: 0x7F0E0010 - public const int Base_TextAppearance_AppCompat_Body2 = 2131623952; - - // aapt resource value: 0x7F0E0011 - public const int Base_TextAppearance_AppCompat_Button = 2131623953; - - // aapt resource value: 0x7F0E0012 - public const int Base_TextAppearance_AppCompat_Caption = 2131623954; - - // aapt resource value: 0x7F0E0013 - public const int Base_TextAppearance_AppCompat_Display1 = 2131623955; - - // aapt resource value: 0x7F0E0014 - public const int Base_TextAppearance_AppCompat_Display2 = 2131623956; - - // aapt resource value: 0x7F0E0015 - public const int Base_TextAppearance_AppCompat_Display3 = 2131623957; - - // aapt resource value: 0x7F0E0016 - public const int Base_TextAppearance_AppCompat_Display4 = 2131623958; - - // aapt resource value: 0x7F0E0017 - public const int Base_TextAppearance_AppCompat_Headline = 2131623959; - - // aapt resource value: 0x7F0E0018 - public const int Base_TextAppearance_AppCompat_Inverse = 2131623960; - - // aapt resource value: 0x7F0E0019 - public const int Base_TextAppearance_AppCompat_Large = 2131623961; - - // aapt resource value: 0x7F0E001A - public const int Base_TextAppearance_AppCompat_Large_Inverse = 2131623962; - - // aapt resource value: 0x7F0E001B - public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131623963; - - // aapt resource value: 0x7F0E001C - public const int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131623964; - - // aapt resource value: 0x7F0E001D - public const int Base_TextAppearance_AppCompat_Medium = 2131623965; - - // aapt resource value: 0x7F0E001E - public const int Base_TextAppearance_AppCompat_Medium_Inverse = 2131623966; - - // aapt resource value: 0x7F0E001F - public const int Base_TextAppearance_AppCompat_Menu = 2131623967; - - // aapt resource value: 0x7F0E0020 - public const int Base_TextAppearance_AppCompat_SearchResult = 2131623968; - - // aapt resource value: 0x7F0E0021 - public const int Base_TextAppearance_AppCompat_SearchResult_Subtitle = 2131623969; - - // aapt resource value: 0x7F0E0022 - public const int Base_TextAppearance_AppCompat_SearchResult_Title = 2131623970; - - // aapt resource value: 0x7F0E0023 - public const int Base_TextAppearance_AppCompat_Small = 2131623971; - - // aapt resource value: 0x7F0E0024 - public const int Base_TextAppearance_AppCompat_Small_Inverse = 2131623972; - - // aapt resource value: 0x7F0E0025 - public const int Base_TextAppearance_AppCompat_Subhead = 2131623973; - - // aapt resource value: 0x7F0E0026 - public const int Base_TextAppearance_AppCompat_Subhead_Inverse = 2131623974; - - // aapt resource value: 0x7F0E0027 - public const int Base_TextAppearance_AppCompat_Title = 2131623975; - - // aapt resource value: 0x7F0E0028 - public const int Base_TextAppearance_AppCompat_Title_Inverse = 2131623976; - - // aapt resource value: 0x7F0E0029 - public const int Base_TextAppearance_AppCompat_Tooltip = 2131623977; - - // aapt resource value: 0x7F0E002A - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131623978; - - // aapt resource value: 0x7F0E002B - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131623979; - - // aapt resource value: 0x7F0E002C - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131623980; - - // aapt resource value: 0x7F0E002D - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title = 2131623981; - - // aapt resource value: 0x7F0E002E - public const int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131623982; - - // aapt resource value: 0x7F0E002F - public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131623983; - - // aapt resource value: 0x7F0E0030 - public const int Base_TextAppearance_AppCompat_Widget_ActionMode_Title = 2131623984; - - // aapt resource value: 0x7F0E0031 - public const int Base_TextAppearance_AppCompat_Widget_Button = 2131623985; - - // aapt resource value: 0x7F0E0032 - public const int Base_TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131623986; - - // aapt resource value: 0x7F0E0033 - public const int Base_TextAppearance_AppCompat_Widget_Button_Colored = 2131623987; - - // aapt resource value: 0x7F0E0034 - public const int Base_TextAppearance_AppCompat_Widget_Button_Inverse = 2131623988; - - // aapt resource value: 0x7F0E0035 - public const int Base_TextAppearance_AppCompat_Widget_DropDownItem = 2131623989; - - // aapt resource value: 0x7F0E0036 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131623990; - - // aapt resource value: 0x7F0E0037 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131623991; - - // aapt resource value: 0x7F0E0038 - public const int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131623992; - - // aapt resource value: 0x7F0E0039 - public const int Base_TextAppearance_AppCompat_Widget_Switch = 2131623993; - - // aapt resource value: 0x7F0E003A - public const int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131623994; - - // aapt resource value: 0x7F0E003B - public const int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131623995; - - // aapt resource value: 0x7F0E003C - public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131623996; - - // aapt resource value: 0x7F0E003D - public const int Base_TextAppearance_Widget_AppCompat_Toolbar_Title = 2131623997; - - // aapt resource value: 0x7F0E004C - public const int Base_ThemeOverlay_AppCompat = 2131624012; - - // aapt resource value: 0x7F0E004D - public const int Base_ThemeOverlay_AppCompat_ActionBar = 2131624013; - - // aapt resource value: 0x7F0E004E - public const int Base_ThemeOverlay_AppCompat_Dark = 2131624014; - - // aapt resource value: 0x7F0E004F - public const int Base_ThemeOverlay_AppCompat_Dark_ActionBar = 2131624015; - - // aapt resource value: 0x7F0E0050 - public const int Base_ThemeOverlay_AppCompat_Dialog = 2131624016; - - // aapt resource value: 0x7F0E0051 - public const int Base_ThemeOverlay_AppCompat_Dialog_Alert = 2131624017; - - // aapt resource value: 0x7F0E0052 - public const int Base_ThemeOverlay_AppCompat_Light = 2131624018; - - // aapt resource value: 0x7F0E003E - public const int Base_Theme_AppCompat = 2131623998; - - // aapt resource value: 0x7F0E003F - public const int Base_Theme_AppCompat_CompactMenu = 2131623999; - - // aapt resource value: 0x7F0E0040 - public const int Base_Theme_AppCompat_Dialog = 2131624000; - - // aapt resource value: 0x7F0E0044 - public const int Base_Theme_AppCompat_DialogWhenLarge = 2131624004; - - // aapt resource value: 0x7F0E0041 - public const int Base_Theme_AppCompat_Dialog_Alert = 2131624001; - - // aapt resource value: 0x7F0E0042 - public const int Base_Theme_AppCompat_Dialog_FixedSize = 2131624002; - - // aapt resource value: 0x7F0E0043 - public const int Base_Theme_AppCompat_Dialog_MinWidth = 2131624003; - - // aapt resource value: 0x7F0E0045 - public const int Base_Theme_AppCompat_Light = 2131624005; - - // aapt resource value: 0x7F0E0046 - public const int Base_Theme_AppCompat_Light_DarkActionBar = 2131624006; - - // aapt resource value: 0x7F0E0047 - public const int Base_Theme_AppCompat_Light_Dialog = 2131624007; - - // aapt resource value: 0x7F0E004B - public const int Base_Theme_AppCompat_Light_DialogWhenLarge = 2131624011; - - // aapt resource value: 0x7F0E0048 - public const int Base_Theme_AppCompat_Light_Dialog_Alert = 2131624008; - - // aapt resource value: 0x7F0E0049 - public const int Base_Theme_AppCompat_Light_Dialog_FixedSize = 2131624009; - - // aapt resource value: 0x7F0E004A - public const int Base_Theme_AppCompat_Light_Dialog_MinWidth = 2131624010; - - // aapt resource value: 0x7F0E0055 - public const int Base_V11_ThemeOverlay_AppCompat_Dialog = 2131624021; - - // aapt resource value: 0x7F0E0053 - public const int Base_V11_Theme_AppCompat_Dialog = 2131624019; - - // aapt resource value: 0x7F0E0054 - public const int Base_V11_Theme_AppCompat_Light_Dialog = 2131624020; - - // aapt resource value: 0x7F0E0056 - public const int Base_V12_Widget_AppCompat_AutoCompleteTextView = 2131624022; - - // aapt resource value: 0x7F0E0057 - public const int Base_V12_Widget_AppCompat_EditText = 2131624023; - - // aapt resource value: 0x7F0E0058 - public const int Base_V14_Widget_Design_AppBarLayout = 2131624024; - - // aapt resource value: 0x7F0E005D - public const int Base_V21_ThemeOverlay_AppCompat_Dialog = 2131624029; - - // aapt resource value: 0x7F0E0059 - public const int Base_V21_Theme_AppCompat = 2131624025; - - // aapt resource value: 0x7F0E005A - public const int Base_V21_Theme_AppCompat_Dialog = 2131624026; - - // aapt resource value: 0x7F0E005B - public const int Base_V21_Theme_AppCompat_Light = 2131624027; - - // aapt resource value: 0x7F0E005C - public const int Base_V21_Theme_AppCompat_Light_Dialog = 2131624028; - - // aapt resource value: 0x7F0E005E - public const int Base_V21_Widget_Design_AppBarLayout = 2131624030; - - // aapt resource value: 0x7F0E005F - public const int Base_V22_Theme_AppCompat = 2131624031; - - // aapt resource value: 0x7F0E0060 - public const int Base_V22_Theme_AppCompat_Light = 2131624032; - - // aapt resource value: 0x7F0E0061 - public const int Base_V23_Theme_AppCompat = 2131624033; - - // aapt resource value: 0x7F0E0062 - public const int Base_V23_Theme_AppCompat_Light = 2131624034; - - // aapt resource value: 0x7F0E0063 - public const int Base_V26_Theme_AppCompat = 2131624035; - - // aapt resource value: 0x7F0E0064 - public const int Base_V26_Theme_AppCompat_Light = 2131624036; - - // aapt resource value: 0x7F0E0065 - public const int Base_V26_Widget_AppCompat_Toolbar = 2131624037; - - // aapt resource value: 0x7F0E0066 - public const int Base_V26_Widget_Design_AppBarLayout = 2131624038; - - // aapt resource value: 0x7F0E006B - public const int Base_V7_ThemeOverlay_AppCompat_Dialog = 2131624043; - - // aapt resource value: 0x7F0E0067 - public const int Base_V7_Theme_AppCompat = 2131624039; - - // aapt resource value: 0x7F0E0068 - public const int Base_V7_Theme_AppCompat_Dialog = 2131624040; - - // aapt resource value: 0x7F0E0069 - public const int Base_V7_Theme_AppCompat_Light = 2131624041; - - // aapt resource value: 0x7F0E006A - public const int Base_V7_Theme_AppCompat_Light_Dialog = 2131624042; - - // aapt resource value: 0x7F0E006C - public const int Base_V7_Widget_AppCompat_AutoCompleteTextView = 2131624044; - - // aapt resource value: 0x7F0E006D - public const int Base_V7_Widget_AppCompat_EditText = 2131624045; - - // aapt resource value: 0x7F0E006E - public const int Base_V7_Widget_AppCompat_Toolbar = 2131624046; - - // aapt resource value: 0x7F0E006F - public const int Base_Widget_AppCompat_ActionBar = 2131624047; - - // aapt resource value: 0x7F0E0070 - public const int Base_Widget_AppCompat_ActionBar_Solid = 2131624048; - - // aapt resource value: 0x7F0E0071 - public const int Base_Widget_AppCompat_ActionBar_TabBar = 2131624049; - - // aapt resource value: 0x7F0E0072 - public const int Base_Widget_AppCompat_ActionBar_TabText = 2131624050; - - // aapt resource value: 0x7F0E0073 - public const int Base_Widget_AppCompat_ActionBar_TabView = 2131624051; - - // aapt resource value: 0x7F0E0074 - public const int Base_Widget_AppCompat_ActionButton = 2131624052; - - // aapt resource value: 0x7F0E0075 - public const int Base_Widget_AppCompat_ActionButton_CloseMode = 2131624053; - - // aapt resource value: 0x7F0E0076 - public const int Base_Widget_AppCompat_ActionButton_Overflow = 2131624054; - - // aapt resource value: 0x7F0E0077 - public const int Base_Widget_AppCompat_ActionMode = 2131624055; - - // aapt resource value: 0x7F0E0078 - public const int Base_Widget_AppCompat_ActivityChooserView = 2131624056; - - // aapt resource value: 0x7F0E0079 - public const int Base_Widget_AppCompat_AutoCompleteTextView = 2131624057; - - // aapt resource value: 0x7F0E007A - public const int Base_Widget_AppCompat_Button = 2131624058; - - // aapt resource value: 0x7F0E0080 - public const int Base_Widget_AppCompat_ButtonBar = 2131624064; - - // aapt resource value: 0x7F0E0081 - public const int Base_Widget_AppCompat_ButtonBar_AlertDialog = 2131624065; - - // aapt resource value: 0x7F0E007B - public const int Base_Widget_AppCompat_Button_Borderless = 2131624059; - - // aapt resource value: 0x7F0E007C - public const int Base_Widget_AppCompat_Button_Borderless_Colored = 2131624060; - - // aapt resource value: 0x7F0E007D - public const int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624061; - - // aapt resource value: 0x7F0E007E - public const int Base_Widget_AppCompat_Button_Colored = 2131624062; - - // aapt resource value: 0x7F0E007F - public const int Base_Widget_AppCompat_Button_Small = 2131624063; - - // aapt resource value: 0x7F0E0082 - public const int Base_Widget_AppCompat_CompoundButton_CheckBox = 2131624066; - - // aapt resource value: 0x7F0E0083 - public const int Base_Widget_AppCompat_CompoundButton_RadioButton = 2131624067; - - // aapt resource value: 0x7F0E0084 - public const int Base_Widget_AppCompat_CompoundButton_Switch = 2131624068; - - // aapt resource value: 0x7F0E0085 - public const int Base_Widget_AppCompat_DrawerArrowToggle = 2131624069; - - // aapt resource value: 0x7F0E0086 - public const int Base_Widget_AppCompat_DrawerArrowToggle_Common = 2131624070; - - // aapt resource value: 0x7F0E0087 - public const int Base_Widget_AppCompat_DropDownItem_Spinner = 2131624071; - - // aapt resource value: 0x7F0E0088 - public const int Base_Widget_AppCompat_EditText = 2131624072; - - // aapt resource value: 0x7F0E0089 - public const int Base_Widget_AppCompat_ImageButton = 2131624073; - - // aapt resource value: 0x7F0E008A - public const int Base_Widget_AppCompat_Light_ActionBar = 2131624074; - - // aapt resource value: 0x7F0E008B - public const int Base_Widget_AppCompat_Light_ActionBar_Solid = 2131624075; - - // aapt resource value: 0x7F0E008C - public const int Base_Widget_AppCompat_Light_ActionBar_TabBar = 2131624076; - - // aapt resource value: 0x7F0E008D - public const int Base_Widget_AppCompat_Light_ActionBar_TabText = 2131624077; - - // aapt resource value: 0x7F0E008E - public const int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624078; - - // aapt resource value: 0x7F0E008F - public const int Base_Widget_AppCompat_Light_ActionBar_TabView = 2131624079; - - // aapt resource value: 0x7F0E0090 - public const int Base_Widget_AppCompat_Light_PopupMenu = 2131624080; - - // aapt resource value: 0x7F0E0091 - public const int Base_Widget_AppCompat_Light_PopupMenu_Overflow = 2131624081; - - // aapt resource value: 0x7F0E0092 - public const int Base_Widget_AppCompat_ListMenuView = 2131624082; - - // aapt resource value: 0x7F0E0093 - public const int Base_Widget_AppCompat_ListPopupWindow = 2131624083; - - // aapt resource value: 0x7F0E0094 - public const int Base_Widget_AppCompat_ListView = 2131624084; - - // aapt resource value: 0x7F0E0095 - public const int Base_Widget_AppCompat_ListView_DropDown = 2131624085; - - // aapt resource value: 0x7F0E0096 - public const int Base_Widget_AppCompat_ListView_Menu = 2131624086; - - // aapt resource value: 0x7F0E0097 - public const int Base_Widget_AppCompat_PopupMenu = 2131624087; - - // aapt resource value: 0x7F0E0098 - public const int Base_Widget_AppCompat_PopupMenu_Overflow = 2131624088; - - // aapt resource value: 0x7F0E0099 - public const int Base_Widget_AppCompat_PopupWindow = 2131624089; - - // aapt resource value: 0x7F0E009A - public const int Base_Widget_AppCompat_ProgressBar = 2131624090; - - // aapt resource value: 0x7F0E009B - public const int Base_Widget_AppCompat_ProgressBar_Horizontal = 2131624091; - - // aapt resource value: 0x7F0E009C - public const int Base_Widget_AppCompat_RatingBar = 2131624092; - - // aapt resource value: 0x7F0E009D - public const int Base_Widget_AppCompat_RatingBar_Indicator = 2131624093; - - // aapt resource value: 0x7F0E009E - public const int Base_Widget_AppCompat_RatingBar_Small = 2131624094; - - // aapt resource value: 0x7F0E009F - public const int Base_Widget_AppCompat_SearchView = 2131624095; - - // aapt resource value: 0x7F0E00A0 - public const int Base_Widget_AppCompat_SearchView_ActionBar = 2131624096; - - // aapt resource value: 0x7F0E00A1 - public const int Base_Widget_AppCompat_SeekBar = 2131624097; - - // aapt resource value: 0x7F0E00A2 - public const int Base_Widget_AppCompat_SeekBar_Discrete = 2131624098; - - // aapt resource value: 0x7F0E00A3 - public const int Base_Widget_AppCompat_Spinner = 2131624099; - - // aapt resource value: 0x7F0E00A4 - public const int Base_Widget_AppCompat_Spinner_Underlined = 2131624100; - - // aapt resource value: 0x7F0E00A5 - public const int Base_Widget_AppCompat_TextView_SpinnerItem = 2131624101; - - // aapt resource value: 0x7F0E00A6 - public const int Base_Widget_AppCompat_Toolbar = 2131624102; - - // aapt resource value: 0x7F0E00A7 - public const int Base_Widget_AppCompat_Toolbar_Button_Navigation = 2131624103; - - // aapt resource value: 0x7F0E00A8 - public const int Base_Widget_Design_AppBarLayout = 2131624104; - - // aapt resource value: 0x7F0E00A9 - public const int Base_Widget_Design_TabLayout = 2131624105; - - // aapt resource value: 0x7F0E00AA - public const int CardView = 2131624106; - - // aapt resource value: 0x7F0E00AB - public const int CardView_Dark = 2131624107; - - // aapt resource value: 0x7F0E00AC - public const int CardView_Light = 2131624108; - - // aapt resource value: 0x7F0E00AD - public const int Platform_AppCompat = 2131624109; - - // aapt resource value: 0x7F0E00AE - public const int Platform_AppCompat_Light = 2131624110; - - // aapt resource value: 0x7F0E00AF - public const int Platform_ThemeOverlay_AppCompat = 2131624111; - - // aapt resource value: 0x7F0E00B0 - public const int Platform_ThemeOverlay_AppCompat_Dark = 2131624112; - - // aapt resource value: 0x7F0E00B1 - public const int Platform_ThemeOverlay_AppCompat_Light = 2131624113; - - // aapt resource value: 0x7F0E00B2 - public const int Platform_V11_AppCompat = 2131624114; - - // aapt resource value: 0x7F0E00B3 - public const int Platform_V11_AppCompat_Light = 2131624115; - - // aapt resource value: 0x7F0E00B4 - public const int Platform_V14_AppCompat = 2131624116; - - // aapt resource value: 0x7F0E00B5 - public const int Platform_V14_AppCompat_Light = 2131624117; - - // aapt resource value: 0x7F0E00B6 - public const int Platform_V21_AppCompat = 2131624118; - - // aapt resource value: 0x7F0E00B7 - public const int Platform_V21_AppCompat_Light = 2131624119; - - // aapt resource value: 0x7F0E00B8 - public const int Platform_V25_AppCompat = 2131624120; - - // aapt resource value: 0x7F0E00B9 - public const int Platform_V25_AppCompat_Light = 2131624121; - - // aapt resource value: 0x7F0E00BA - public const int Platform_Widget_AppCompat_Spinner = 2131624122; - - // aapt resource value: 0x7F0E00BB - public const int RtlOverlay_DialogWindowTitle_AppCompat = 2131624123; - - // aapt resource value: 0x7F0E00BC - public const int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem = 2131624124; - - // aapt resource value: 0x7F0E00BD - public const int RtlOverlay_Widget_AppCompat_DialogTitle_Icon = 2131624125; - - // aapt resource value: 0x7F0E00BE - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem = 2131624126; - - // aapt resource value: 0x7F0E00BF - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup = 2131624127; - - // aapt resource value: 0x7F0E00C0 - public const int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text = 2131624128; - - // aapt resource value: 0x7F0E00C6 - public const int RtlOverlay_Widget_AppCompat_SearchView_MagIcon = 2131624134; - - // aapt resource value: 0x7F0E00C1 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown = 2131624129; - - // aapt resource value: 0x7F0E00C2 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1 = 2131624130; - - // aapt resource value: 0x7F0E00C3 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2 = 2131624131; - - // aapt resource value: 0x7F0E00C4 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Query = 2131624132; - - // aapt resource value: 0x7F0E00C5 - public const int RtlOverlay_Widget_AppCompat_Search_DropDown_Text = 2131624133; - - // aapt resource value: 0x7F0E00C7 - public const int RtlUnderlay_Widget_AppCompat_ActionButton = 2131624135; - - // aapt resource value: 0x7F0E00C8 - public const int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow = 2131624136; - - // aapt resource value: 0x7F0E00C9 - public const int TextAppearance_AppCompat = 2131624137; - - // aapt resource value: 0x7F0E00CA - public const int TextAppearance_AppCompat_Body1 = 2131624138; - - // aapt resource value: 0x7F0E00CB - public const int TextAppearance_AppCompat_Body2 = 2131624139; - - // aapt resource value: 0x7F0E00CC - public const int TextAppearance_AppCompat_Button = 2131624140; - - // aapt resource value: 0x7F0E00CD - public const int TextAppearance_AppCompat_Caption = 2131624141; - - // aapt resource value: 0x7F0E00CE - public const int TextAppearance_AppCompat_Display1 = 2131624142; - - // aapt resource value: 0x7F0E00CF - public const int TextAppearance_AppCompat_Display2 = 2131624143; - - // aapt resource value: 0x7F0E00D0 - public const int TextAppearance_AppCompat_Display3 = 2131624144; - - // aapt resource value: 0x7F0E00D1 - public const int TextAppearance_AppCompat_Display4 = 2131624145; - - // aapt resource value: 0x7F0E00D2 - public const int TextAppearance_AppCompat_Headline = 2131624146; - - // aapt resource value: 0x7F0E00D3 - public const int TextAppearance_AppCompat_Inverse = 2131624147; - - // aapt resource value: 0x7F0E00D4 - public const int TextAppearance_AppCompat_Large = 2131624148; - - // aapt resource value: 0x7F0E00D5 - public const int TextAppearance_AppCompat_Large_Inverse = 2131624149; - - // aapt resource value: 0x7F0E00D6 - public const int TextAppearance_AppCompat_Light_SearchResult_Subtitle = 2131624150; - - // aapt resource value: 0x7F0E00D7 - public const int TextAppearance_AppCompat_Light_SearchResult_Title = 2131624151; - - // aapt resource value: 0x7F0E00D8 - public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large = 2131624152; - - // aapt resource value: 0x7F0E00D9 - public const int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small = 2131624153; - - // aapt resource value: 0x7F0E00DA - public const int TextAppearance_AppCompat_Medium = 2131624154; - - // aapt resource value: 0x7F0E00DB - public const int TextAppearance_AppCompat_Medium_Inverse = 2131624155; - - // aapt resource value: 0x7F0E00DC - public const int TextAppearance_AppCompat_Menu = 2131624156; - - // aapt resource value: 0x7F0E00DD - public const int TextAppearance_AppCompat_SearchResult_Subtitle = 2131624157; - - // aapt resource value: 0x7F0E00DE - public const int TextAppearance_AppCompat_SearchResult_Title = 2131624158; - - // aapt resource value: 0x7F0E00DF - public const int TextAppearance_AppCompat_Small = 2131624159; - - // aapt resource value: 0x7F0E00E0 - public const int TextAppearance_AppCompat_Small_Inverse = 2131624160; - - // aapt resource value: 0x7F0E00E1 - public const int TextAppearance_AppCompat_Subhead = 2131624161; - - // aapt resource value: 0x7F0E00E2 - public const int TextAppearance_AppCompat_Subhead_Inverse = 2131624162; - - // aapt resource value: 0x7F0E00E3 - public const int TextAppearance_AppCompat_Title = 2131624163; - - // aapt resource value: 0x7F0E00E4 - public const int TextAppearance_AppCompat_Title_Inverse = 2131624164; - - // aapt resource value: 0x7F0E00E5 - public const int TextAppearance_AppCompat_Tooltip = 2131624165; - - // aapt resource value: 0x7F0E00E6 - public const int TextAppearance_AppCompat_Widget_ActionBar_Menu = 2131624166; - - // aapt resource value: 0x7F0E00E7 - public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle = 2131624167; - - // aapt resource value: 0x7F0E00E8 - public const int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse = 2131624168; - - // aapt resource value: 0x7F0E00E9 - public const int TextAppearance_AppCompat_Widget_ActionBar_Title = 2131624169; - - // aapt resource value: 0x7F0E00EA - public const int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse = 2131624170; - - // aapt resource value: 0x7F0E00EB - public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle = 2131624171; - - // aapt resource value: 0x7F0E00EC - public const int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse = 2131624172; - - // aapt resource value: 0x7F0E00ED - public const int TextAppearance_AppCompat_Widget_ActionMode_Title = 2131624173; - - // aapt resource value: 0x7F0E00EE - public const int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse = 2131624174; - - // aapt resource value: 0x7F0E00EF - public const int TextAppearance_AppCompat_Widget_Button = 2131624175; - - // aapt resource value: 0x7F0E00F0 - public const int TextAppearance_AppCompat_Widget_Button_Borderless_Colored = 2131624176; - - // aapt resource value: 0x7F0E00F1 - public const int TextAppearance_AppCompat_Widget_Button_Colored = 2131624177; - - // aapt resource value: 0x7F0E00F2 - public const int TextAppearance_AppCompat_Widget_Button_Inverse = 2131624178; - - // aapt resource value: 0x7F0E00F3 - public const int TextAppearance_AppCompat_Widget_DropDownItem = 2131624179; - - // aapt resource value: 0x7F0E00F4 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Header = 2131624180; - - // aapt resource value: 0x7F0E00F5 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Large = 2131624181; - - // aapt resource value: 0x7F0E00F6 - public const int TextAppearance_AppCompat_Widget_PopupMenu_Small = 2131624182; - - // aapt resource value: 0x7F0E00F7 - public const int TextAppearance_AppCompat_Widget_Switch = 2131624183; - - // aapt resource value: 0x7F0E00F8 - public const int TextAppearance_AppCompat_Widget_TextView_SpinnerItem = 2131624184; - - // aapt resource value: 0x7F0E00F9 - public const int TextAppearance_Compat_Notification = 2131624185; - - // aapt resource value: 0x7F0E00FA - public const int TextAppearance_Compat_Notification_Info = 2131624186; - - // aapt resource value: 0x7F0E00FB - public const int TextAppearance_Compat_Notification_Info_Media = 2131624187; - - // aapt resource value: 0x7F0E00FC - public const int TextAppearance_Compat_Notification_Line2 = 2131624188; - - // aapt resource value: 0x7F0E00FD - public const int TextAppearance_Compat_Notification_Line2_Media = 2131624189; - - // aapt resource value: 0x7F0E00FE - public const int TextAppearance_Compat_Notification_Media = 2131624190; - - // aapt resource value: 0x7F0E00FF - public const int TextAppearance_Compat_Notification_Time = 2131624191; - - // aapt resource value: 0x7F0E0100 - public const int TextAppearance_Compat_Notification_Time_Media = 2131624192; - - // aapt resource value: 0x7F0E0101 - public const int TextAppearance_Compat_Notification_Title = 2131624193; - - // aapt resource value: 0x7F0E0102 - public const int TextAppearance_Compat_Notification_Title_Media = 2131624194; - - // aapt resource value: 0x7F0E0103 - public const int TextAppearance_Design_CollapsingToolbar_Expanded = 2131624195; - - // aapt resource value: 0x7F0E0104 - public const int TextAppearance_Design_Counter = 2131624196; - - // aapt resource value: 0x7F0E0105 - public const int TextAppearance_Design_Counter_Overflow = 2131624197; - - // aapt resource value: 0x7F0E0106 - public const int TextAppearance_Design_Error = 2131624198; - - // aapt resource value: 0x7F0E0107 - public const int TextAppearance_Design_Hint = 2131624199; - - // aapt resource value: 0x7F0E0108 - public const int TextAppearance_Design_Snackbar_Message = 2131624200; - - // aapt resource value: 0x7F0E0109 - public const int TextAppearance_Design_Tab = 2131624201; - - // aapt resource value: 0x7F0E010A - public const int TextAppearance_MediaRouter_PrimaryText = 2131624202; - - // aapt resource value: 0x7F0E010B - public const int TextAppearance_MediaRouter_SecondaryText = 2131624203; - - // aapt resource value: 0x7F0E010C - public const int TextAppearance_MediaRouter_Title = 2131624204; - - // aapt resource value: 0x7F0E010D - public const int TextAppearance_Widget_AppCompat_ExpandedMenu_Item = 2131624205; - - // aapt resource value: 0x7F0E010E - public const int TextAppearance_Widget_AppCompat_Toolbar_Subtitle = 2131624206; - - // aapt resource value: 0x7F0E010F - public const int TextAppearance_Widget_AppCompat_Toolbar_Title = 2131624207; - - // aapt resource value: 0x7F0E012F - public const int ThemeOverlay_AppCompat = 2131624239; - - // aapt resource value: 0x7F0E0130 - public const int ThemeOverlay_AppCompat_ActionBar = 2131624240; - - // aapt resource value: 0x7F0E0131 - public const int ThemeOverlay_AppCompat_Dark = 2131624241; - - // aapt resource value: 0x7F0E0132 - public const int ThemeOverlay_AppCompat_Dark_ActionBar = 2131624242; - - // aapt resource value: 0x7F0E0133 - public const int ThemeOverlay_AppCompat_Dialog = 2131624243; - - // aapt resource value: 0x7F0E0134 - public const int ThemeOverlay_AppCompat_Dialog_Alert = 2131624244; - - // aapt resource value: 0x7F0E0135 - public const int ThemeOverlay_AppCompat_Light = 2131624245; - - // aapt resource value: 0x7F0E0136 - public const int ThemeOverlay_MediaRouter_Dark = 2131624246; - - // aapt resource value: 0x7F0E0137 - public const int ThemeOverlay_MediaRouter_Light = 2131624247; - - // aapt resource value: 0x7F0E0110 - public const int Theme_AppCompat = 2131624208; - - // aapt resource value: 0x7F0E0111 - public const int Theme_AppCompat_CompactMenu = 2131624209; - - // aapt resource value: 0x7F0E0112 - public const int Theme_AppCompat_DayNight = 2131624210; - - // aapt resource value: 0x7F0E0113 - public const int Theme_AppCompat_DayNight_DarkActionBar = 2131624211; - - // aapt resource value: 0x7F0E0114 - public const int Theme_AppCompat_DayNight_Dialog = 2131624212; - - // aapt resource value: 0x7F0E0117 - public const int Theme_AppCompat_DayNight_DialogWhenLarge = 2131624215; - - // aapt resource value: 0x7F0E0115 - public const int Theme_AppCompat_DayNight_Dialog_Alert = 2131624213; - - // aapt resource value: 0x7F0E0116 - public const int Theme_AppCompat_DayNight_Dialog_MinWidth = 2131624214; - - // aapt resource value: 0x7F0E0118 - public const int Theme_AppCompat_DayNight_NoActionBar = 2131624216; - - // aapt resource value: 0x7F0E0119 - public const int Theme_AppCompat_Dialog = 2131624217; - - // aapt resource value: 0x7F0E011C - public const int Theme_AppCompat_DialogWhenLarge = 2131624220; - - // aapt resource value: 0x7F0E011A - public const int Theme_AppCompat_Dialog_Alert = 2131624218; - - // aapt resource value: 0x7F0E011B - public const int Theme_AppCompat_Dialog_MinWidth = 2131624219; - - // aapt resource value: 0x7F0E011D - public const int Theme_AppCompat_Light = 2131624221; - - // aapt resource value: 0x7F0E011E - public const int Theme_AppCompat_Light_DarkActionBar = 2131624222; - - // aapt resource value: 0x7F0E011F - public const int Theme_AppCompat_Light_Dialog = 2131624223; - - // aapt resource value: 0x7F0E0122 - public const int Theme_AppCompat_Light_DialogWhenLarge = 2131624226; - - // aapt resource value: 0x7F0E0120 - public const int Theme_AppCompat_Light_Dialog_Alert = 2131624224; - - // aapt resource value: 0x7F0E0121 - public const int Theme_AppCompat_Light_Dialog_MinWidth = 2131624225; - - // aapt resource value: 0x7F0E0123 - public const int Theme_AppCompat_Light_NoActionBar = 2131624227; - - // aapt resource value: 0x7F0E0124 - public const int Theme_AppCompat_NoActionBar = 2131624228; - - // aapt resource value: 0x7F0E0125 - public const int Theme_Design = 2131624229; - - // aapt resource value: 0x7F0E0126 - public const int Theme_Design_BottomSheetDialog = 2131624230; - - // aapt resource value: 0x7F0E0127 - public const int Theme_Design_Light = 2131624231; - - // aapt resource value: 0x7F0E0128 - public const int Theme_Design_Light_BottomSheetDialog = 2131624232; - - // aapt resource value: 0x7F0E0129 - public const int Theme_Design_Light_NoActionBar = 2131624233; - - // aapt resource value: 0x7F0E012A - public const int Theme_Design_NoActionBar = 2131624234; - - // aapt resource value: 0x7F0E012B - public const int Theme_MediaRouter = 2131624235; - - // aapt resource value: 0x7F0E012C - public const int Theme_MediaRouter_Light = 2131624236; - - // aapt resource value: 0x7F0E012E - public const int Theme_MediaRouter_LightControlPanel = 2131624238; - - // aapt resource value: 0x7F0E012D - public const int Theme_MediaRouter_Light_DarkControlPanel = 2131624237; - - // aapt resource value: 0x7F0E0138 - public const int Widget_AppCompat_ActionBar = 2131624248; - - // aapt resource value: 0x7F0E0139 - public const int Widget_AppCompat_ActionBar_Solid = 2131624249; - - // aapt resource value: 0x7F0E013A - public const int Widget_AppCompat_ActionBar_TabBar = 2131624250; - - // aapt resource value: 0x7F0E013B - public const int Widget_AppCompat_ActionBar_TabText = 2131624251; - - // aapt resource value: 0x7F0E013C - public const int Widget_AppCompat_ActionBar_TabView = 2131624252; - - // aapt resource value: 0x7F0E013D - public const int Widget_AppCompat_ActionButton = 2131624253; - - // aapt resource value: 0x7F0E013E - public const int Widget_AppCompat_ActionButton_CloseMode = 2131624254; - - // aapt resource value: 0x7F0E013F - public const int Widget_AppCompat_ActionButton_Overflow = 2131624255; - - // aapt resource value: 0x7F0E0140 - public const int Widget_AppCompat_ActionMode = 2131624256; - - // aapt resource value: 0x7F0E0141 - public const int Widget_AppCompat_ActivityChooserView = 2131624257; - - // aapt resource value: 0x7F0E0142 - public const int Widget_AppCompat_AutoCompleteTextView = 2131624258; - - // aapt resource value: 0x7F0E0143 - public const int Widget_AppCompat_Button = 2131624259; - - // aapt resource value: 0x7F0E0149 - public const int Widget_AppCompat_ButtonBar = 2131624265; - - // aapt resource value: 0x7F0E014A - public const int Widget_AppCompat_ButtonBar_AlertDialog = 2131624266; - - // aapt resource value: 0x7F0E0144 - public const int Widget_AppCompat_Button_Borderless = 2131624260; - - // aapt resource value: 0x7F0E0145 - public const int Widget_AppCompat_Button_Borderless_Colored = 2131624261; - - // aapt resource value: 0x7F0E0146 - public const int Widget_AppCompat_Button_ButtonBar_AlertDialog = 2131624262; - - // aapt resource value: 0x7F0E0147 - public const int Widget_AppCompat_Button_Colored = 2131624263; - - // aapt resource value: 0x7F0E0148 - public const int Widget_AppCompat_Button_Small = 2131624264; - - // aapt resource value: 0x7F0E014B - public const int Widget_AppCompat_CompoundButton_CheckBox = 2131624267; - - // aapt resource value: 0x7F0E014C - public const int Widget_AppCompat_CompoundButton_RadioButton = 2131624268; - - // aapt resource value: 0x7F0E014D - public const int Widget_AppCompat_CompoundButton_Switch = 2131624269; - - // aapt resource value: 0x7F0E014E - public const int Widget_AppCompat_DrawerArrowToggle = 2131624270; - - // aapt resource value: 0x7F0E014F - public const int Widget_AppCompat_DropDownItem_Spinner = 2131624271; - - // aapt resource value: 0x7F0E0150 - public const int Widget_AppCompat_EditText = 2131624272; - - // aapt resource value: 0x7F0E0151 - public const int Widget_AppCompat_ImageButton = 2131624273; - - // aapt resource value: 0x7F0E0152 - public const int Widget_AppCompat_Light_ActionBar = 2131624274; - - // aapt resource value: 0x7F0E0153 - public const int Widget_AppCompat_Light_ActionBar_Solid = 2131624275; - - // aapt resource value: 0x7F0E0154 - public const int Widget_AppCompat_Light_ActionBar_Solid_Inverse = 2131624276; - - // aapt resource value: 0x7F0E0155 - public const int Widget_AppCompat_Light_ActionBar_TabBar = 2131624277; - - // aapt resource value: 0x7F0E0156 - public const int Widget_AppCompat_Light_ActionBar_TabBar_Inverse = 2131624278; - - // aapt resource value: 0x7F0E0157 - public const int Widget_AppCompat_Light_ActionBar_TabText = 2131624279; - - // aapt resource value: 0x7F0E0158 - public const int Widget_AppCompat_Light_ActionBar_TabText_Inverse = 2131624280; - - // aapt resource value: 0x7F0E0159 - public const int Widget_AppCompat_Light_ActionBar_TabView = 2131624281; - - // aapt resource value: 0x7F0E015A - public const int Widget_AppCompat_Light_ActionBar_TabView_Inverse = 2131624282; - - // aapt resource value: 0x7F0E015B - public const int Widget_AppCompat_Light_ActionButton = 2131624283; - - // aapt resource value: 0x7F0E015C - public const int Widget_AppCompat_Light_ActionButton_CloseMode = 2131624284; - - // aapt resource value: 0x7F0E015D - public const int Widget_AppCompat_Light_ActionButton_Overflow = 2131624285; - - // aapt resource value: 0x7F0E015E - public const int Widget_AppCompat_Light_ActionMode_Inverse = 2131624286; - - // aapt resource value: 0x7F0E015F - public const int Widget_AppCompat_Light_ActivityChooserView = 2131624287; - - // aapt resource value: 0x7F0E0160 - public const int Widget_AppCompat_Light_AutoCompleteTextView = 2131624288; - - // aapt resource value: 0x7F0E0161 - public const int Widget_AppCompat_Light_DropDownItem_Spinner = 2131624289; - - // aapt resource value: 0x7F0E0162 - public const int Widget_AppCompat_Light_ListPopupWindow = 2131624290; - - // aapt resource value: 0x7F0E0163 - public const int Widget_AppCompat_Light_ListView_DropDown = 2131624291; - - // aapt resource value: 0x7F0E0164 - public const int Widget_AppCompat_Light_PopupMenu = 2131624292; - - // aapt resource value: 0x7F0E0165 - public const int Widget_AppCompat_Light_PopupMenu_Overflow = 2131624293; - - // aapt resource value: 0x7F0E0166 - public const int Widget_AppCompat_Light_SearchView = 2131624294; - - // aapt resource value: 0x7F0E0167 - public const int Widget_AppCompat_Light_Spinner_DropDown_ActionBar = 2131624295; - - // aapt resource value: 0x7F0E0168 - public const int Widget_AppCompat_ListMenuView = 2131624296; - - // aapt resource value: 0x7F0E0169 - public const int Widget_AppCompat_ListPopupWindow = 2131624297; - - // aapt resource value: 0x7F0E016A - public const int Widget_AppCompat_ListView = 2131624298; - - // aapt resource value: 0x7F0E016B - public const int Widget_AppCompat_ListView_DropDown = 2131624299; - - // aapt resource value: 0x7F0E016C - public const int Widget_AppCompat_ListView_Menu = 2131624300; - - // aapt resource value: 0x7F0E016D - public const int Widget_AppCompat_PopupMenu = 2131624301; - - // aapt resource value: 0x7F0E016E - public const int Widget_AppCompat_PopupMenu_Overflow = 2131624302; - - // aapt resource value: 0x7F0E016F - public const int Widget_AppCompat_PopupWindow = 2131624303; - - // aapt resource value: 0x7F0E0170 - public const int Widget_AppCompat_ProgressBar = 2131624304; - - // aapt resource value: 0x7F0E0171 - public const int Widget_AppCompat_ProgressBar_Horizontal = 2131624305; - - // aapt resource value: 0x7F0E0172 - public const int Widget_AppCompat_RatingBar = 2131624306; - - // aapt resource value: 0x7F0E0173 - public const int Widget_AppCompat_RatingBar_Indicator = 2131624307; - - // aapt resource value: 0x7F0E0174 - public const int Widget_AppCompat_RatingBar_Small = 2131624308; - - // aapt resource value: 0x7F0E0175 - public const int Widget_AppCompat_SearchView = 2131624309; - - // aapt resource value: 0x7F0E0176 - public const int Widget_AppCompat_SearchView_ActionBar = 2131624310; - - // aapt resource value: 0x7F0E0177 - public const int Widget_AppCompat_SeekBar = 2131624311; - - // aapt resource value: 0x7F0E0178 - public const int Widget_AppCompat_SeekBar_Discrete = 2131624312; - - // aapt resource value: 0x7F0E0179 - public const int Widget_AppCompat_Spinner = 2131624313; - - // aapt resource value: 0x7F0E017A - public const int Widget_AppCompat_Spinner_DropDown = 2131624314; - - // aapt resource value: 0x7F0E017B - public const int Widget_AppCompat_Spinner_DropDown_ActionBar = 2131624315; - - // aapt resource value: 0x7F0E017C - public const int Widget_AppCompat_Spinner_Underlined = 2131624316; - - // aapt resource value: 0x7F0E017D - public const int Widget_AppCompat_TextView_SpinnerItem = 2131624317; - - // aapt resource value: 0x7F0E017E - public const int Widget_AppCompat_Toolbar = 2131624318; - - // aapt resource value: 0x7F0E017F - public const int Widget_AppCompat_Toolbar_Button_Navigation = 2131624319; - - // aapt resource value: 0x7F0E0180 - public const int Widget_Compat_NotificationActionContainer = 2131624320; - - // aapt resource value: 0x7F0E0181 - public const int Widget_Compat_NotificationActionText = 2131624321; - - // aapt resource value: 0x7F0E0182 - public const int Widget_Design_AppBarLayout = 2131624322; - - // aapt resource value: 0x7F0E0183 - public const int Widget_Design_BottomNavigationView = 2131624323; - - // aapt resource value: 0x7F0E0184 - public const int Widget_Design_BottomSheet_Modal = 2131624324; - - // aapt resource value: 0x7F0E0185 - public const int Widget_Design_CollapsingToolbar = 2131624325; - - // aapt resource value: 0x7F0E0186 - public const int Widget_Design_CoordinatorLayout = 2131624326; - - // aapt resource value: 0x7F0E0187 - public const int Widget_Design_FloatingActionButton = 2131624327; - - // aapt resource value: 0x7F0E0188 - public const int Widget_Design_NavigationView = 2131624328; - - // aapt resource value: 0x7F0E0189 - public const int Widget_Design_ScrimInsetsFrameLayout = 2131624329; - - // aapt resource value: 0x7F0E018A - public const int Widget_Design_Snackbar = 2131624330; - - // aapt resource value: 0x7F0E018B - public const int Widget_Design_TabLayout = 2131624331; - - // aapt resource value: 0x7F0E018C - public const int Widget_Design_TextInputLayout = 2131624332; - - // aapt resource value: 0x7F0E018D - public const int Widget_MediaRouter_Light_MediaRouteButton = 2131624333; - - // aapt resource value: 0x7F0E018E - public const int Widget_MediaRouter_MediaRouteButton = 2131624334; - - static Style() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Style() - { - } - } - - public partial class Styleable - { - - // aapt resource value: { 0x7F030031,0x7F030032,0x7F030033,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F030077,0x7F03007B,0x7F03007C,0x7F030087,0x7F0300A8,0x7F0300A9,0x7F0300AD,0x7F0300AE,0x7F0300AF,0x7F0300B4,0x7F0300BA,0x7F0300D5,0x7F0300EB,0x7F0300FB,0x7F0300FF,0x7F030100,0x7F030124,0x7F030127,0x7F030153,0x7F03015D } - public static int[] ActionBar = new int[] { - 2130903089, - 2130903090, - 2130903091, - 2130903142, - 2130903143, - 2130903144, - 2130903145, - 2130903146, - 2130903147, - 2130903159, - 2130903163, - 2130903164, - 2130903175, - 2130903208, - 2130903209, - 2130903213, - 2130903214, - 2130903215, - 2130903220, - 2130903226, - 2130903253, - 2130903275, - 2130903291, - 2130903295, - 2130903296, - 2130903332, - 2130903335, - 2130903379, - 2130903389}; - - // aapt resource value: { 0x10100B3 } - public static int[] ActionBarLayout = new int[] { - 16842931}; - - // aapt resource value: 0 - public const int ActionBarLayout_android_layout_gravity = 0; - - // aapt resource value: 0 - public const int ActionBar_background = 0; - - // aapt resource value: 1 - public const int ActionBar_backgroundSplit = 1; - - // aapt resource value: 2 - public const int ActionBar_backgroundStacked = 2; - - // aapt resource value: 3 - public const int ActionBar_contentInsetEnd = 3; - - // aapt resource value: 4 - public const int ActionBar_contentInsetEndWithActions = 4; - - // aapt resource value: 5 - public const int ActionBar_contentInsetLeft = 5; - - // aapt resource value: 6 - public const int ActionBar_contentInsetRight = 6; - - // aapt resource value: 7 - public const int ActionBar_contentInsetStart = 7; - - // aapt resource value: 8 - public const int ActionBar_contentInsetStartWithNavigation = 8; - - // aapt resource value: 9 - public const int ActionBar_customNavigationLayout = 9; - - // aapt resource value: 10 - public const int ActionBar_displayOptions = 10; - - // aapt resource value: 11 - public const int ActionBar_divider = 11; - - // aapt resource value: 12 - public const int ActionBar_elevation = 12; - - // aapt resource value: 13 - public const int ActionBar_height = 13; - - // aapt resource value: 14 - public const int ActionBar_hideOnContentScroll = 14; - - // aapt resource value: 15 - public const int ActionBar_homeAsUpIndicator = 15; - - // aapt resource value: 16 - public const int ActionBar_homeLayout = 16; - - // aapt resource value: 17 - public const int ActionBar_icon = 17; - - // aapt resource value: 18 - public const int ActionBar_indeterminateProgressStyle = 18; - - // aapt resource value: 19 - public const int ActionBar_itemPadding = 19; - - // aapt resource value: 20 - public const int ActionBar_logo = 20; - - // aapt resource value: 21 - public const int ActionBar_navigationMode = 21; - - // aapt resource value: 22 - public const int ActionBar_popupTheme = 22; - - // aapt resource value: 23 - public const int ActionBar_progressBarPadding = 23; - - // aapt resource value: 24 - public const int ActionBar_progressBarStyle = 24; - - // aapt resource value: 25 - public const int ActionBar_subtitle = 25; - - // aapt resource value: 26 - public const int ActionBar_subtitleTextStyle = 26; - - // aapt resource value: 27 - public const int ActionBar_title = 27; - - // aapt resource value: 28 - public const int ActionBar_titleTextStyle = 28; - - // aapt resource value: { 0x101013F } - public static int[] ActionMenuItemView = new int[] { - 16843071}; - - // aapt resource value: 0 - public const int ActionMenuItemView_android_minWidth = 0; - - // aapt resource value: { 0xFFFFFFFF } - public static int[] ActionMenuView = new int[] { - -1}; - - // aapt resource value: { 0x7F030031,0x7F030032,0x7F030054,0x7F0300A8,0x7F030127,0x7F03015D } - public static int[] ActionMode = new int[] { - 2130903089, - 2130903090, - 2130903124, - 2130903208, - 2130903335, - 2130903389}; - - // aapt resource value: 0 - public const int ActionMode_background = 0; - - // aapt resource value: 1 - public const int ActionMode_backgroundSplit = 1; - - // aapt resource value: 2 - public const int ActionMode_closeItemLayout = 2; - - // aapt resource value: 3 - public const int ActionMode_height = 3; - - // aapt resource value: 4 - public const int ActionMode_subtitleTextStyle = 4; - - // aapt resource value: 5 - public const int ActionMode_titleTextStyle = 5; - - // aapt resource value: { 0x7F03008A,0x7F0300B5 } - public static int[] ActivityChooserView = new int[] { - 2130903178, - 2130903221}; - - // aapt resource value: 0 - public const int ActivityChooserView_expandActivityOverflowButtonDrawable = 0; - - // aapt resource value: 1 - public const int ActivityChooserView_initialActivityCount = 1; - - // aapt resource value: { 0x10100F2,0x7F030046,0x7F0300CC,0x7F0300CD,0x7F0300E8,0x7F030114,0x7F030115 } - public static int[] AlertDialog = new int[] { - 16842994, - 2130903110, - 2130903244, - 2130903245, - 2130903272, - 2130903316, - 2130903317}; - - // aapt resource value: 0 - public const int AlertDialog_android_layout = 0; - - // aapt resource value: 1 - public const int AlertDialog_buttonPanelSideLayout = 1; - - // aapt resource value: 2 - public const int AlertDialog_listItemLayout = 2; - - // aapt resource value: 3 - public const int AlertDialog_listLayout = 3; - - // aapt resource value: 4 - public const int AlertDialog_multiChoiceItemLayout = 4; - - // aapt resource value: 5 - public const int AlertDialog_showTitle = 5; - - // aapt resource value: 6 - public const int AlertDialog_singleChoiceItemLayout = 6; - - // aapt resource value: { 0x10100D4,0x101048F,0x1010540,0x7F030087,0x7F03008B } - public static int[] AppBarLayout = new int[] { - 16842964, - 16843919, - 16844096, - 2130903175, - 2130903179}; - - // aapt resource value: { 0x7F03011E,0x7F03011F } - public static int[] AppBarLayoutStates = new int[] { - 2130903326, - 2130903327}; - - // aapt resource value: 0 - public const int AppBarLayoutStates_state_collapsed = 0; - - // aapt resource value: 1 - public const int AppBarLayoutStates_state_collapsible = 1; - - // aapt resource value: 0 - public const int AppBarLayout_android_background = 0; - - // aapt resource value: 2 - public const int AppBarLayout_android_keyboardNavigationCluster = 2; - - // aapt resource value: 1 - public const int AppBarLayout_android_touchscreenBlocksFocus = 1; - - // aapt resource value: 3 - public const int AppBarLayout_elevation = 3; - - // aapt resource value: 4 - public const int AppBarLayout_expanded = 4; - - // aapt resource value: { 0x7F0300C8,0x7F0300C9 } - public static int[] AppBarLayout_Layout = new int[] { - 2130903240, - 2130903241}; - - // aapt resource value: 0 - public const int AppBarLayout_Layout_layout_scrollFlags = 0; - - // aapt resource value: 1 - public const int AppBarLayout_Layout_layout_scrollInterpolator = 1; - - // aapt resource value: { 0x1010119,0x7F03011B,0x7F030151,0x7F030152 } - public static int[] AppCompatImageView = new int[] { - 16843033, - 2130903323, - 2130903377, - 2130903378}; - - // aapt resource value: 0 - public const int AppCompatImageView_android_src = 0; - - // aapt resource value: 1 - public const int AppCompatImageView_srcCompat = 1; - - // aapt resource value: 2 - public const int AppCompatImageView_tint = 2; - - // aapt resource value: 3 - public const int AppCompatImageView_tintMode = 3; - - // aapt resource value: { 0x1010142,0x7F03014E,0x7F03014F,0x7F030150 } - public static int[] AppCompatSeekBar = new int[] { - 16843074, - 2130903374, - 2130903375, - 2130903376}; - - // aapt resource value: 0 - public const int AppCompatSeekBar_android_thumb = 0; - - // aapt resource value: 1 - public const int AppCompatSeekBar_tickMark = 1; - - // aapt resource value: 2 - public const int AppCompatSeekBar_tickMarkTint = 2; - - // aapt resource value: 3 - public const int AppCompatSeekBar_tickMarkTintMode = 3; - - // aapt resource value: { 0x1010034,0x101016D,0x101016E,0x101016F,0x1010170,0x1010392,0x1010393 } - public static int[] AppCompatTextHelper = new int[] { - 16842804, - 16843117, - 16843118, - 16843119, - 16843120, - 16843666, - 16843667}; - - // aapt resource value: 2 - public const int AppCompatTextHelper_android_drawableBottom = 2; - - // aapt resource value: 6 - public const int AppCompatTextHelper_android_drawableEnd = 6; - - // aapt resource value: 3 - public const int AppCompatTextHelper_android_drawableLeft = 3; - - // aapt resource value: 4 - public const int AppCompatTextHelper_android_drawableRight = 4; - - // aapt resource value: 5 - public const int AppCompatTextHelper_android_drawableStart = 5; - - // aapt resource value: 1 - public const int AppCompatTextHelper_android_drawableTop = 1; - - // aapt resource value: 0 - public const int AppCompatTextHelper_android_textAppearance = 0; - - // aapt resource value: { 0x1010034,0x7F03002C,0x7F03002D,0x7F03002E,0x7F03002F,0x7F030030,0x7F03009B,0x7F03013D } - public static int[] AppCompatTextView = new int[] { - 16842804, - 2130903084, - 2130903085, - 2130903086, - 2130903087, - 2130903088, - 2130903195, - 2130903357}; - - // aapt resource value: 0 - public const int AppCompatTextView_android_textAppearance = 0; - - // aapt resource value: 1 - public const int AppCompatTextView_autoSizeMaxTextSize = 1; - - // aapt resource value: 2 - public const int AppCompatTextView_autoSizeMinTextSize = 2; - - // aapt resource value: 3 - public const int AppCompatTextView_autoSizePresetSizes = 3; - - // aapt resource value: 4 - public const int AppCompatTextView_autoSizeStepGranularity = 4; - - // aapt resource value: 5 - public const int AppCompatTextView_autoSizeTextType = 5; - - // aapt resource value: 6 - public const int AppCompatTextView_fontFamily = 6; - - // aapt resource value: 7 - public const int AppCompatTextView_textAllCaps = 7; - - // aapt resource value: { 0x1010057,0x10100AE,0x7F030000,0x7F030001,0x7F030002,0x7F030003,0x7F030004,0x7F030005,0x7F030006,0x7F030007,0x7F030008,0x7F030009,0x7F03000A,0x7F03000B,0x7F03000C,0x7F03000E,0x7F03000F,0x7F030010,0x7F030011,0x7F030012,0x7F030013,0x7F030014,0x7F030015,0x7F030016,0x7F030017,0x7F030018,0x7F030019,0x7F03001A,0x7F03001B,0x7F03001C,0x7F03001D,0x7F03001E,0x7F030021,0x7F030022,0x7F030023,0x7F030024,0x7F030025,0x7F03002B,0x7F03003D,0x7F030040,0x7F030041,0x7F030042,0x7F030043,0x7F030044,0x7F030047,0x7F030048,0x7F030051,0x7F030052,0x7F03005A,0x7F03005B,0x7F03005C,0x7F03005D,0x7F03005E,0x7F03005F,0x7F030060,0x7F030061,0x7F030062,0x7F030063,0x7F030072,0x7F030079,0x7F03007A,0x7F03007D,0x7F03007F,0x7F030082,0x7F030083,0x7F030084,0x7F030085,0x7F030086,0x7F0300AD,0x7F0300B3,0x7F0300CA,0x7F0300CB,0x7F0300CE,0x7F0300CF,0x7F0300D0,0x7F0300D1,0x7F0300D2,0x7F0300D3,0x7F0300D4,0x7F0300F2,0x7F0300F3,0x7F0300F4,0x7F0300FA,0x7F0300FC,0x7F030103,0x7F030104,0x7F030105,0x7F030106,0x7F03010D,0x7F03010E,0x7F03010F,0x7F030110,0x7F030118,0x7F030119,0x7F03012B,0x7F03013E,0x7F03013F,0x7F030140,0x7F030141,0x7F030142,0x7F030143,0x7F030144,0x7F030145,0x7F030146,0x7F030148,0x7F03015F,0x7F030160,0x7F030161,0x7F030162,0x7F030169,0x7F03016A,0x7F03016B,0x7F03016C,0x7F03016D,0x7F03016E,0x7F03016F,0x7F030170,0x7F030171,0x7F030172 } - public static int[] AppCompatTheme = new int[] { - 16842839, - 16842926, - 2130903040, - 2130903041, - 2130903042, - 2130903043, - 2130903044, - 2130903045, - 2130903046, - 2130903047, - 2130903048, - 2130903049, - 2130903050, - 2130903051, - 2130903052, - 2130903054, - 2130903055, - 2130903056, - 2130903057, - 2130903058, - 2130903059, - 2130903060, - 2130903061, - 2130903062, - 2130903063, - 2130903064, - 2130903065, - 2130903066, - 2130903067, - 2130903068, - 2130903069, - 2130903070, - 2130903073, - 2130903074, - 2130903075, - 2130903076, - 2130903077, - 2130903083, - 2130903101, - 2130903104, - 2130903105, - 2130903106, - 2130903107, - 2130903108, - 2130903111, - 2130903112, - 2130903121, - 2130903122, - 2130903130, - 2130903131, - 2130903132, - 2130903133, - 2130903134, - 2130903135, - 2130903136, - 2130903137, - 2130903138, - 2130903139, - 2130903154, - 2130903161, - 2130903162, - 2130903165, - 2130903167, - 2130903170, - 2130903171, - 2130903172, - 2130903173, - 2130903174, - 2130903213, - 2130903219, - 2130903242, - 2130903243, - 2130903246, - 2130903247, - 2130903248, - 2130903249, - 2130903250, - 2130903251, - 2130903252, - 2130903282, - 2130903283, - 2130903284, - 2130903290, - 2130903292, - 2130903299, - 2130903300, - 2130903301, - 2130903302, - 2130903309, - 2130903310, - 2130903311, - 2130903312, - 2130903320, - 2130903321, - 2130903339, - 2130903358, - 2130903359, - 2130903360, - 2130903361, - 2130903362, - 2130903363, - 2130903364, - 2130903365, - 2130903366, - 2130903368, - 2130903391, - 2130903392, - 2130903393, - 2130903394, - 2130903401, - 2130903402, - 2130903403, - 2130903404, - 2130903405, - 2130903406, - 2130903407, - 2130903408, - 2130903409, - 2130903410}; - - // aapt resource value: 2 - public const int AppCompatTheme_actionBarDivider = 2; - - // aapt resource value: 3 - public const int AppCompatTheme_actionBarItemBackground = 3; - - // aapt resource value: 4 - public const int AppCompatTheme_actionBarPopupTheme = 4; - - // aapt resource value: 5 - public const int AppCompatTheme_actionBarSize = 5; - - // aapt resource value: 6 - public const int AppCompatTheme_actionBarSplitStyle = 6; - - // aapt resource value: 7 - public const int AppCompatTheme_actionBarStyle = 7; - - // aapt resource value: 8 - public const int AppCompatTheme_actionBarTabBarStyle = 8; - - // aapt resource value: 9 - public const int AppCompatTheme_actionBarTabStyle = 9; - - // aapt resource value: 10 - public const int AppCompatTheme_actionBarTabTextStyle = 10; - - // aapt resource value: 11 - public const int AppCompatTheme_actionBarTheme = 11; - - // aapt resource value: 12 - public const int AppCompatTheme_actionBarWidgetTheme = 12; - - // aapt resource value: 13 - public const int AppCompatTheme_actionButtonStyle = 13; - - // aapt resource value: 14 - public const int AppCompatTheme_actionDropDownStyle = 14; - - // aapt resource value: 15 - public const int AppCompatTheme_actionMenuTextAppearance = 15; - - // aapt resource value: 16 - public const int AppCompatTheme_actionMenuTextColor = 16; - - // aapt resource value: 17 - public const int AppCompatTheme_actionModeBackground = 17; - - // aapt resource value: 18 - public const int AppCompatTheme_actionModeCloseButtonStyle = 18; - - // aapt resource value: 19 - public const int AppCompatTheme_actionModeCloseDrawable = 19; - - // aapt resource value: 20 - public const int AppCompatTheme_actionModeCopyDrawable = 20; - - // aapt resource value: 21 - public const int AppCompatTheme_actionModeCutDrawable = 21; - - // aapt resource value: 22 - public const int AppCompatTheme_actionModeFindDrawable = 22; - - // aapt resource value: 23 - public const int AppCompatTheme_actionModePasteDrawable = 23; - - // aapt resource value: 24 - public const int AppCompatTheme_actionModePopupWindowStyle = 24; - - // aapt resource value: 25 - public const int AppCompatTheme_actionModeSelectAllDrawable = 25; - - // aapt resource value: 26 - public const int AppCompatTheme_actionModeShareDrawable = 26; - - // aapt resource value: 27 - public const int AppCompatTheme_actionModeSplitBackground = 27; - - // aapt resource value: 28 - public const int AppCompatTheme_actionModeStyle = 28; - - // aapt resource value: 29 - public const int AppCompatTheme_actionModeWebSearchDrawable = 29; - - // aapt resource value: 30 - public const int AppCompatTheme_actionOverflowButtonStyle = 30; - - // aapt resource value: 31 - public const int AppCompatTheme_actionOverflowMenuStyle = 31; - - // aapt resource value: 32 - public const int AppCompatTheme_activityChooserViewStyle = 32; - - // aapt resource value: 33 - public const int AppCompatTheme_alertDialogButtonGroupStyle = 33; - - // aapt resource value: 34 - public const int AppCompatTheme_alertDialogCenterButtons = 34; - - // aapt resource value: 35 - public const int AppCompatTheme_alertDialogStyle = 35; - - // aapt resource value: 36 - public const int AppCompatTheme_alertDialogTheme = 36; - - // aapt resource value: 1 - public const int AppCompatTheme_android_windowAnimationStyle = 1; - - // aapt resource value: 0 - public const int AppCompatTheme_android_windowIsFloating = 0; - - // aapt resource value: 37 - public const int AppCompatTheme_autoCompleteTextViewStyle = 37; - - // aapt resource value: 38 - public const int AppCompatTheme_borderlessButtonStyle = 38; - - // aapt resource value: 39 - public const int AppCompatTheme_buttonBarButtonStyle = 39; - - // aapt resource value: 40 - public const int AppCompatTheme_buttonBarNegativeButtonStyle = 40; - - // aapt resource value: 41 - public const int AppCompatTheme_buttonBarNeutralButtonStyle = 41; - - // aapt resource value: 42 - public const int AppCompatTheme_buttonBarPositiveButtonStyle = 42; - - // aapt resource value: 43 - public const int AppCompatTheme_buttonBarStyle = 43; - - // aapt resource value: 44 - public const int AppCompatTheme_buttonStyle = 44; - - // aapt resource value: 45 - public const int AppCompatTheme_buttonStyleSmall = 45; - - // aapt resource value: 46 - public const int AppCompatTheme_checkboxStyle = 46; - - // aapt resource value: 47 - public const int AppCompatTheme_checkedTextViewStyle = 47; - - // aapt resource value: 48 - public const int AppCompatTheme_colorAccent = 48; - - // aapt resource value: 49 - public const int AppCompatTheme_colorBackgroundFloating = 49; - - // aapt resource value: 50 - public const int AppCompatTheme_colorButtonNormal = 50; - - // aapt resource value: 51 - public const int AppCompatTheme_colorControlActivated = 51; - - // aapt resource value: 52 - public const int AppCompatTheme_colorControlHighlight = 52; - - // aapt resource value: 53 - public const int AppCompatTheme_colorControlNormal = 53; - - // aapt resource value: 54 - public const int AppCompatTheme_colorError = 54; - - // aapt resource value: 55 - public const int AppCompatTheme_colorPrimary = 55; - - // aapt resource value: 56 - public const int AppCompatTheme_colorPrimaryDark = 56; - - // aapt resource value: 57 - public const int AppCompatTheme_colorSwitchThumbNormal = 57; - - // aapt resource value: 58 - public const int AppCompatTheme_controlBackground = 58; - - // aapt resource value: 59 - public const int AppCompatTheme_dialogPreferredPadding = 59; - - // aapt resource value: 60 - public const int AppCompatTheme_dialogTheme = 60; - - // aapt resource value: 61 - public const int AppCompatTheme_dividerHorizontal = 61; - - // aapt resource value: 62 - public const int AppCompatTheme_dividerVertical = 62; - - // aapt resource value: 64 - public const int AppCompatTheme_dropdownListPreferredItemHeight = 64; - - // aapt resource value: 63 - public const int AppCompatTheme_dropDownListViewStyle = 63; - - // aapt resource value: 65 - public const int AppCompatTheme_editTextBackground = 65; - - // aapt resource value: 66 - public const int AppCompatTheme_editTextColor = 66; - - // aapt resource value: 67 - public const int AppCompatTheme_editTextStyle = 67; - - // aapt resource value: 68 - public const int AppCompatTheme_homeAsUpIndicator = 68; - - // aapt resource value: 69 - public const int AppCompatTheme_imageButtonStyle = 69; - - // aapt resource value: 70 - public const int AppCompatTheme_listChoiceBackgroundIndicator = 70; - - // aapt resource value: 71 - public const int AppCompatTheme_listDividerAlertDialog = 71; - - // aapt resource value: 72 - public const int AppCompatTheme_listMenuViewStyle = 72; - - // aapt resource value: 73 - public const int AppCompatTheme_listPopupWindowStyle = 73; - - // aapt resource value: 74 - public const int AppCompatTheme_listPreferredItemHeight = 74; - - // aapt resource value: 75 - public const int AppCompatTheme_listPreferredItemHeightLarge = 75; - - // aapt resource value: 76 - public const int AppCompatTheme_listPreferredItemHeightSmall = 76; - - // aapt resource value: 77 - public const int AppCompatTheme_listPreferredItemPaddingLeft = 77; - - // aapt resource value: 78 - public const int AppCompatTheme_listPreferredItemPaddingRight = 78; - - // aapt resource value: 79 - public const int AppCompatTheme_panelBackground = 79; - - // aapt resource value: 80 - public const int AppCompatTheme_panelMenuListTheme = 80; - - // aapt resource value: 81 - public const int AppCompatTheme_panelMenuListWidth = 81; - - // aapt resource value: 82 - public const int AppCompatTheme_popupMenuStyle = 82; - - // aapt resource value: 83 - public const int AppCompatTheme_popupWindowStyle = 83; - - // aapt resource value: 84 - public const int AppCompatTheme_radioButtonStyle = 84; - - // aapt resource value: 85 - public const int AppCompatTheme_ratingBarStyle = 85; - - // aapt resource value: 86 - public const int AppCompatTheme_ratingBarStyleIndicator = 86; - - // aapt resource value: 87 - public const int AppCompatTheme_ratingBarStyleSmall = 87; - - // aapt resource value: 88 - public const int AppCompatTheme_searchViewStyle = 88; - - // aapt resource value: 89 - public const int AppCompatTheme_seekBarStyle = 89; - - // aapt resource value: 90 - public const int AppCompatTheme_selectableItemBackground = 90; - - // aapt resource value: 91 - public const int AppCompatTheme_selectableItemBackgroundBorderless = 91; - - // aapt resource value: 92 - public const int AppCompatTheme_spinnerDropDownItemStyle = 92; - - // aapt resource value: 93 - public const int AppCompatTheme_spinnerStyle = 93; - - // aapt resource value: 94 - public const int AppCompatTheme_switchStyle = 94; - - // aapt resource value: 95 - public const int AppCompatTheme_textAppearanceLargePopupMenu = 95; - - // aapt resource value: 96 - public const int AppCompatTheme_textAppearanceListItem = 96; - - // aapt resource value: 97 - public const int AppCompatTheme_textAppearanceListItemSecondary = 97; - - // aapt resource value: 98 - public const int AppCompatTheme_textAppearanceListItemSmall = 98; - - // aapt resource value: 99 - public const int AppCompatTheme_textAppearancePopupMenuHeader = 99; - - // aapt resource value: 100 - public const int AppCompatTheme_textAppearanceSearchResultSubtitle = 100; - - // aapt resource value: 101 - public const int AppCompatTheme_textAppearanceSearchResultTitle = 101; - - // aapt resource value: 102 - public const int AppCompatTheme_textAppearanceSmallPopupMenu = 102; - - // aapt resource value: 103 - public const int AppCompatTheme_textColorAlertDialogListItem = 103; - - // aapt resource value: 104 - public const int AppCompatTheme_textColorSearchUrl = 104; - - // aapt resource value: 105 - public const int AppCompatTheme_toolbarNavigationButtonStyle = 105; - - // aapt resource value: 106 - public const int AppCompatTheme_toolbarStyle = 106; - - // aapt resource value: 107 - public const int AppCompatTheme_tooltipForegroundColor = 107; - - // aapt resource value: 108 - public const int AppCompatTheme_tooltipFrameBackground = 108; - - // aapt resource value: 109 - public const int AppCompatTheme_windowActionBar = 109; - - // aapt resource value: 110 - public const int AppCompatTheme_windowActionBarOverlay = 110; - - // aapt resource value: 111 - public const int AppCompatTheme_windowActionModeOverlay = 111; - - // aapt resource value: 112 - public const int AppCompatTheme_windowFixedHeightMajor = 112; - - // aapt resource value: 113 - public const int AppCompatTheme_windowFixedHeightMinor = 113; - - // aapt resource value: 114 - public const int AppCompatTheme_windowFixedWidthMajor = 114; - - // aapt resource value: 115 - public const int AppCompatTheme_windowFixedWidthMinor = 115; - - // aapt resource value: 116 - public const int AppCompatTheme_windowMinWidthMajor = 116; - - // aapt resource value: 117 - public const int AppCompatTheme_windowMinWidthMinor = 117; - - // aapt resource value: 118 - public const int AppCompatTheme_windowNoTitle = 118; - - // aapt resource value: { 0x7F030087,0x7F0300B8,0x7F0300B9,0x7F0300BC,0x7F0300E7 } - public static int[] BottomNavigationView = new int[] { - 2130903175, - 2130903224, - 2130903225, - 2130903228, - 2130903271}; - - // aapt resource value: 0 - public const int BottomNavigationView_elevation = 0; - - // aapt resource value: 1 - public const int BottomNavigationView_itemBackground = 1; - - // aapt resource value: 2 - public const int BottomNavigationView_itemIconTint = 2; - - // aapt resource value: 3 - public const int BottomNavigationView_itemTextColor = 3; - - // aapt resource value: 4 - public const int BottomNavigationView_menu = 4; - - // aapt resource value: { 0x7F030038,0x7F03003A,0x7F03003B } - public static int[] BottomSheetBehavior_Layout = new int[] { - 2130903096, - 2130903098, - 2130903099}; - - // aapt resource value: 0 - public const int BottomSheetBehavior_Layout_behavior_hideable = 0; - - // aapt resource value: 1 - public const int BottomSheetBehavior_Layout_behavior_peekHeight = 1; - - // aapt resource value: 2 - public const int BottomSheetBehavior_Layout_behavior_skipCollapsed = 2; - - // aapt resource value: { 0x7F030026 } - public static int[] ButtonBarLayout = new int[] { - 2130903078}; - - // aapt resource value: 0 - public const int ButtonBarLayout_allowStacking = 0; - - // aapt resource value: { 0x101013F,0x1010140,0x7F03004B,0x7F03004C,0x7F03004D,0x7F03004E,0x7F03004F,0x7F030050,0x7F03006C,0x7F03006D,0x7F03006E,0x7F03006F,0x7F030070 } - public static int[] CardView = new int[] { - 16843071, - 16843072, - 2130903115, - 2130903116, - 2130903117, - 2130903118, - 2130903119, - 2130903120, - 2130903148, - 2130903149, - 2130903150, - 2130903151, - 2130903152}; - - // aapt resource value: 1 - public const int CardView_android_minHeight = 1; - - // aapt resource value: 0 - public const int CardView_android_minWidth = 0; - - // aapt resource value: 2 - public const int CardView_cardBackgroundColor = 2; - - // aapt resource value: 3 - public const int CardView_cardCornerRadius = 3; - - // aapt resource value: 4 - public const int CardView_cardElevation = 4; - - // aapt resource value: 5 - public const int CardView_cardMaxElevation = 5; - - // aapt resource value: 6 - public const int CardView_cardPreventCornerOverlap = 6; - - // aapt resource value: 7 - public const int CardView_cardUseCompatPadding = 7; - - // aapt resource value: 8 - public const int CardView_contentPadding = 8; - - // aapt resource value: 9 - public const int CardView_contentPaddingBottom = 9; - - // aapt resource value: 10 - public const int CardView_contentPaddingLeft = 10; - - // aapt resource value: 11 - public const int CardView_contentPaddingRight = 11; - - // aapt resource value: 12 - public const int CardView_contentPaddingTop = 12; - - // aapt resource value: { 0x7F030057,0x7F030058,0x7F030071,0x7F03008C,0x7F03008D,0x7F03008E,0x7F03008F,0x7F030090,0x7F030091,0x7F030092,0x7F030109,0x7F03010A,0x7F030121,0x7F030153,0x7F030154,0x7F03015E } - public static int[] CollapsingToolbarLayout = new int[] { - 2130903127, - 2130903128, - 2130903153, - 2130903180, - 2130903181, - 2130903182, - 2130903183, - 2130903184, - 2130903185, - 2130903186, - 2130903305, - 2130903306, - 2130903329, - 2130903379, - 2130903380, - 2130903390}; - - // aapt resource value: 0 - public const int CollapsingToolbarLayout_collapsedTitleGravity = 0; - - // aapt resource value: 1 - public const int CollapsingToolbarLayout_collapsedTitleTextAppearance = 1; - - // aapt resource value: 2 - public const int CollapsingToolbarLayout_contentScrim = 2; - - // aapt resource value: 3 - public const int CollapsingToolbarLayout_expandedTitleGravity = 3; - - // aapt resource value: 4 - public const int CollapsingToolbarLayout_expandedTitleMargin = 4; - - // aapt resource value: 5 - public const int CollapsingToolbarLayout_expandedTitleMarginBottom = 5; - - // aapt resource value: 6 - public const int CollapsingToolbarLayout_expandedTitleMarginEnd = 6; - - // aapt resource value: 7 - public const int CollapsingToolbarLayout_expandedTitleMarginStart = 7; - - // aapt resource value: 8 - public const int CollapsingToolbarLayout_expandedTitleMarginTop = 8; - - // aapt resource value: 9 - public const int CollapsingToolbarLayout_expandedTitleTextAppearance = 9; - - // aapt resource value: { 0x7F0300C3,0x7F0300C4 } - public static int[] CollapsingToolbarLayout_Layout = new int[] { - 2130903235, - 2130903236}; - - // aapt resource value: 0 - public const int CollapsingToolbarLayout_Layout_layout_collapseMode = 0; - - // aapt resource value: 1 - public const int CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier = 1; - - // aapt resource value: 10 - public const int CollapsingToolbarLayout_scrimAnimationDuration = 10; - - // aapt resource value: 11 - public const int CollapsingToolbarLayout_scrimVisibleHeightTrigger = 11; - - // aapt resource value: 12 - public const int CollapsingToolbarLayout_statusBarScrim = 12; - - // aapt resource value: 13 - public const int CollapsingToolbarLayout_title = 13; - - // aapt resource value: 14 - public const int CollapsingToolbarLayout_titleEnabled = 14; - - // aapt resource value: 15 - public const int CollapsingToolbarLayout_toolbarId = 15; - - // aapt resource value: { 0x10101A5,0x101031F,0x7F030027 } - public static int[] ColorStateListItem = new int[] { - 16843173, - 16843551, - 2130903079}; - - // aapt resource value: 2 - public const int ColorStateListItem_alpha = 2; - - // aapt resource value: 1 - public const int ColorStateListItem_android_alpha = 1; - - // aapt resource value: 0 - public const int ColorStateListItem_android_color = 0; - - // aapt resource value: { 0x1010107,0x7F030049,0x7F03004A } - public static int[] CompoundButton = new int[] { - 16843015, - 2130903113, - 2130903114}; - - // aapt resource value: 0 - public const int CompoundButton_android_button = 0; - - // aapt resource value: 1 - public const int CompoundButton_buttonTint = 1; - - // aapt resource value: 2 - public const int CompoundButton_buttonTintMode = 2; - - // aapt resource value: { 0x7F0300BD,0x7F030120 } - public static int[] CoordinatorLayout = new int[] { - 2130903229, - 2130903328}; - - // aapt resource value: 0 - public const int CoordinatorLayout_keylines = 0; - - // aapt resource value: { 0x10100B3,0x7F0300C0,0x7F0300C1,0x7F0300C2,0x7F0300C5,0x7F0300C6,0x7F0300C7 } - public static int[] CoordinatorLayout_Layout = new int[] { - 16842931, - 2130903232, - 2130903233, - 2130903234, - 2130903237, - 2130903238, - 2130903239}; - - // aapt resource value: 0 - public const int CoordinatorLayout_Layout_android_layout_gravity = 0; - - // aapt resource value: 1 - public const int CoordinatorLayout_Layout_layout_anchor = 1; - - // aapt resource value: 2 - public const int CoordinatorLayout_Layout_layout_anchorGravity = 2; - - // aapt resource value: 3 - public const int CoordinatorLayout_Layout_layout_behavior = 3; - - // aapt resource value: 4 - public const int CoordinatorLayout_Layout_layout_dodgeInsetEdges = 4; - - // aapt resource value: 5 - public const int CoordinatorLayout_Layout_layout_insetEdge = 5; - - // aapt resource value: 6 - public const int CoordinatorLayout_Layout_layout_keyline = 6; - - // aapt resource value: 1 - public const int CoordinatorLayout_statusBarBackground = 1; - - // aapt resource value: { 0x7F03003E,0x7F03003F,0x7F030147 } - public static int[] DesignTheme = new int[] { - 2130903102, - 2130903103, - 2130903367}; - - // aapt resource value: 0 - public const int DesignTheme_bottomSheetDialogTheme = 0; - - // aapt resource value: 1 - public const int DesignTheme_bottomSheetStyle = 1; - - // aapt resource value: 2 - public const int DesignTheme_textColorError = 2; - - // aapt resource value: { 0x7F030029,0x7F03002A,0x7F030036,0x7F030059,0x7F030080,0x7F0300A5,0x7F030117,0x7F03014A } - public static int[] DrawerArrowToggle = new int[] { - 2130903081, - 2130903082, - 2130903094, - 2130903129, - 2130903168, - 2130903205, - 2130903319, - 2130903370}; - - // aapt resource value: 0 - public const int DrawerArrowToggle_arrowHeadLength = 0; - - // aapt resource value: 1 - public const int DrawerArrowToggle_arrowShaftLength = 1; - - // aapt resource value: 2 - public const int DrawerArrowToggle_barLength = 2; - - // aapt resource value: 3 - public const int DrawerArrowToggle_color = 3; - - // aapt resource value: 4 - public const int DrawerArrowToggle_drawableSize = 4; - - // aapt resource value: 5 - public const int DrawerArrowToggle_gapBetweenBars = 5; - - // aapt resource value: 6 - public const int DrawerArrowToggle_spinBars = 6; - - // aapt resource value: 7 - public const int DrawerArrowToggle_thickness = 7; - - // aapt resource value: { 0x7F030034,0x7F030035,0x7F03003C,0x7F030087,0x7F030094,0x7F0300FE,0x7F030108,0x7F030167 } - public static int[] FloatingActionButton = new int[] { - 2130903092, - 2130903093, - 2130903100, - 2130903175, - 2130903188, - 2130903294, - 2130903304, - 2130903399}; - - // aapt resource value: 0 - public const int FloatingActionButton_backgroundTint = 0; - - // aapt resource value: 1 - public const int FloatingActionButton_backgroundTintMode = 1; - - // aapt resource value: { 0x7F030037 } - public static int[] FloatingActionButton_Behavior_Layout = new int[] { - 2130903095}; - - // aapt resource value: 0 - public const int FloatingActionButton_Behavior_Layout_behavior_autoHide = 0; - - // aapt resource value: 2 - public const int FloatingActionButton_borderWidth = 2; - - // aapt resource value: 3 - public const int FloatingActionButton_elevation = 3; - - // aapt resource value: 4 - public const int FloatingActionButton_fabSize = 4; - - // aapt resource value: 5 - public const int FloatingActionButton_pressedTranslationZ = 5; - - // aapt resource value: 6 - public const int FloatingActionButton_rippleColor = 6; - - // aapt resource value: 7 - public const int FloatingActionButton_useCompatPadding = 7; - - // aapt resource value: { 0x7F03009C,0x7F03009D,0x7F03009E,0x7F03009F,0x7F0300A0,0x7F0300A1 } - public static int[] FontFamily = new int[] { - 2130903196, - 2130903197, - 2130903198, - 2130903199, - 2130903200, - 2130903201}; - - // aapt resource value: { 0x1010532,0x1010533,0x101053F,0x7F03009A,0x7F0300A2,0x7F0300A3 } - public static int[] FontFamilyFont = new int[] { - 16844082, - 16844083, - 16844095, - 2130903194, - 2130903202, - 2130903203}; - - // aapt resource value: 0 - public const int FontFamilyFont_android_font = 0; - - // aapt resource value: 2 - public const int FontFamilyFont_android_fontStyle = 2; - - // aapt resource value: 1 - public const int FontFamilyFont_android_fontWeight = 1; - - // aapt resource value: 3 - public const int FontFamilyFont_font = 3; - - // aapt resource value: 4 - public const int FontFamilyFont_fontStyle = 4; - - // aapt resource value: 5 - public const int FontFamilyFont_fontWeight = 5; - - // aapt resource value: 0 - public const int FontFamily_fontProviderAuthority = 0; - - // aapt resource value: 1 - public const int FontFamily_fontProviderCerts = 1; - - // aapt resource value: 2 - public const int FontFamily_fontProviderFetchStrategy = 2; - - // aapt resource value: 3 - public const int FontFamily_fontProviderFetchTimeout = 3; - - // aapt resource value: 4 - public const int FontFamily_fontProviderPackage = 4; - - // aapt resource value: 5 - public const int FontFamily_fontProviderQuery = 5; - - // aapt resource value: { 0x1010109,0x1010200,0x7F0300A4 } - public static int[] ForegroundLinearLayout = new int[] { - 16843017, - 16843264, - 2130903204}; - - // aapt resource value: 0 - public const int ForegroundLinearLayout_android_foreground = 0; - - // aapt resource value: 1 - public const int ForegroundLinearLayout_android_foregroundGravity = 1; - - // aapt resource value: 2 - public const int ForegroundLinearLayout_foregroundInsidePadding = 2; - - // aapt resource value: { 0x10100AF,0x10100C4,0x1010126,0x1010127,0x1010128,0x7F03007C,0x7F03007E,0x7F0300D9,0x7F030112 } - public static int[] LinearLayoutCompat = new int[] { - 16842927, - 16842948, - 16843046, - 16843047, - 16843048, - 2130903164, - 2130903166, - 2130903257, - 2130903314}; - - // aapt resource value: 2 - public const int LinearLayoutCompat_android_baselineAligned = 2; - - // aapt resource value: 3 - public const int LinearLayoutCompat_android_baselineAlignedChildIndex = 3; - - // aapt resource value: 0 - public const int LinearLayoutCompat_android_gravity = 0; - - // aapt resource value: 1 - public const int LinearLayoutCompat_android_orientation = 1; - - // aapt resource value: 4 - public const int LinearLayoutCompat_android_weightSum = 4; - - // aapt resource value: 5 - public const int LinearLayoutCompat_divider = 5; - - // aapt resource value: 6 - public const int LinearLayoutCompat_dividerPadding = 6; - - // aapt resource value: { 0x10100B3,0x10100F4,0x10100F5,0x1010181 } - public static int[] LinearLayoutCompat_Layout = new int[] { - 16842931, - 16842996, - 16842997, - 16843137}; - - // aapt resource value: 0 - public const int LinearLayoutCompat_Layout_android_layout_gravity = 0; - - // aapt resource value: 2 - public const int LinearLayoutCompat_Layout_android_layout_height = 2; - - // aapt resource value: 3 - public const int LinearLayoutCompat_Layout_android_layout_weight = 3; - - // aapt resource value: 1 - public const int LinearLayoutCompat_Layout_android_layout_width = 1; - - // aapt resource value: 7 - public const int LinearLayoutCompat_measureWithLargestChild = 7; - - // aapt resource value: 8 - public const int LinearLayoutCompat_showDividers = 8; - - // aapt resource value: { 0x10102AC,0x10102AD } - public static int[] ListPopupWindow = new int[] { - 16843436, - 16843437}; - - // aapt resource value: 0 - public const int ListPopupWindow_android_dropDownHorizontalOffset = 0; - - // aapt resource value: 1 - public const int ListPopupWindow_android_dropDownVerticalOffset = 1; - - // aapt resource value: { 0x101013F,0x1010140,0x7F030093,0x7F0300DC } - public static int[] MediaRouteButton = new int[] { - 16843071, - 16843072, - 2130903187, - 2130903260}; - - // aapt resource value: 1 - public const int MediaRouteButton_android_minHeight = 1; - - // aapt resource value: 0 - public const int MediaRouteButton_android_minWidth = 0; - - // aapt resource value: 2 - public const int MediaRouteButton_externalRouteEnabledDrawable = 2; - - // aapt resource value: 3 - public const int MediaRouteButton_mediaRouteButtonTint = 3; - - // aapt resource value: { 0x101000E,0x10100D0,0x1010194,0x10101DE,0x10101DF,0x10101E0 } - public static int[] MenuGroup = new int[] { - 16842766, - 16842960, - 16843156, - 16843230, - 16843231, - 16843232}; - - // aapt resource value: 5 - public const int MenuGroup_android_checkableBehavior = 5; - - // aapt resource value: 0 - public const int MenuGroup_android_enabled = 0; - - // aapt resource value: 1 - public const int MenuGroup_android_id = 1; - - // aapt resource value: 3 - public const int MenuGroup_android_menuCategory = 3; - - // aapt resource value: 4 - public const int MenuGroup_android_orderInCategory = 4; - - // aapt resource value: 2 - public const int MenuGroup_android_visible = 2; - - // aapt resource value: { 0x1010002,0x101000E,0x10100D0,0x1010106,0x1010194,0x10101DE,0x10101DF,0x10101E1,0x10101E2,0x10101E3,0x10101E4,0x10101E5,0x101026F,0x7F03000D,0x7F03001F,0x7F030020,0x7F030028,0x7F030065,0x7F0300B0,0x7F0300B1,0x7F0300EC,0x7F030111,0x7F030163 } - public static int[] MenuItem = new int[] { - 16842754, - 16842766, - 16842960, - 16843014, - 16843156, - 16843230, - 16843231, - 16843233, - 16843234, - 16843235, - 16843236, - 16843237, - 16843375, - 2130903053, - 2130903071, - 2130903072, - 2130903080, - 2130903141, - 2130903216, - 2130903217, - 2130903276, - 2130903313, - 2130903395}; - - // aapt resource value: 13 - public const int MenuItem_actionLayout = 13; - - // aapt resource value: 14 - public const int MenuItem_actionProviderClass = 14; - - // aapt resource value: 15 - public const int MenuItem_actionViewClass = 15; - - // aapt resource value: 16 - public const int MenuItem_alphabeticModifiers = 16; - - // aapt resource value: 9 - public const int MenuItem_android_alphabeticShortcut = 9; - - // aapt resource value: 11 - public const int MenuItem_android_checkable = 11; - - // aapt resource value: 3 - public const int MenuItem_android_checked = 3; - - // aapt resource value: 1 - public const int MenuItem_android_enabled = 1; - - // aapt resource value: 0 - public const int MenuItem_android_icon = 0; - - // aapt resource value: 2 - public const int MenuItem_android_id = 2; - - // aapt resource value: 5 - public const int MenuItem_android_menuCategory = 5; - - // aapt resource value: 10 - public const int MenuItem_android_numericShortcut = 10; - - // aapt resource value: 12 - public const int MenuItem_android_onClick = 12; - - // aapt resource value: 6 - public const int MenuItem_android_orderInCategory = 6; - - // aapt resource value: 7 - public const int MenuItem_android_title = 7; - - // aapt resource value: 8 - public const int MenuItem_android_titleCondensed = 8; - - // aapt resource value: 4 - public const int MenuItem_android_visible = 4; - - // aapt resource value: 17 - public const int MenuItem_contentDescription = 17; - - // aapt resource value: 18 - public const int MenuItem_iconTint = 18; - - // aapt resource value: 19 - public const int MenuItem_iconTintMode = 19; - - // aapt resource value: 20 - public const int MenuItem_numericModifiers = 20; - - // aapt resource value: 21 - public const int MenuItem_showAsAction = 21; - - // aapt resource value: 22 - public const int MenuItem_tooltipText = 22; - - // aapt resource value: { 0x10100AE,0x101012C,0x101012D,0x101012E,0x101012F,0x1010130,0x1010131,0x7F0300FD,0x7F030122 } - public static int[] MenuView = new int[] { - 16842926, - 16843052, - 16843053, - 16843054, - 16843055, - 16843056, - 16843057, - 2130903293, - 2130903330}; - - // aapt resource value: 4 - public const int MenuView_android_headerBackground = 4; - - // aapt resource value: 2 - public const int MenuView_android_horizontalDivider = 2; - - // aapt resource value: 5 - public const int MenuView_android_itemBackground = 5; - - // aapt resource value: 6 - public const int MenuView_android_itemIconDisabledAlpha = 6; - - // aapt resource value: 1 - public const int MenuView_android_itemTextAppearance = 1; - - // aapt resource value: 3 - public const int MenuView_android_verticalDivider = 3; - - // aapt resource value: 0 - public const int MenuView_android_windowAnimationStyle = 0; - - // aapt resource value: 7 - public const int MenuView_preserveIconSpacing = 7; - - // aapt resource value: 8 - public const int MenuView_subMenuArrow = 8; - - // aapt resource value: { 0x10100D4,0x10100DD,0x101011F,0x7F030087,0x7F0300A7,0x7F0300B8,0x7F0300B9,0x7F0300BB,0x7F0300BC,0x7F0300E7 } - public static int[] NavigationView = new int[] { - 16842964, - 16842973, - 16843039, - 2130903175, - 2130903207, - 2130903224, - 2130903225, - 2130903227, - 2130903228, - 2130903271}; - - // aapt resource value: 0 - public const int NavigationView_android_background = 0; - - // aapt resource value: 1 - public const int NavigationView_android_fitsSystemWindows = 1; - - // aapt resource value: 2 - public const int NavigationView_android_maxWidth = 2; - - // aapt resource value: 3 - public const int NavigationView_elevation = 3; - - // aapt resource value: 4 - public const int NavigationView_headerLayout = 4; - - // aapt resource value: 5 - public const int NavigationView_itemBackground = 5; - - // aapt resource value: 6 - public const int NavigationView_itemIconTint = 6; - - // aapt resource value: 7 - public const int NavigationView_itemTextAppearance = 7; - - // aapt resource value: 8 - public const int NavigationView_itemTextColor = 8; - - // aapt resource value: 9 - public const int NavigationView_menu = 9; - - // aapt resource value: { 0x1010176,0x10102C9,0x7F0300ED } - public static int[] PopupWindow = new int[] { - 16843126, - 16843465, - 2130903277}; - - // aapt resource value: { 0x7F03011D } - public static int[] PopupWindowBackgroundState = new int[] { - 2130903325}; - - // aapt resource value: 0 - public const int PopupWindowBackgroundState_state_above_anchor = 0; - - // aapt resource value: 1 - public const int PopupWindow_android_popupAnimationStyle = 1; - - // aapt resource value: 0 - public const int PopupWindow_android_popupBackground = 0; - - // aapt resource value: 2 - public const int PopupWindow_overlapAnchor = 2; - - // aapt resource value: { 0x7F0300EE,0x7F0300F1 } - public static int[] RecycleListView = new int[] { - 2130903278, - 2130903281}; - - // aapt resource value: 0 - public const int RecycleListView_paddingBottomNoButtons = 0; - - // aapt resource value: 1 - public const int RecycleListView_paddingTopNoTitle = 1; - - // aapt resource value: { 0x10100C4,0x10100F1,0x7F030095,0x7F030096,0x7F030097,0x7F030098,0x7F030099,0x7F0300BF,0x7F030107,0x7F030116,0x7F03011C } - public static int[] RecyclerView = new int[] { - 16842948, - 16842993, - 2130903189, - 2130903190, - 2130903191, - 2130903192, - 2130903193, - 2130903231, - 2130903303, - 2130903318, - 2130903324}; - - // aapt resource value: 1 - public const int RecyclerView_android_descendantFocusability = 1; - - // aapt resource value: 0 - public const int RecyclerView_android_orientation = 0; - - // aapt resource value: 2 - public const int RecyclerView_fastScrollEnabled = 2; - - // aapt resource value: 3 - public const int RecyclerView_fastScrollHorizontalThumbDrawable = 3; - - // aapt resource value: 4 - public const int RecyclerView_fastScrollHorizontalTrackDrawable = 4; - - // aapt resource value: 5 - public const int RecyclerView_fastScrollVerticalThumbDrawable = 5; - - // aapt resource value: 6 - public const int RecyclerView_fastScrollVerticalTrackDrawable = 6; - - // aapt resource value: 7 - public const int RecyclerView_layoutManager = 7; - - // aapt resource value: 8 - public const int RecyclerView_reverseLayout = 8; - - // aapt resource value: 9 - public const int RecyclerView_spanCount = 9; - - // aapt resource value: 10 - public const int RecyclerView_stackFromEnd = 10; - - // aapt resource value: { 0x7F0300B6 } - public static int[] ScrimInsetsFrameLayout = new int[] { - 2130903222}; - - // aapt resource value: 0 - public const int ScrimInsetsFrameLayout_insetForeground = 0; - - // aapt resource value: { 0x7F030039 } - public static int[] ScrollingViewBehavior_Layout = new int[] { - 2130903097}; - - // aapt resource value: 0 - public const int ScrollingViewBehavior_Layout_behavior_overlapTop = 0; - - // aapt resource value: { 0x10100DA,0x101011F,0x1010220,0x1010264,0x7F030053,0x7F030064,0x7F030078,0x7F0300A6,0x7F0300B2,0x7F0300BE,0x7F030101,0x7F030102,0x7F03010B,0x7F03010C,0x7F030123,0x7F030128,0x7F030168 } - public static int[] SearchView = new int[] { - 16842970, - 16843039, - 16843296, - 16843364, - 2130903123, - 2130903140, - 2130903160, - 2130903206, - 2130903218, - 2130903230, - 2130903297, - 2130903298, - 2130903307, - 2130903308, - 2130903331, - 2130903336, - 2130903400}; - - // aapt resource value: 0 - public const int SearchView_android_focusable = 0; - - // aapt resource value: 3 - public const int SearchView_android_imeOptions = 3; - - // aapt resource value: 2 - public const int SearchView_android_inputType = 2; - - // aapt resource value: 1 - public const int SearchView_android_maxWidth = 1; - - // aapt resource value: 4 - public const int SearchView_closeIcon = 4; - - // aapt resource value: 5 - public const int SearchView_commitIcon = 5; - - // aapt resource value: 6 - public const int SearchView_defaultQueryHint = 6; - - // aapt resource value: 7 - public const int SearchView_goIcon = 7; - - // aapt resource value: 8 - public const int SearchView_iconifiedByDefault = 8; - - // aapt resource value: 9 - public const int SearchView_layout = 9; - - // aapt resource value: 10 - public const int SearchView_queryBackground = 10; - - // aapt resource value: 11 - public const int SearchView_queryHint = 11; - - // aapt resource value: 12 - public const int SearchView_searchHintIcon = 12; - - // aapt resource value: 13 - public const int SearchView_searchIcon = 13; - - // aapt resource value: 14 - public const int SearchView_submitBackground = 14; - - // aapt resource value: 15 - public const int SearchView_suggestionRowLayout = 15; - - // aapt resource value: 16 - public const int SearchView_voiceIcon = 16; - - // aapt resource value: { 0x101011F,0x7F030087,0x7F0300D7 } - public static int[] SnackbarLayout = new int[] { - 16843039, - 2130903175, - 2130903255}; - - // aapt resource value: 0 - public const int SnackbarLayout_android_maxWidth = 0; - - // aapt resource value: 1 - public const int SnackbarLayout_elevation = 1; - - // aapt resource value: 2 - public const int SnackbarLayout_maxActionInlineWidth = 2; - - // aapt resource value: { 0x10100B2,0x1010176,0x101017B,0x1010262,0x7F0300FB } - public static int[] Spinner = new int[] { - 16842930, - 16843126, - 16843131, - 16843362, - 2130903291}; - - // aapt resource value: 3 - public const int Spinner_android_dropDownWidth = 3; - - // aapt resource value: 0 - public const int Spinner_android_entries = 0; - - // aapt resource value: 1 - public const int Spinner_android_popupBackground = 1; - - // aapt resource value: 2 - public const int Spinner_android_prompt = 2; - - // aapt resource value: 4 - public const int Spinner_popupTheme = 4; - - // aapt resource value: { 0x1010124,0x1010125,0x1010142,0x7F030113,0x7F03011A,0x7F030129,0x7F03012A,0x7F03012C,0x7F03014B,0x7F03014C,0x7F03014D,0x7F030164,0x7F030165,0x7F030166 } - public static int[] SwitchCompat = new int[] { - 16843044, - 16843045, - 16843074, - 2130903315, - 2130903322, - 2130903337, - 2130903338, - 2130903340, - 2130903371, - 2130903372, - 2130903373, - 2130903396, - 2130903397, - 2130903398}; - - // aapt resource value: 1 - public const int SwitchCompat_android_textOff = 1; - - // aapt resource value: 0 - public const int SwitchCompat_android_textOn = 0; - - // aapt resource value: 2 - public const int SwitchCompat_android_thumb = 2; - - // aapt resource value: 3 - public const int SwitchCompat_showText = 3; - - // aapt resource value: 4 - public const int SwitchCompat_splitTrack = 4; - - // aapt resource value: 5 - public const int SwitchCompat_switchMinWidth = 5; - - // aapt resource value: 6 - public const int SwitchCompat_switchPadding = 6; - - // aapt resource value: 7 - public const int SwitchCompat_switchTextAppearance = 7; - - // aapt resource value: 8 - public const int SwitchCompat_thumbTextPadding = 8; - - // aapt resource value: 9 - public const int SwitchCompat_thumbTint = 9; - - // aapt resource value: 10 - public const int SwitchCompat_thumbTintMode = 10; - - // aapt resource value: 11 - public const int SwitchCompat_track = 11; - - // aapt resource value: 12 - public const int SwitchCompat_trackTint = 12; - - // aapt resource value: 13 - public const int SwitchCompat_trackTintMode = 13; - - // aapt resource value: { 0x1010002,0x10100F2,0x101014F } - public static int[] TabItem = new int[] { - 16842754, - 16842994, - 16843087}; - - // aapt resource value: 0 - public const int TabItem_android_icon = 0; - - // aapt resource value: 1 - public const int TabItem_android_layout = 1; - - // aapt resource value: 2 - public const int TabItem_android_text = 2; - - // aapt resource value: { 0x7F03012D,0x7F03012E,0x7F03012F,0x7F030130,0x7F030131,0x7F030132,0x7F030133,0x7F030134,0x7F030135,0x7F030136,0x7F030137,0x7F030138,0x7F030139,0x7F03013A,0x7F03013B,0x7F03013C } - public static int[] TabLayout = new int[] { - 2130903341, - 2130903342, - 2130903343, - 2130903344, - 2130903345, - 2130903346, - 2130903347, - 2130903348, - 2130903349, - 2130903350, - 2130903351, - 2130903352, - 2130903353, - 2130903354, - 2130903355, - 2130903356}; - - // aapt resource value: 0 - public const int TabLayout_tabBackground = 0; - - // aapt resource value: 1 - public const int TabLayout_tabContentStart = 1; - - // aapt resource value: 2 - public const int TabLayout_tabGravity = 2; - - // aapt resource value: 3 - public const int TabLayout_tabIndicatorColor = 3; - - // aapt resource value: 4 - public const int TabLayout_tabIndicatorHeight = 4; - - // aapt resource value: 5 - public const int TabLayout_tabMaxWidth = 5; - - // aapt resource value: 6 - public const int TabLayout_tabMinWidth = 6; - - // aapt resource value: 7 - public const int TabLayout_tabMode = 7; - - // aapt resource value: 8 - public const int TabLayout_tabPadding = 8; - - // aapt resource value: 9 - public const int TabLayout_tabPaddingBottom = 9; - - // aapt resource value: 10 - public const int TabLayout_tabPaddingEnd = 10; - - // aapt resource value: 11 - public const int TabLayout_tabPaddingStart = 11; - - // aapt resource value: 12 - public const int TabLayout_tabPaddingTop = 12; - - // aapt resource value: 13 - public const int TabLayout_tabSelectedTextColor = 13; - - // aapt resource value: 14 - public const int TabLayout_tabTextAppearance = 14; - - // aapt resource value: 15 - public const int TabLayout_tabTextColor = 15; - - // aapt resource value: { 0x1010095,0x1010096,0x1010097,0x1010098,0x101009A,0x101009B,0x1010161,0x1010162,0x1010163,0x1010164,0x10103AC,0x7F03009B,0x7F03013D } - public static int[] TextAppearance = new int[] { - 16842901, - 16842902, - 16842903, - 16842904, - 16842906, - 16842907, - 16843105, - 16843106, - 16843107, - 16843108, - 16843692, - 2130903195, - 2130903357}; - - // aapt resource value: 10 - public const int TextAppearance_android_fontFamily = 10; - - // aapt resource value: 6 - public const int TextAppearance_android_shadowColor = 6; - - // aapt resource value: 7 - public const int TextAppearance_android_shadowDx = 7; - - // aapt resource value: 8 - public const int TextAppearance_android_shadowDy = 8; - - // aapt resource value: 9 - public const int TextAppearance_android_shadowRadius = 9; - - // aapt resource value: 3 - public const int TextAppearance_android_textColor = 3; - - // aapt resource value: 4 - public const int TextAppearance_android_textColorHint = 4; - - // aapt resource value: 5 - public const int TextAppearance_android_textColorLink = 5; - - // aapt resource value: 0 - public const int TextAppearance_android_textSize = 0; - - // aapt resource value: 2 - public const int TextAppearance_android_textStyle = 2; - - // aapt resource value: 1 - public const int TextAppearance_android_typeface = 1; - - // aapt resource value: 11 - public const int TextAppearance_fontFamily = 11; - - // aapt resource value: 12 - public const int TextAppearance_textAllCaps = 12; - - // aapt resource value: { 0x101009A,0x1010150,0x7F030073,0x7F030074,0x7F030075,0x7F030076,0x7F030088,0x7F030089,0x7F0300AA,0x7F0300AB,0x7F0300AC,0x7F0300F5,0x7F0300F6,0x7F0300F7,0x7F0300F8,0x7F0300F9 } - public static int[] TextInputLayout = new int[] { - 16842906, - 16843088, - 2130903155, - 2130903156, - 2130903157, - 2130903158, - 2130903176, - 2130903177, - 2130903210, - 2130903211, - 2130903212, - 2130903285, - 2130903286, - 2130903287, - 2130903288, - 2130903289}; - - // aapt resource value: 1 - public const int TextInputLayout_android_hint = 1; - - // aapt resource value: 0 - public const int TextInputLayout_android_textColorHint = 0; - - // aapt resource value: 2 - public const int TextInputLayout_counterEnabled = 2; - - // aapt resource value: 3 - public const int TextInputLayout_counterMaxLength = 3; - - // aapt resource value: 4 - public const int TextInputLayout_counterOverflowTextAppearance = 4; - - // aapt resource value: 5 - public const int TextInputLayout_counterTextAppearance = 5; - - // aapt resource value: 6 - public const int TextInputLayout_errorEnabled = 6; - - // aapt resource value: 7 - public const int TextInputLayout_errorTextAppearance = 7; - - // aapt resource value: 8 - public const int TextInputLayout_hintAnimationEnabled = 8; - - // aapt resource value: 9 - public const int TextInputLayout_hintEnabled = 9; - - // aapt resource value: 10 - public const int TextInputLayout_hintTextAppearance = 10; - - // aapt resource value: 11 - public const int TextInputLayout_passwordToggleContentDescription = 11; - - // aapt resource value: 12 - public const int TextInputLayout_passwordToggleDrawable = 12; - - // aapt resource value: 13 - public const int TextInputLayout_passwordToggleEnabled = 13; - - // aapt resource value: 14 - public const int TextInputLayout_passwordToggleTint = 14; - - // aapt resource value: 15 - public const int TextInputLayout_passwordToggleTintMode = 15; - - // aapt resource value: { 0x10100AF,0x1010140,0x7F030045,0x7F030055,0x7F030056,0x7F030066,0x7F030067,0x7F030068,0x7F030069,0x7F03006A,0x7F03006B,0x7F0300D5,0x7F0300D6,0x7F0300D8,0x7F0300E9,0x7F0300EA,0x7F0300FB,0x7F030124,0x7F030125,0x7F030126,0x7F030153,0x7F030155,0x7F030156,0x7F030157,0x7F030158,0x7F030159,0x7F03015A,0x7F03015B,0x7F03015C } - public static int[] Toolbar = new int[] { - 16842927, - 16843072, - 2130903109, - 2130903125, - 2130903126, - 2130903142, - 2130903143, - 2130903144, - 2130903145, - 2130903146, - 2130903147, - 2130903253, - 2130903254, - 2130903256, - 2130903273, - 2130903274, - 2130903291, - 2130903332, - 2130903333, - 2130903334, - 2130903379, - 2130903381, - 2130903382, - 2130903383, - 2130903384, - 2130903385, - 2130903386, - 2130903387, - 2130903388}; - - // aapt resource value: 0 - public const int Toolbar_android_gravity = 0; - - // aapt resource value: 1 - public const int Toolbar_android_minHeight = 1; - - // aapt resource value: 2 - public const int Toolbar_buttonGravity = 2; - - // aapt resource value: 3 - public const int Toolbar_collapseContentDescription = 3; - - // aapt resource value: 4 - public const int Toolbar_collapseIcon = 4; - - // aapt resource value: 5 - public const int Toolbar_contentInsetEnd = 5; - - // aapt resource value: 6 - public const int Toolbar_contentInsetEndWithActions = 6; - - // aapt resource value: 7 - public const int Toolbar_contentInsetLeft = 7; - - // aapt resource value: 8 - public const int Toolbar_contentInsetRight = 8; - - // aapt resource value: 9 - public const int Toolbar_contentInsetStart = 9; - - // aapt resource value: 10 - public const int Toolbar_contentInsetStartWithNavigation = 10; - - // aapt resource value: 11 - public const int Toolbar_logo = 11; - - // aapt resource value: 12 - public const int Toolbar_logoDescription = 12; - - // aapt resource value: 13 - public const int Toolbar_maxButtonHeight = 13; - - // aapt resource value: 14 - public const int Toolbar_navigationContentDescription = 14; - - // aapt resource value: 15 - public const int Toolbar_navigationIcon = 15; - - // aapt resource value: 16 - public const int Toolbar_popupTheme = 16; - - // aapt resource value: 17 - public const int Toolbar_subtitle = 17; - - // aapt resource value: 18 - public const int Toolbar_subtitleTextAppearance = 18; - - // aapt resource value: 19 - public const int Toolbar_subtitleTextColor = 19; - - // aapt resource value: 20 - public const int Toolbar_title = 20; - - // aapt resource value: 21 - public const int Toolbar_titleMargin = 21; - - // aapt resource value: 22 - public const int Toolbar_titleMarginBottom = 22; - - // aapt resource value: 23 - public const int Toolbar_titleMarginEnd = 23; - - // aapt resource value: 26 - public const int Toolbar_titleMargins = 26; - - // aapt resource value: 24 - public const int Toolbar_titleMarginStart = 24; - - // aapt resource value: 25 - public const int Toolbar_titleMarginTop = 25; - - // aapt resource value: 27 - public const int Toolbar_titleTextAppearance = 27; - - // aapt resource value: 28 - public const int Toolbar_titleTextColor = 28; - - // aapt resource value: { 0x1010000,0x10100DA,0x7F0300EF,0x7F0300F0,0x7F030149 } - public static int[] View = new int[] { - 16842752, - 16842970, - 2130903279, - 2130903280, - 2130903369}; - - // aapt resource value: { 0x10100D4,0x7F030034,0x7F030035 } - public static int[] ViewBackgroundHelper = new int[] { - 16842964, - 2130903092, - 2130903093}; - - // aapt resource value: 0 - public const int ViewBackgroundHelper_android_background = 0; - - // aapt resource value: 1 - public const int ViewBackgroundHelper_backgroundTint = 1; - - // aapt resource value: 2 - public const int ViewBackgroundHelper_backgroundTintMode = 2; - - // aapt resource value: { 0x10100D0,0x10100F2,0x10100F3 } - public static int[] ViewStubCompat = new int[] { - 16842960, - 16842994, - 16842995}; - - // aapt resource value: 0 - public const int ViewStubCompat_android_id = 0; - - // aapt resource value: 2 - public const int ViewStubCompat_android_inflatedId = 2; - - // aapt resource value: 1 - public const int ViewStubCompat_android_layout = 1; - - // aapt resource value: 1 - public const int View_android_focusable = 1; - - // aapt resource value: 0 - public const int View_android_theme = 0; - - // aapt resource value: 2 - public const int View_paddingEnd = 2; - - // aapt resource value: 3 - public const int View_paddingStart = 3; - - // aapt resource value: 4 - public const int View_theme = 4; - - static Styleable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Styleable() - { - } - } - } -} -#pragma warning restore 1591 diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/layout/activity_main.axml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/layout/activity_main.axml deleted file mode 100644 index 7447d516..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/layout/activity_main.axml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index c9ad5f98..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index c9ad5f98..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2531cb31efc3a0a3de6113ab9c7845dc1d9654e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1634 zcmV-o2A%ndP)B+Z3$1(8#|f~9B42Y^N-3=o2YCq0YUY$Zu=pM;#hG{lHi%n~Vh z1d1vN#EDO19X?u$`cV z!a}AKG@Bb*#1cdYg8af_;jP69b`k%G1n?0=F^8bI^o>wg-vEliK^U}y^!D|^p|ax; zC|pK=f+FHp!RUAhtlpGGUxJb|wm^5! z<1r%$<$TR02wajxKZ4MiR#aAxDLE(##UNyD|ABr4WoGRF*?@e^2|~Hq(gurSSJH*;Q~5lw{J5A_(PCXBWhzZE${qgzv0{dk-F( z1<}>r181tLiEla&f1j&?p2xjbfp2cTt-c1Ox~?9EhK9`cJ9Vatf)loIoQ@#h&}cIGD>Z#QLE}&(bMo@7Ff|7f#Nm^$PJpVcbj+v~K7wfVwF}=) zRQsc+`=A-+C)vrRvaIC-5u>|;3h z*G4-u#RI<_vuSN~vZ6{|I~q5FFk3%de#+*>UFG>&bq6~ zUEMZ~FIOmFO=kA^5rkp-Msw?^63xvdXVZ-rv@{6{iVO}M!}^Je%2BPbi+(L<5<%~h z2v^D+f<|j%7~cJjOzg*!GPQ{%uE{i%YgcZhuZh{yNlQ}RhaU1jd=K+AopVKP+D}&} zZ3y$llqZiln=Z_A$!qzkGbX0D{?l(v5@1|`QyCvCnQ`eKI>|zj_zo%y#fKf85VhQ} zP)y&j4P*nR3q{-o35iV6nx7QDqq<;WDVIt}|N%`!dgv*y3va8eLNj zU9x(?ieweHfQ*yXk8|=ssZ~qJEz^QoKJ|iGa>ge_Vm_8l}S+UvJ{8g4jr+o#aTSFsz1W;PDP zW765JXGU#3JL>SlIl3NRV2{7B2dLO1cIP)a4ZRYL|MBD36O1#oSgAf}APz5@;x=_U-<=y)Py7*}O5(uu7BL_eLe6Ek7pH|G zMq)FrF1EFq&yruS5b=F=w)fVVoPd(oeRyTFym_Uwyn~L=OL(O?cf^2L5R(SmjORx6 z%nmZf^W=3pkvT*>@osUNi>DULH1hL;y`JGQX$onRCr_U0=H~Viodq!<7Q{3rPk~{G gu#IhOV;e2n|1(WJB~7~kivR!s07*qoM6N<$g7lUVaR2}S diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 7a859c25556af7a2e46e22a2220eaded55628e9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1441 zcma)+`#%#30L6FjMQg%tuA0%p#0??L`*E=rD#U2F4L5n@F+O9Sp;(QwEQy7+?sX?r zCWN(!Hg`+j5k8*H@|yQEtnAi*(D{7M`Tlf1=eKjq)BUsp2nqrK01B=yNUv`!`EH=x zx8$xJQUd^Fuec%|(TT&0V}4orr_==mmCnEuzD+ff8Pg>pJRqsWsD{#?eGPaCu0(sEH_2RG@<6-Nt<8 ztPMUmmAz9Ga$23Y9~p9dqJSgJJ#Jk_r@o13^%d-Xf46i+Lrmz3 zy9(DUDVXj;Zny7nO+yn&W2flEX=C!8&D0zI`G# z8;XmlonoghgRFUY*$+7pPLa}Uy)onw>TT9t(FTV6#BV8&lXWDPRvQW_n~xZ|yLcZjX>m$Eaf1)dwXS`&E^ zkNjO;%;fWywchc=+w4utQ0Vbn%B>b~yy4I#D{?1!P`$P>Wdo+ljCo(tYia04JTc=$$u+IbzDVPFYpm8+AQj+ zGKH zfS{{hN%W)kF+(26oZpkURD5Q_G_z97F6{Jval+TOj-;5y)*Rdo3a$^^k~q5gpTzmp1q@+2X9O z;_VUF>;s~C1~gpFrFoh?{aQ|LlBIYz!z^P~lndX5-ES)p#+9GW*|-WBTzQ*&gKOE` zM##bUaWl`6rZBXw0!~_oUhf+H$tNc@lLZCj0bZT^KSo@C|P?7YR8dP0se1jj z9aA0|7MONf(ZYaLZs$s}r*05fx25-iN6mZe_*Rq%uyz(+^-k;t`!R`?uf~rn#1ZC7 zuv3}UrmMzcBbo4jym@fS5%I+G`GJIC1s$)MQs3Vhld?a2U;w}$@V%dC@%qpO7+3#$ N&GnQ!lI8SQ#{X#Iv!eh2 diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index b8d35b3a1cfe5308bb099a5a1429555c41417e36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3552 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 diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 8f56909cddfa86f1387074bf43003f36d6e67be1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2413 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 b;}yxl--Ss53i@>Q4YQuNcebmsMJN0NT!aL! diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 9737d79c0492b2c8a811621660eba33e79fab959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4858 zcmVxlCBHiW_rSgI3_J^MKwHqJEz|i*Sg*YtOHn%!8|O@U|xT*V!1aH) zx9aT)+OT1e6*I^fro))}A|t%nqOC49C*uh}iznRD0RVt(Fkci3aF-cE^~v-{jirSe z8y+KDRrXqA%?3VAUmJ!e`Y4{{Db{MI)J1oI-WfBjRTVY1Q!rK-v!l86id7G;UWZ3x z7~0LnZOuZ2xjo$KBiYmM_`2d z5?SVjnV>hVk!Z_9*%?FywwjSrU-z}DU~qVkNCML#z4GhV z_dS*4ib?_|4A~&o6c6ZDCNLfVt@G)TDg@Pe&InwDu_Y44rH_jqbYt zQQk%w?14PLdL_onhlQI!tDo8~G_ws5=fN6HW6)RMZ1xE78Tw}PR+Lk5El;CNtD@BG z@-c!)0b@`g>cgGvV&(C9t(F;co=4};U+^dfw6xu|4X@RormvOYhELMs z#n0=>EFFekYFvrh+S)vl0br1y$L?uHF?ZLL#>k8mg*7cHSw;nCRmALvD)pwhLaqK` zH{FAdpJ?$&@EJOEIG%e~S}30iDZGsfvTJYqebn^#ei9&%5{a3h)`)uHexhMfx2GC}a7&+PSj;~z&<#NnP097H+5#qe^HCa1jY34dHKXo8 zyY}pNY0`(An$dSZ{AfkZ$4_A9@iVII_BL<*2^~Fl!lh?HY6o9?8_(#NGRALVO#8VI z9n&Hr&MA(;4gAX2_<|07{q29d4A%Yse8#Sg>u#G&F@_8Hz`UC4@30;drblKka481` z?((Z|zQ@@uWsI@Bpz3gpTq7nHw%?y+JiTRw)x(8QKjZG6LV@5aU|(2+QR(aE^IiQA zbbY#Ry<58f_jBjbjM>lIwKaI;ZD{|mhuvbp&fR-a)yVM<(;)5!g71B_7Ufosrv7ZTPIz#p-Luu#-A?Iq&cPX$ zzM1o0ayvrq*fGO)ASt78v{QGK(f{&-ng{so_ts*sjO@u0Q~!L6QwtMIG_TAibnspej~MaY~_~X)&16cA3OA}Uc)}S zZIuHg0l)fIxZO8!t8bb(l>-Cnku0bDbBiIiN=wjhmPbZL24MzlVdpYjrNWx)(Pv+N zBWOAR3??M;Y<>CqF?UmT!q$5#$Hw0_5S%iz0WXT*1g|T5HRZin>UI=?a+d@J@ z!s*q|QbSDkGb%|Ptu~nUaAClGGv)}o`WafkaSJLkjkN=I!IBjnQqbDkiW**Ov@?)k zGq(Qtv*2Socm6z@IOPdFd$xCn2c|3a@PedtiB%Y-T!Ns zB*nm2J}l((;v)h?(g?ET>{yU|?VjUA$|Z5Ar4z zy&(!+?I)a55qI7%Xw>;RW~l8%Ar-Om{WT5^Y~x$+J4{7<@%1J_QxP{h$Tzu?ijZcP zKq?}fVC`eW07@i+F8B>mD^4f z)ZCiSzUcJ1kJo--m#qXTfHz@!FdhAeQdfr()df(n8{lw5hWt__$<&YXgbf+9gAJMc zW<2fEh74^Wt)GRe=bqeL_c`r8F zZ%NkP(2@K3Gurh1b{rks2WKzipslrswj^bFgIglwlMH~dvpP|4vRM$R(A9m*hXM4a z{4CC!@(@?pZpuIQ%!_Vq%1@oy;BZ@V_r3$1Hs$Z-xhbElE&Cp0JBVQHxI|GZmG;L! z!cy}pUl5`!WzA<_x?Ps?(38*EwFT+}D%{)w4WeKG+_o)f-(4r+oe$Td9FAov)Yh)P z4vEusup1UeF!pl7fNJ<-5Wab=5QSObu{0lZy)X+3VhwhMS;IIMX0@RgaIog6Fbk?C zTx|!ur{OpMjaOloqObP-sLfq@n$Z3)UV(sl1(Orr_5onOR78jzqW7(*JljLXv( z@h(qS6x5&?Y5JXjX{Y+%Mhyk@@83TeKfIkwUdT~|ykpm%Uc~^Yq_8a%b~pV1Kc(8z zoqm3P3c4D?#dpPGV`HIoB1)QRoC#7O#GxDz9Gw!NHm6%&QMzz}Dm~%)iV{ zGPeP+B$&E(5j7MN5)+rJ)D3A8;w8Q8Ui6aQr~h3q$V+_zR@JpD!O z6@t8|oswO4Y(T`I62MR_7K=EYk`fUS0Y|&XC1n`qz>CL1NP%Y`Rj{AeQ3cHE2i+g9 z$XNi`5e&JWnnKxva6i8wwX9(94k6-#zI|8+z44N)E#Bqp8<0hBzPP9Rok_u<_*BiE zpx1Fxs=hMmM6B-%{ zA2dja5v#^23aZ50BUK|xXAp(ZNxW`U&_!XEVU zV=I}8Hxwt!nhV$vjJo7JX>U56>IHQz@}zXb3SyKmUA_mmg3DQhUCz8!fC<4Spew($ z;e$P^5VEzFCeakFf!%)Me)ZWyyPbef8C|hjw-#fOPGdr0)8${-=*QRtI6OT$v*@eK zi3wKVrx$(=1tndn_noPttFW$%gmXQxy3=ANthcD6zW40_8=X((d6Lp}-{86D0tN(& zZvEtyH_Ip|VaiO>7(QVPGkrcnp8}qJ7#~Vh7lPV>GV>&s(e3sxEJ25Ufq{YWg(3I~ zU4}R<|4n&8b;l=6`T`RyF%KQ(#w&8b;KGpu5;Awcp8UKO#RMXPAPH&lO6_b}ZskR& zg{195@012Qu|}yJD!-GOQ*kj)rU6$ojja60o(A8hpey)lFE0@=K^2{-xJ8;-yobph z^)_i>uX^gpvCN{qQFM@{qUQ*6_423>yD?RDp(2q8PKHwW2Z!m!s={|bY(W~B4{CZc zBgoh~q*j(U7>QN+?}>s2z^;~p%x!?DfzM_FxM6|*{{Hd!XA1bo10~8y5>4?As19Hv zXJVxP@Fdrg9#hA8pGcxH?u+Cm=y&w<~fq{a`3jA*+9(;bhBKtXM zc3BhSDM86L(XTyXBiK5gjD@OThB3w~vQ@?l6Mli8uULbAMT{ygP>eX7*m2G=arDK$ZBF}Q^?qZJyqqn zs*>=^35vw}6AZKrL^?D)sxnTNIS&VL+rdVVNZLw8F)D#!iaU&9?q|O7!fuc02hQ(- zzF`b;shJHS;gMBD-N@*%QeKXzH>ez!B4=8E21biSp%TJ~G+$re+-R|EVxl_lZE05N zewrCWSdzj1Rt=>p+F4)5ZfAgH|Bktj4K}mVfzc4B;J)@jpU^iRLmpZ2GJ0&3x(V#= z$hNy|1Bh}U=v3lSfND}<5Hf;-29ykx$R{Nza~qR044YE3%a6(Os;LcbSgo`tWz85z zM6Y}k^$a{K&#$=z^*PCz#!b*R^Z|WApR`-)l>%cSdOonz`u#q}hyd`Xv7U{CH=~GD zr~w#EIbjjeb+AI?Q?+vvl=*LnGxVQHGK)8-Xv==V%sG^rS9w&PS9u%={+*grehB`C zwp4sK%tv;}Pv(A9KbA_?6$<gpmV|K5zk3V^6LOr zItEUINek*iBnmPHhK5%JV^9ZN9bXRw|Aya*M8O8Qhuo_nI$cfLl0w_GVWsqY5b3*L zUsE+)7~w;7ZhxW%!r+Bw@V#kOMM+39QCTtqD3F3ha`Lwn`d*O)o`p8Z%h6$^?f#@M zpUWM1R~X_)cHscHP`c6}I0E!FfNDe0@HbM85K5l$Cv98-oF_vVruYz*(T{-2Cg%4( gUP6AytBbGy15leQhEvp{>;M1&07*qoM6N<$g7ZLQy#N3J diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9133e31b43252d00767a6a3806df9ba68de2d265..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3871 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 diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index c3ae5f5ccdecc01a9b17a2a0c2b1bb20602f0151..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8001 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 z 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 F@PB1o0BisN diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index ef89bd5215ffcc38c68b119a7495a77a7084543b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10893 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 diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/colors.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/colors.xml deleted file mode 100644 index bf1bf200..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #2c3e50 - #1B3147 - #3498db - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/ic_launcher_background.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/ic_launcher_background.xml deleted file mode 100644 index 6ec24e64..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #2C3E50 - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/strings.xml b/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/strings.xml deleted file mode 100644 index c0cff9dd..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/Resources/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - LaunchDarkly.ClientSdk.Android.Tests - Settings - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs b/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs deleted file mode 100644 index 6f57d93b..00000000 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/XunitConsoleLoggingResultChannel.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Xunit.Runners; - -// This class is used in both iOS and Android test projects. It is not applicable in .NET Standard. -// -// It is based on the TextWriterResultChannel provided by xunit.runner.devices, but it has better -// diagnostic output for our purposes: captured test output is included for failed tests, and any -// multi-line error messages are logged as individual lines so that our log-parsing logic won't be -// confused by lines with no log prefix. - -namespace LaunchDarkly.Sdk.Client.Tests -{ - public class XunitConsoleLoggingResultChannel : IResultChannel - { - private readonly TextWriter _writer; - private readonly object _lock = new object(); - - private int _passed, _skipped, _failed; - - public XunitConsoleLoggingResultChannel() - { - _writer = Console.Out; - } - - public Task OpenChannel(string message = null) - { - lock (_lock) - { - _failed = _passed = _skipped = 0; - _writer.WriteLine("[Runner executing:\t{0}]", message); - return Task.FromResult(true); - } - } - - public Task CloseChannel() - { - lock (_lock) - { - var total = _passed + _failed; - _writer.WriteLine("Tests run: {0} Passed: {1} Failed: {2} Skipped: {3}", total, _passed, _failed, _skipped); - return Task.FromResult(true); - } - } - - public void RecordResult(TestResultViewModel result) - { - lock (_lock) - { - switch (result.TestCase.Result) - { - case TestState.Passed: - _writer.Write("\t[PASS] "); - _passed++; - break; - case TestState.Skipped: - _writer.Write("\t[SKIPPED] "); - _skipped++; - break; - case TestState.Failed: - _writer.Write("\t[FAIL] "); - _failed++; - break; - default: - _writer.Write("\t[INFO] "); - break; - } - _writer.Write(result.TestCase.DisplayName); - - var message = result.ErrorMessage; - if (!string.IsNullOrEmpty(message)) - { - _writer.Write(" : {0}", message.Replace("\r\n", "\\r\\n")); - } - _writer.WriteLine(); - - var stacktrace = result.ErrorStackTrace; - if (!string.IsNullOrEmpty(result.ErrorStackTrace)) - { - WriteMultiLine(result.ErrorStackTrace, "\t\t"); - } - } - - if (result.HasOutput && result.TestCase.Result != TestState.Passed) - { - _writer.WriteLine(">>> test output follows:"); - WriteMultiLine(result.Output, ""); - } - } - - private void WriteMultiLine(string text, string prefix) - { - var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) - { - _writer.WriteLine(prefix + line); - } - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj new file mode 100644 index 00000000..f8b92db7 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj @@ -0,0 +1,78 @@ + + + + net7.0-android; net7.0-ios + $(TargetFrameworks);net7.0-windows10.0.19041.0 + + + Exe + true + true + enable + true + false + false + + + DotnetSdkTests + + + com.LaunchDarkly.ClientSdk.Device.Tests + 740c3da9-1864-4990-b6b3-d1623e8e8bec + + + 1.0 + 1 + + 11.0 + 13.1 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.sln b/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.sln new file mode 100644 index 00000000..f499803a --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1706.6 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LaunchDarkly.ClientSdk.Device.Tests", "LaunchDarkly.ClientSdk.Device.Tests.csproj", "{EAD27208-B680-415B-B93C-3606CD62B302}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EAD27208-B680-415B-B93C-3606CD62B302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAD27208-B680-415B-B93C-3606CD62B302}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAD27208-B680-415B-B93C-3606CD62B302}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAD27208-B680-415B-B93C-3606CD62B302}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6A3D7A01-9533-4035-ADA4-76CA0C14DADC} + EndGlobalSection +EndGlobal diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/LdClientContextTests.cs similarity index 100% rename from tests/LaunchDarkly.ClientSdk.Android.Tests/LdClientContextTests.cs rename to tests/LaunchDarkly.ClientSdk.Device.Tests/LdClientContextTests.cs diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/MauiProgram.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/MauiProgram.cs new file mode 100644 index 00000000..cd2f0911 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/MauiProgram.cs @@ -0,0 +1,20 @@ +using Xunit.Runners.Maui; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + public static class MauiProgram + { + public static MauiApp CreateMauiApp() => + MauiApp + .CreateBuilder() + .ConfigureTests(new TestOptions() + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) + .UseVisualRunner() + .Build(); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidManifest.xml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidManifest.xml new file mode 100644 index 00000000..40a06481 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidSpecificTests.cs similarity index 87% rename from tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs rename to tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidSpecificTests.cs index 21ccd7af..929fefb4 100644 --- a/tests/LaunchDarkly.ClientSdk.Android.Tests/AndroidSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/AndroidSpecificTests.cs @@ -8,12 +8,6 @@ namespace LaunchDarkly.Sdk.Client.Android.Tests { public class AndroidSpecificTests : BaseTest { - [Fact] - public void SdkReturnsAndroidPlatformType() - { - Assert.Equal(PlatformType.Android, LdClient.PlatformType); - } - [Fact] public void EventHandlerIsCalledOnUIThread() { diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainActivity.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainActivity.cs new file mode 100644 index 00000000..bbe144dc --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainActivity.cs @@ -0,0 +1,11 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + public class MainActivity : MauiAppCompatActivity + { + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainApplication.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainApplication.cs new file mode 100644 index 00000000..9aace80d --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/MainApplication.cs @@ -0,0 +1,16 @@ +using Android.App; +using Android.Runtime; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + [Application] + public class MainApplication : MauiApplication + { + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/Resources/values/colors.xml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 00000000..5cd16049 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/AppDelegate.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 00000000..e98ccac5 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,10 @@ +using Foundation; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Info.plist b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Info.plist new file mode 100644 index 00000000..0690e472 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,30 @@ + + + + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Program.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Program.cs new file mode 100644 index 00000000..67f79154 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,16 @@ +using ObjCRuntime; +using UIKit; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + public class Program + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/Main.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/Main.cs new file mode 100644 index 00000000..79d22928 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/Main.cs @@ -0,0 +1,17 @@ +using Microsoft.Maui; +using Microsoft.Maui.Hosting; +using System; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + internal class Program : MauiApplication + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + + static void Main(string[] args) + { + var app = new Program(); + app.Run(args); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/tizen-manifest.xml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/tizen-manifest.xml new file mode 100644 index 00000000..fdb0699a --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Tizen/tizen-manifest.xml @@ -0,0 +1,15 @@ + + + + + + maui-appicon-placeholder + + + + + http://tizen.org/privilege/internet + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml new file mode 100644 index 00000000..99de196c --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml.cs new file mode 100644 index 00000000..c3d9201c --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/App.xaml.cs @@ -0,0 +1,25 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace namespace LaunchDarkly.ClientSdk.Device.Tests.WinUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : MauiWinUIApplication + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } + +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/Package.appxmanifest b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/Package.appxmanifest new file mode 100644 index 00000000..763343d6 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/app.manifest b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/app.manifest new file mode 100644 index 00000000..a087f840 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/Windows/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/AppDelegate.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/AppDelegate.cs new file mode 100644 index 00000000..e98ccac5 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,10 @@ +using Foundation; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/IOsSpecificTests.cs similarity index 85% rename from tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs rename to tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/IOsSpecificTests.cs index 2cd29bf0..be312d34 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/IOsSpecificTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/IOsSpecificTests.cs @@ -1,6 +1,6 @@ -using System.Threading; -using LaunchDarkly.Sdk.Client.Integrations; -using LaunchDarkly.TestHelpers; +using System.Threading; +using LaunchDarkly.Sdk.Client.Integrations; +using LaunchDarkly.TestHelpers; using Foundation; using Xunit; @@ -8,12 +8,6 @@ namespace LaunchDarkly.Sdk.Client.iOS.Tests { public class IOsSpecificTests : BaseTest { - [Fact] - public void SdkReturnsIOsPlatformType() - { - Assert.Equal(PlatformType.IOs, LdClient.PlatformType); - } - [Fact] public void EventHandlerIsCalledOnUIThread() { diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Info.plist b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Info.plist similarity index 51% rename from tests/LaunchDarkly.ClientSdk.iOS.Tests/Info.plist rename to tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Info.plist index 622549dc..358337bb 100644 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Info.plist +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Info.plist @@ -1,61 +1,32 @@ - - - - - CFBundleDisplayName - LaunchDarkly.ClientSdk.iOS.Tests - CFBundleIdentifier - com.launchdarkly.ClientSdkTests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - MinimumOSVersion - 10.0 - UIDeviceFamily - - 1 - 2 - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIMainStoryboardFile~ipad - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - XSAppIconAssets - Assets.xcassets/AppIcon.appiconset - CFBundleName - LaunchDarkly.ClientSdk.iOS.Tests - NSAppTransportSecurity - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - - - + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Program.cs b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Program.cs new file mode 100644 index 00000000..67f79154 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Platforms/iOS/Program.cs @@ -0,0 +1,16 @@ +using ObjCRuntime; +using UIKit; + +namespace LaunchDarkly.ClientSdk.Device.Tests +{ + public class Program + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appicon.svg b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appicon.svg new file mode 100644 index 00000000..5f04fcfc --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appiconfg.svg b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appiconfg.svg new file mode 100644 index 00000000..62d66d7a --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Fonts/OpenSans-Regular.ttf b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dadcb015d492fac2cfdd868689bf26ba19e75309 GIT binary patch literal 107168 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�u>gYo2#{i|Un2TZ=vdP|Rt&?MLJNpIhdUHE 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

Fpy)y!S(AQHSG#udd_8#b4sQu!R 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 PgFh2POM|ilu9yD@)d*=D diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon120.png deleted file mode 100644 index 9c60a1761dbf62cc2a45ff98b9fdb63ade16e4d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3773 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 Dh-f%Y diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon152.png deleted file mode 100644 index 448d6efb577d07e227a5c62545173ddf6bd86b55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4750 zcmdsb=QrF9wDnK#B^X9;Q9^V{l#CXA)L_)ej2hhtqlaMh5WS5Wg6N_|8RZ$G_vkf9 z5JZU<_2zy5i+ewuz1RM*&sqD^UhBjd=xI=qvycM-K&7Rr`urbf{=Xq5{)gcIxVZlj zp`)^{G62*iQd}d50Dw+SOI6v}4{!ekg(s{Rq@YE5pOB7&`>m3SpD-<+qnxv4BTc@~ zM{1D|O$!#56?*b|pjiA#`~(%lh{=Se_>I>=aGy#&c20J1)xLMF9?|AKE-r2*uD9=L zRY*6d50*AXL)Jq$@9tJ}ma)sZ0~?*^w~ptSKl}5a9mjs_?y7Pd#S^L|D+OqJQxG540qoJ9dxD4)lwK(7)=k+md0c4*X=xd1L*Bu!u z%IRa8oVJY=UYOj>NnpuG}*2TYAF24V94?je zUn_6KJ`0DnJuwUn#kMy`qNMZoy|$PAr?*5OdiL(X0#Lq<3T~)ZC0OaK@7P&x#jE<9*CKd^1)k_8t0b@>!&CT(6^Vy?`Uq7#5j&EGJlORzv>e%! znNY2P<X(KdS7AjZJSP76n+gVPg|8`_aX=2NCQjf`n$&Bz-=oXMpPbt_7ZJ zh^-Xlyca1Utv+%7>m5TkZ{%Qx(C#Z=+|Ej(;ElO(DCF9luaWBuyGh>)*@GDaGT|BR zod!zD@$y#$wNz2RUfGI#+@(Fab9)QAnmytV*y@sSQ!PL@jUse^PgI$Z$)92HQ~LD{ zETF}D!n%DLy>--g$73{;S&vPo1Op{M5Ow8=Dym*(FD85KiP$$c8#!85;PhF2Y`QUV zFYV765M%m}sXorn6EC=*dKDqU(97Y^MD|aU`n#>k#$3a<^jHyE$E_ zemwewpe2Do>xLc2Qs2o)m%*~Rw{ONg2CjLpZNk*!h2eNhni=!5W?Yo`zF-Mw~$kw3gkv;)WEeRJ%Q#FGB11W}4wRlTZ_TV#D%k#g~SnL+{^%` z!z{{}F%_S;kjB;peqTqeD8S#O4Ew}rkJt3(C6$|Ej8)nF0RPHbe;HZy_f4`qbZctO zJ2n+lCL2LrHFIF=$KUYnMUKU>8P|%UNaM)h9GZRy8an#?)qVHE{XY9^6FT@3&eTm2 zmfrOrEy4-?BYRLOE8bpz~Nldc&T14?{R<3(Au5u#{QUh8Td$cUzy#9flp8IQ*Qj(u}oeZ78W=8^%vHP{^4|N#Bvl`98)G7?ib* zoNPdZFMTRlbt^A=-Q`Xz1*?wU!9+Z|UQXAZ4X|G}riTAG)jiQR$py2ZLE0uN+dG^# zd|fWhqc=?NN~|J)y}8VM=fCrBnVqCpaREogX!bt^Fy07PpnjHSW{Q!Bo<5CWE_v+C za)!T*V-&cDBb&5_`CZuHK1=TW9^ef&mq1{}F}JQk3LuBJgZ?)WRXSZx>W@9xHFd1& z&9ObICBPZVUc`-DDv1^r@5_aaB#W^8`xpJe=_J(qB`m&bHhNh4vRAri(u({~Q_F39 z?XYMfzb{3*TeZj0rikqNKnRpM^k`v$yt0mH8Rs@J2g!{RSc%zeO3#=U3;(IRwN~+Z z?myI?|BNin+Teiq%C8Vcs0l_Ktl+_X0#26De~_A4M%i^+d&6aNuFS(tgT>TdY~>n! zf$orZ*ktv&J&p-vx*+|e5GAexQaP~l%|!2T;*w{bBb1FFeD~T*8Pe8S&hJJ-QNvJ~ z8ime-a|vZ8+`v?z%T8ur9xjS4tY)jqR34HEH!x}F_V^I2Ag~?Q%yiCKO0Gsnp9akF zMysFO^KhSgTd!K}e?JTXbPXNIR_mw~#ra3fza zNY9x!b;s{dzWU16;-4K4r<<&q*^G0ipD3G%<#l*-DqVqNVh&*3SSzn2a&d*F4FvTY z;-^06$>qyavKOs36@iC7Hr8Wn6>6*rH|O_^bLAR5!arFD9R={zZ0Fi#dgvlpSX+T zUa=FNiB~wXLASe7I01qA^knmf?`_* zOGlz=XT63?s{)&Idd46x6&$(Ab@My};^Y3ckF?y+-qvrz^CQQI{3HOwNGUPL91nXk zTvxP}wu+f4Ch%pN1RcggTQKZ~F zs74ss`*&JuYb+(?i$hlx{Eg>KWG6F-#r5{un4~1-EtOAX`aTi|ZnU2|m!kW7eT75j zO`(A~7FD6*`lQr0j;Bx#qq|-y=!>b~rC-p~y!U)^V~`XIr%fgQ-_g>cb+jRJCDHur z(+`%WiWvmgEQ!K*Vhu;1k%~1|iX1G2@+?G`-=)lOw~6hebs-IG(pRs zOb{x3)`8YbZFA6cO5!DJL4-i?EM}RI)IW1C=&q922RESUr(yV)h9n{<{U5e!pB)e! z%*7&CrdxA?Jg7fydY$6Ov`SZmiB%rWI;_&(I>?X=d0afq1A-4D2j?hiQBjcQZ+%MX*%c73h>8}umx>Yk zu%9A@CVcq*DjVu#CwPYRDx2nM8(rYbipb?~!Xv8eZmGZ_P&jHD8S!cH5&Y7X#-e-g^BJ47w zJ=YWa$dfPc|NI`CWwK#epKw_#qw@4m)YeGnj2wR@*m1pDeI?EE??9?yI*z>wWP90; z+qsoIH?Om_4DTqV?2_qkA=Ps-qwahZR14~k2=m2jAu{n#>U;2yYgd`Kq^4}6X}NKYt$M$s_fw8pV9QRPl8=H4k#gS1^M^#1Fr+!c}) za~LH(u*dYD?@|@`52N!Ts9hphYz04~oJ6?<`0DlobtEGk)b-Q)0>q)?x17*u9ru*& zYTu7!Qr?gImCE83qE|s?LG!M60&wSxU#l2l*<9} z&{ro~y}D^!A)u%{9m45WkeHB5hpdTccw6XYwCuDHy)m;)&Up`HcbI0M8YSKz-Y)(B zTli^XzGAR6X1yBm{Nx)UkzfbO?hlZ${iLwJhBuu&#-?gcNP(xT#8Z<$daYs_*~N5~ zhOr-VX%k}P!}}Vxz8AUUFH;qX&Q$r%p#X*iRYx8429g>nUoWodB?xZW8p7y*T3JdgT+tzFIjJ| z$X{d&TB>l6wj5fxEB0$o7r75{NuXjK6V+{afG#yk{~3Y&PC&dSsO$+GdB&AAZvFa1 zOZK;IdxUWe=GqjJ5Pd1J^@BnFADubOZs>8dU#I&^rp+AlEsOTcoMSj8M{AiGg=gK< ze~X`_zI1^l+yRtY_-}(8n?bw8w${K z2}LeY9MEb%k}ym^+?aNudB+yp;yb80EB(Q5)pS352CzlkdfF8FTqm=$8tHavHIl4l zr>1E6u6cr&eF~IvS_T#>g>1694{4KDQ_>p@u$AVykK1udpf0TngCXH z5zQ&a+HwldYT^w$?BQ@e4IBsgOQ`y+1dLPf%$r9PR|0DDS<;Wh;@ml2YMS!$J#gkr z2I8`ly?+YO>2-{fM+YoYbrn@32CkVywO~r$DxLswt&x0x907iFJj0q5;NdTp^x=HG xOgkb~Yyd%RnTwfZ2r)bvM0@({f35M3^J$0L{S2#8=6??+Kub+ewOR!p_CK+I_KyGn diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon167.png deleted file mode 100644 index 8524768f8d764da7e9c452a444208708f2d18ff1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4692 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 diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon180.png deleted file mode 100644 index 60a64703c0f11d08705cd607f7751338707f5919..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5192 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 nB>N_)Q7iwV%v7MwAoJ}E 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 T3-0p800000NkvXXu0mjfSlal| diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon58.png deleted file mode 100644 index 1ad04f004b638bf781012290d78e4138f97bbe5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1761 zcmV<71|Io|P)4P%ubY|S^%$zf~ zmwTOa@12BA$oV-Y9!V&U%c=j==#_}M2ylE}1m>yyDoGsZ#Yy zNX}RO*f(MzmKS&u`qiajIyW{Y_LC%m2NqT@Ic|QpvYqwNgBK7n5X%c(3k^?2>EOA` zqGaXjE7H9BiJ55fh0iJRW}@=&(@R^E1hLB>kE%PS6eP@VZVdtn(fh;5DPKg!j;fJZ%)wH{Wn#~V&#n(o1URS zsyS`0Tu2m;-H}z9O^h`!UZAFr@?0a7Z;pYOi0uZhgzh=rOEDi`FIkKtVu*gEcSM!h zmb#_XR$akjlg$JI75MXgWkG7IUnYJ+X=1J!qJ%jXVj{L1I2QU%?=?DgV^U?)92DZm zV?>``xT1#kZdgUt!2n?|0>*6ae4tikA9FAlJ}kjmMQm_z3LB5sZYHBKdbex9_Hv@K z%Y?q@9-)b7vJ6X$3h0B4tH__=#*`9^efY@IQfghn*=E2Nb8sR8lrQlu`Ca_Rmm6>Z z7bkEe^w8M>x;hoNUvWu_GZJPVpI;bMTsBpf(@U$Ch(-gk0T#WpsaB1{7ISQ~Y48mW z;Nk?@LjM_?q{BV-D=veoOmJoncVDC1GwGGz(O5@o7ZkGCIJMPO(7K9b6M_wF?Xqd< zo4J6KF0_U2<1=T3x0qc6G6#g+^=N{QTpChC!GZuCY*|eU{Rw)LMN7a2wwbaCdn_dE zzy+Lip(XU4-+SoTFyEcnH3?HRV^%-;Ylx;|>8v&^Dy459ZJ_1zio`68!6s8SO(6sq zaN75WUKiF9+8MruR3=w5)hzA^Z1clVBuXc)+8@e);xX7bfygR&FsIIt-gQ+==(c;S z#J*CO1qZBF&M&6TRmskZXaMOU6&?jn_(BqY5 z>Y|^?uOh;yp6w0QR1`>tiEz_-{Zu!N#(nhRndJV$7;LOgPyZQ*J2yCin+~*u!qKj# zItsG2IDi{ZH+E6j=D|ht=qWxKNxaA6E>3Wna>=z1gy?*#>|g_1^BGspGro&OWRc(k zPP)(*y0WZ7Z-kICr3#g7($P*LCjE>7S`Xh~s!b~bPTo0XSkRm2T(M`QZbv-`Imx*Ulqk*aI* zaqa*=>61?nFb8w3wuiA&zyVyGz>!cm-pY3xEsMEiY)Th4FVrtqWp%V~gxW82)>4^N z*H{>GR?m$^s6BN^M=4^iEjT7(gM(8z7K#9Sn(M_`)oSH2zHDCla&cmK)bb|4nAMIO ze4S3gI9L8AD+T9c#C8K8JF)I68NBWK`5pE`q^OR#h~-u!e7P2i$UDq7^*uZr< zbp8sR<$nYxb8EKH|BltapZ^j3+PZt_u^PR*lT6;TCNxu^yFH$j(!JXvbmHfQ>a0>O z+2k{tOWD%ln$M`tD&>+*KBKvmEgi1;jOwgXF4_DG_&r&PcxYTT00000NkvXXu0mjf DptxI> diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon60.png deleted file mode 100644 index 2dd52620a8f15577e56ec7fe8e671988dd17ab0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2537 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 diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon76.png deleted file mode 100644 index b058cae2f440e5a5875e45c036c99f1fb6356046..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2332 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 Kc&(xHv;P1B$EH01 diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon87.png deleted file mode 100644 index 4954a4bd33f613d45f74dc0b12beb516e3b38661..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2758 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 z - - - - - diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj deleted file mode 100644 index 53f2df9e..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - Debug - iPhoneSimulator - {5EFF7561-35C1-4C62-B0BE-A76E37DCEB32} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {edc1b0fa-90cd-4038-8fad-98fe74adb368} - Xamarin.iOS10 - Exe - bin\$(Configuration)\$(Framework) - LaunchDarkly.Sdk.Client.iOS.Tests - LaunchDarkly.ClientSdk.iOS.Tests - Resources - true - NSUrlSessionHandler - true - iPhoneSimulator;iPhone;AnyCPU - - - true - full - false - DEBUG - prompt - 4 - false - x86_64 - None - true - - - none - true - prompt - 4 - None - x86_64 - false - - - true - full - false - DEBUG - prompt - 4 - false - ARM64 - Entitlements.plist - iPhone Developer - true - - - none - true - prompt - 4 - Entitlements.plist - ARM64 - false - iPhone Developer - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchScreen.storyboard b/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchScreen.storyboard deleted file mode 100644 index f18534b4..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs deleted file mode 100644 index 963a7b4a..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.cs +++ /dev/null @@ -1,13 +0,0 @@ -using UIKit; - -// For more details about how this test project works, see CONTRIBUTING.md -namespace LaunchDarkly.Sdk.Client.iOS.Tests -{ - public class Application - { - static void Main(string[] args) - { - UIApplication.Main(args, null, typeof(AppDelegate)); - } - } -} diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.storyboard b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.storyboard deleted file mode 100644 index a3f40d68..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Main.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Properties/AssemblyInfo.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index c4e33f39..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Xunit; - -// We must disable all parallel test running by XUnit in order for LogSink to work. -[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Resources/LaunchScreen.xib b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Resources/LaunchScreen.xib deleted file mode 100644 index e745d4e0..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Resources/LaunchScreen.xib +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs deleted file mode 100644 index 6f57d93b..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/XunitConsoleLoggingResultChannel.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Xunit.Runners; - -// This class is used in both iOS and Android test projects. It is not applicable in .NET Standard. -// -// It is based on the TextWriterResultChannel provided by xunit.runner.devices, but it has better -// diagnostic output for our purposes: captured test output is included for failed tests, and any -// multi-line error messages are logged as individual lines so that our log-parsing logic won't be -// confused by lines with no log prefix. - -namespace LaunchDarkly.Sdk.Client.Tests -{ - public class XunitConsoleLoggingResultChannel : IResultChannel - { - private readonly TextWriter _writer; - private readonly object _lock = new object(); - - private int _passed, _skipped, _failed; - - public XunitConsoleLoggingResultChannel() - { - _writer = Console.Out; - } - - public Task OpenChannel(string message = null) - { - lock (_lock) - { - _failed = _passed = _skipped = 0; - _writer.WriteLine("[Runner executing:\t{0}]", message); - return Task.FromResult(true); - } - } - - public Task CloseChannel() - { - lock (_lock) - { - var total = _passed + _failed; - _writer.WriteLine("Tests run: {0} Passed: {1} Failed: {2} Skipped: {3}", total, _passed, _failed, _skipped); - return Task.FromResult(true); - } - } - - public void RecordResult(TestResultViewModel result) - { - lock (_lock) - { - switch (result.TestCase.Result) - { - case TestState.Passed: - _writer.Write("\t[PASS] "); - _passed++; - break; - case TestState.Skipped: - _writer.Write("\t[SKIPPED] "); - _skipped++; - break; - case TestState.Failed: - _writer.Write("\t[FAIL] "); - _failed++; - break; - default: - _writer.Write("\t[INFO] "); - break; - } - _writer.Write(result.TestCase.DisplayName); - - var message = result.ErrorMessage; - if (!string.IsNullOrEmpty(message)) - { - _writer.Write(" : {0}", message.Replace("\r\n", "\\r\\n")); - } - _writer.WriteLine(); - - var stacktrace = result.ErrorStackTrace; - if (!string.IsNullOrEmpty(result.ErrorStackTrace)) - { - WriteMultiLine(result.ErrorStackTrace, "\t\t"); - } - } - - if (result.HasOutput && result.TestCase.Result != TestState.Passed) - { - _writer.WriteLine(">>> test output follows:"); - WriteMultiLine(result.Output, ""); - } - } - - private void WriteMultiLine(string text, string prefix) - { - var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) - { - _writer.WriteLine(prefix + line); - } - } - } -} From df7cb97f3ad92df09cf5901c2baf6dcb3da287ec Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 18 Dec 2023 17:24:43 -0600 Subject: [PATCH 491/499] fixing action --- .github/actions/ci/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 964e166b..330d44d0 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -50,6 +50,7 @@ runs: run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-windows src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Run Unit Tests for Net7 + shell: bash run: | dotnet restore tests/LaunchDarkly.ClientSdk.Tests dotnet test -v=normal \ From 23dcb7bb1a16c810aaa18c4369fdca625ef76eb4 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 18 Dec 2023 17:25:32 -0600 Subject: [PATCH 492/499] fixing action --- .github/actions/ci/action.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 330d44d0..bfda8fd7 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -59,10 +59,12 @@ runs: - name: Build Contract Tests if: inputs.run_tests == 'true' + shell: bash run: dotnet build contract-tests/TestService.csproj - name: Run Contract Tests if: inputs.run_tests == 'true' + shell: bash run: | dotnet contract-tests/bin/release/net7.0/ContractTestService.dll > test-service.log 2>&1 & disown curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end \ @@ -70,6 +72,7 @@ runs: - name: Build Test App if: inputs.run_tests == 'true' + shell: bash run: | dotnet build /restore /p:Configuration=release \ tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj From f2e4294308e71decea312a7c16ed779e599855c4 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 19 Dec 2023 09:23:39 -0600 Subject: [PATCH 493/499] Adjusting readme --- README.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9c012119..95902174 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,19 @@ For using LaunchDarkly in *server-side* .NET applications, refer to our [Server- ## LaunchDarkly overview [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today! - -[![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly) ## Supported platforms This version of the SDK is built for the following targets: +* .Net Standard 2.0 * .Net 7 Android, for use with Android 5.0 (Android API 21) and higher. * .Net 7 iOS, for use with iOS 11 and higher. * .Net 7 macOS (using Mac Catalyst), for use with macOS 10.15 and higher. * .Net 7 Windows (using WinUI), for Windows 11 and Windows 10 version 1809 or higher. -* .NET 7, for use with any runtime platform that supports .NET Standard 2.1, or in portable .NET Standard library code. +* .NET 7 -The .NET 7 target does not use any MAUI packages and has no OS-specific code. This allows the SDK to be used in a desktop .NET Framework or .NET 7.0 application. However, due to the lack of OS-specific integration, SDK functionality will be limited in those environments: for instance, the SDK will not be able to detect whether networking is turned on or off. +The .Net Standard and .Net 7.0 targets have no OS-specific code. This allows the SDK to be used in a desktop .NET Framework or .NET 7.0 application. However, due to the lack of OS-specific integration, SDK functionality will be limited in those environments: for instance, the SDK will not be able to detect whether networking is turned on or off. The .NET build tools should automatically load the most appropriate build of the SDK for whatever platform your application or library is targeted to. @@ -34,21 +33,6 @@ The .NET build tools should automatically load the most appropriate build of the Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/client-side/dotnet) for instructions on getting started with using the SDK. -## Signing - -The published version of this assembly is digitally signed with Authenticode and [strong-named](https://docs.microsoft.com/en-us/dotnet/framework/app-domains/strong-named-assemblies). Building the code locally in the default Debug configuration does not use strong-naming and does not require a key file. The public key file is in this repository at `LaunchDarkly.pk` as well as here: - -``` -Public Key: -0024000004800000940000000602000000240000525341310004000001000100 -058a1dbccbc342759dc98b1eaba4467bfdea062629f212cf7c669ff26b4e2ff3 -c408292487bc349b8a687d73033ff14dbf861e1eea23303a5b5d13b1db034799 -13bd120ba372cf961d27db9f652631565f4e8aff4a79e11cfe713833157ecb5d -cbc02d772967d919f8f06fbee227a664dc591932d5b05f4da1c8439702ecfdb1 - -Public Key Token: 90b24964a3dfb906 -``` - ## Learn more Read our [documentation](https://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/sdk/client-side/dotnet). From 85e5bd2394fa1c469a2203ae17323dc1d9725eec Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 19 Dec 2023 14:22:45 -0600 Subject: [PATCH 494/499] ci: fixing contract tests in action --- .github/actions/ci/action.yml | 9 ++++----- .github/workflows/ci.yml | 4 ++-- src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index bfda8fd7..f9eadb9b 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -19,7 +19,7 @@ runs: - name: Install MAUI Workload shell: bash - run: dotnet workload install maui-android maui-ios maui-windows maui-maccatalyst --ignore-failed-sources + run: dotnet workload restore - name: Restore Dependencies shell: bash @@ -60,15 +60,14 @@ runs: - name: Build Contract Tests if: inputs.run_tests == 'true' shell: bash - run: dotnet build contract-tests/TestService.csproj + run: dotnet build /p:Configuration=release contract-tests/TestService.csproj - name: Run Contract Tests if: inputs.run_tests == 'true' shell: bash run: | dotnet contract-tests/bin/release/net7.0/ContractTestService.dll > test-service.log 2>&1 & disown - curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end \ - -junit /tmp/circle-reports/contract-tests-junit.xml" sh + curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end" sh - name: Build Test App if: inputs.run_tests == 'true' @@ -77,6 +76,7 @@ runs: dotnet build /restore /p:Configuration=release \ tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj + # TODO: The test application does not auto-execute at the moment, so this is commented out. For now this must be done manually. # - name: Set up JDK 17 # if: inputs.run_tests == 'true' # uses: actions/setup-java@v3 @@ -88,7 +88,6 @@ runs: # if: inputs.run_tests == 'true' # uses: android-actions/setup-android@v3 - # TODO: Tests are not auto executing, so this is commented out. For now this must be done manually. # - name: Run Android Test App on Emulator # if: inputs.run_tests == 'true' # uses: reactivecircus/android-emulator-runner@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c45ab37..f27af7b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: jobs: ci-build: - runs-on: macos-latest-large + runs-on: macos-latest permissions: id-token: write contents: read @@ -24,6 +24,6 @@ jobs: name: Get secrets with: aws_assume_role: ${{ vars.AWS_ROLE_ARN }} - ssm_parameter_pairs: '/production/common/releasing/digicert/host = DIGICERT_HOST,/production/common/releasing/digicert/api_key = DIGICERT_API_KEY,/production/common/releasing/digicert/client_cert_file_b64 = DIGICERT_CLIENT_CERT_FILE_B64,/production/common/releasing/digicert/client_cert_password = DIGICERT_CLIENT_CERT_PASSWORD,/production/common/releasing/digicert/code_signing_cert_sha1_hash = DIGICERT_CODE_SIGNING_CERT_SHA1_HASH' + ssm_parameter_pairs: '/production/common/releasing/placeholder = placeholder' - uses: ./.github/actions/ci diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index a7a6c67e..2ffd180d 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -31,6 +31,7 @@ true snupkg LaunchDarkly.Sdk.Client + false 1570,1571,1572,1573,1574,1580,1581,1584,1591,1710,1711,1712 From cf0e22cd0670e243c1deb9e006cb55ca6eaee783 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 19 Dec 2023 15:12:50 -0600 Subject: [PATCH 495/499] ci: reverting to use debug varients for CI action --- .github/actions/ci/action.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index f9eadb9b..230dcf38 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -27,27 +27,27 @@ runs: # - name: Build for NetStandard2.0 # shell: bash - # run: dotnet build /p:Configuration=release /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + # run: dotnet build /p:Configuration=debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7 shell: bash - run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + run: dotnet build /p:Configuration=debug /p:TargetFramework=net7.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7-android shell: bash - run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-android src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + run: dotnet build /p:Configuration=debug /p:TargetFramework=net7.0-android src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7-ios shell: bash - run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-ios src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + run: dotnet build /p:Configuration=debug /p:TargetFramework=net7.0-ios src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7-windows shell: bash - run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-maccatalyst src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + run: dotnet build /p:Configuration=debug /p:TargetFramework=net7.0-maccatalyst src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7-maccatalyst shell: bash - run: dotnet build /p:Configuration=release /p:TargetFramework=net7.0-windows src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + run: dotnet build /p:Configuration=debug /p:TargetFramework=net7.0-windows src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Run Unit Tests for Net7 shell: bash @@ -60,20 +60,20 @@ runs: - name: Build Contract Tests if: inputs.run_tests == 'true' shell: bash - run: dotnet build /p:Configuration=release contract-tests/TestService.csproj + run: dotnet build /p:Configuration=debug contract-tests/TestService.csproj - name: Run Contract Tests if: inputs.run_tests == 'true' shell: bash run: | - dotnet contract-tests/bin/release/net7.0/ContractTestService.dll > test-service.log 2>&1 & disown + dotnet contract-tests/bin/debug/net7.0/ContractTestService.dll > test-service.log 2>&1 & disown curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end" sh - name: Build Test App if: inputs.run_tests == 'true' shell: bash run: | - dotnet build /restore /p:Configuration=release \ + dotnet build /restore /p:Configuration=debug \ tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj # TODO: The test application does not auto-execute at the moment, so this is commented out. For now this must be done manually. @@ -95,7 +95,7 @@ runs: # api-level: 27 # script: | # dotnet run --framework net7.0-android --project tests/LaunchDarkly.ClientSdk.Device.Tests/LaunchDarkly.ClientSdk.Device.Tests.csproj - # adb install tests/LaunchDarkly.ClientSdk.Device.Tests/bin/release/net7.0-android/com.LaunchDarkly.ClientSdk.Device.Tests-Signed.apk + # adb install tests/LaunchDarkly.ClientSdk.Device.Tests/bin/debug/net7.0-android/com.LaunchDarkly.ClientSdk.Device.Tests-Signed.apk # ( adb logcat DOTNET:D AndroidRuntime:D & ) | tee test-run.log | grep -q 'Tests run:' # cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,7- # if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi From ffe61b57c2b0fbcef1d3d6b5f6f3b2eeeb7279be Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 19 Dec 2023 17:15:32 -0600 Subject: [PATCH 496/499] Reintroducing netstand2.0 target --- .github/actions/ci/action.yml | 6 +++--- .../LaunchDarkly.ClientSdk.csproj | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 230dcf38..f0208696 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -25,9 +25,9 @@ runs: shell: bash run: dotnet restore src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - # - name: Build for NetStandard2.0 - # shell: bash - # run: dotnet build /p:Configuration=debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj + - name: Build for NetStandard2.0 + shell: bash + run: dotnet build /p:Configuration=debug /p:TargetFramework=netstandard2.0 src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - name: Build for Net7 shell: bash diff --git a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj index 2ffd180d..9b76a473 100644 --- a/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj +++ b/src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj @@ -6,10 +6,10 @@ a situation where we need to build only the .NET Standard target and do not want to even mention the Android/iOS/Mac/Windows targets, because we're in an environment that doesn't have the MAUI tools installed. That is currently the case in - the release phase where we build HTML documentation. --> + the release phase where we build HTML documentation. --> - net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst;net7.0-windows + netstandard2.0;net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst;net7.0-windows $(BUILDFRAMEWORKS) true Library @@ -50,7 +50,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -71,7 +71,7 @@ - + @@ -86,7 +86,7 @@ - + From 3b819a9c3e4a447699a05ad9d7d1d7eaf7646bc4 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 19 Dec 2023 17:45:28 -0600 Subject: [PATCH 497/499] ci: using macos-13 runner for xcode 14.3+ dependency --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f27af7b8..6958dbba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: jobs: ci-build: - runs-on: macos-latest + runs-on: macos-13 permissions: id-token: write contents: read From 396e3381d50b30c10bf40080376001218f79ebb3 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 20 Dec 2023 10:25:52 -0600 Subject: [PATCH 498/499] Updating documentation and removing deprecated UWP --- CONTRIBUTING.md | 2 +- src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48388f5d..88c5dae7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We encourage pull requests and other contributions from the community. Before su ### Prerequisites -The .NET 7 target requires only the .NET Core 2.1 SDK or higher, while the iOS, Android, MacCatalys, and Windows targets require the corresponding MAUI SDKs. +The .NET Standard target requires only the .NET Core 2.1 SDK or higher. The iOS, Android, MacCatalys, and Windows targets require Net 7.0 or later. ### Building diff --git a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs index d2b15f44..f15e5363 100644 --- a/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs +++ b/src/LaunchDarkly.ClientSdk/PlatformSpecific/DeviceInfo.maui.cs @@ -31,7 +31,7 @@ private static string PlatformToFamilyString(Devices.DevicePlatform platform) { { return "Apple"; } - else if(platform == Devices.DevicePlatform.UWP || platform == Devices.DevicePlatform.WinUI) + else if(platform == Devices.DevicePlatform.WinUI) { return "Windows"; } From 6ab18e85ffacc0cc0eefdc3de8f13340bb804ccb Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 20 Dec 2023 10:43:17 -0600 Subject: [PATCH 499/499] ci: removing circle ci --- .circleci/config.yml | 203 ------------------ .circleci/scripts/LICENSE | 21 -- .circleci/scripts/circle-android | 88 -------- .../scripts/macos-install-android-sdk.sh | 69 ------ .circleci/scripts/macos-install-xamarin.sh | 87 -------- 5 files changed, 468 deletions(-) delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/scripts/LICENSE delete mode 100755 .circleci/scripts/circle-android delete mode 100755 .circleci/scripts/macos-install-android-sdk.sh delete mode 100755 .circleci/scripts/macos-install-xamarin.sh diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 3b8da7ac..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,203 +0,0 @@ -version: 2.1 - -orbs: - android: circleci/android@2.1.2 - -workflows: - test: - jobs: - - test_dotnet_standard - - build_android_and_ios - - test_android: - requires: - - build_android_and_ios - - test_ios: - requires: - - build_android_and_ios - -jobs: - test_dotnet_standard: - # Technically we could omit the "build the SDK" step here if we made this job depend on the build products - # from build_android_and_ios. However, setting up the Linux build is so much faster than the Mac host that - # it's better to let this one run independently so we can get quick CI feedback for any basic problems. - docker: - - image: mcr.microsoft.com/dotnet/sdk:6.0-focal - environment: - ASPNETCORE_SUPPRESSSTATUSMESSAGES: "true" # suppresses annoying debug output from embedded HTTP servers in tests - TEST_HARNESS_PARAMS: -junit /tmp/circle-reports/contract-tests-junit.xml - TESTFRAMEWORK: net6.0 - steps: - - checkout - - run: mkdir -p /tmp/circle-reports - - run: dotnet restore src/LaunchDarkly.ClientSdk - - run: dotnet build src/LaunchDarkly.ClientSdk -f netstandard2.0 - - run: dotnet restore tests/LaunchDarkly.ClientSdk.Tests - - run: - name: run unit tests - command: | - dotnet test -v=normal \ - --logger:"junit;LogFilePath=/tmp/circle-reports/unit-tests.xml" \ - tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj - - - run: - name: build contract tests - command: ./scripts/build-contract-tests.sh - - run: - name: run contract test service - command: ./scripts/start-contract-test-service.sh - background: true - - run: - name: run contract tests - command: ./scripts/run-contract-tests.sh - - - store_test_results: - path: /tmp/circle-reports - - build_android_and_ios: - macos: - xcode: "13.4.1" - resource_class: macos.x86.medium.gen2 - - steps: - - checkout - - - run: - name: Install .NET/Xamarin build tools - command: .circleci/scripts/macos-install-xamarin.sh android ios - - - run: - name: Install Android SDK - command: .circleci/scripts/macos-install-android-sdk.sh 27 - - - run: - name: Build SDK for MonoAndroid81 - # Deliberately building only the version of MonoAndroid that we will use in the tests; - # otherwise we would have to install more SDKs - command: | - msbuild /restore /p:Configuration=debug /p:TargetFramework=MonoAndroid81 \ - src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - - - run: - name: Build SDK for Xamarin.iOS10 - # Unfortunately msbuild doesn't allow us to specify multiple specific target frameworks in one command - command: | - msbuild /restore /p:Configuration=debug /p:TargetFramework=Xamarin.iOS10 \ - src/LaunchDarkly.ClientSdk/LaunchDarkly.ClientSdk.csproj - - - run: - name: Build Android test project - command: | - msbuild /restore /p:Configuration=Debug /t:SignAndroidPackage \ - tests/LaunchDarkly.ClientSdk.Android.Tests/LaunchDarkly.ClientSdk.Android.Tests.csproj - - - run: - name: Build iOS test project - command: | - msbuild /restore /p:Configuration=Debug /p:Platform=iPhoneSimulator \ - tests/LaunchDarkly.ClientSdk.iOS.Tests/LaunchDarkly.ClientSdk.iOS.Tests.csproj - # Note that we must specify Platform=iPhoneSimulator here explicitly because, when using a current - # version of msbuild with a project file that uses MSBuild.Sdk.Extras, it seems like Platform does *not* - # default to an empty string (I think it defaults to "AnyCPU"), therefore it will try to build it for a - # real iPhone, which will fail because it can't do code signing. We want a debug build that we will only - # be running in the simulator. - - - persist_to_workspace: - root: tests - paths: - - LaunchDarkly.ClientSdk.Android.Tests/bin/Debug - - LaunchDarkly.ClientSdk.iOS.Tests/bin/Debug - - test_android: - executor: - name: android/android-machine - tag: 2022.07.1 - resource-class: large - - steps: - - checkout - - - attach_workspace: - at: tests - - - android/create-avd: - avd-name: ci-android-avd - install: true - system-image: system-images;android-27;default;x86 - - - android/start-emulator: - avd-name: ci-android-avd - no-window: true - wait-for-emulator: true - restore-gradle-cache-post-emulator-launch: false # this isn't a Gradle project - post-emulator-launch-assemble-command: "" # this isn't a Gradle project - - - run: - name: Start capturing log output - command: adb logcat mono-stdout:D AndroidRuntime:D *:S | tee test-run.log - # mono-stdout is the default tag for standard output from a Xamarin app - that's where our test runner output goes - background: true - no_output_timeout: 10m - - - run: - name: Deploy app to emulator - command: adb install tests/LaunchDarkly.ClientSdk.Android.Tests/bin/Debug/com.launchdarkly.xamarinandroidtests-Signed.apk - - - run: - name: Start app in emulator - command: adb shell monkey -p com.launchdarkly.xamarinandroidtests 1 - - - run: - name: Wait for tests to finish running - # https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found - command: "( tail -f -c+0 test-run.log & ) | grep -q 'Tests run:'" - - - run: - name: Show all test output - command: | - cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,7- - if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi - # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a - # JUnit-compatible test results file; you'll just have to look at the output. - - test_ios: - macos: - xcode: "13.4.1" - resource_class: macos.x86.medium.gen2 - - steps: - - checkout - - - attach_workspace: - at: tests - - - run: - name: Start simulator - command: | - xcrun simctl create xm-ios com.apple.CoreSimulator.SimDeviceType.iPhone-12 com.apple.CoreSimulator.SimRuntime.iOS-15-5 - xcrun simctl boot xm-ios - - - run: - name: Load test app into simulator - command: xcrun simctl install "xm-ios" tests/LaunchDarkly.ClientSdk.iOS.Tests/bin/Debug/xamarin.ios10/LaunchDarkly.ClientSdk.iOS.Tests.app - - - run: - name: Start capturing log output - command: xcrun simctl spawn booted log stream --predicate 'senderImagePath contains "LaunchDarkly.ClientSdk.iOS.Tests"' | tee test-run.log - background: true - - - run: - name: Launch test app in simulator - command: xcrun simctl launch "xm-ios" com.launchdarkly.ClientSdkTests - - - run: - name: Wait for tests to finish running - # https://superuser.com/questions/270529/monitoring-a-file-until-a-string-is-found - command: "( tail -f -c+0 test-run.log & ) | grep -q 'Tests run:'" - - - run: - name: Show all test output - command: | - cat test-run.log | tr -s ' ' | cut -d ' ' -f 1,2,9- - if grep '\[FAIL\]' test-run.log >/dev/null; then exit 1; fi - # "exit 1" causes the CI job to fail if there were any test failures. Note that we still won't have a - # JUnit-compatible test results file; you'll just have to look at the output. diff --git a/.circleci/scripts/LICENSE b/.circleci/scripts/LICENSE deleted file mode 100644 index 13115564..00000000 --- a/.circleci/scripts/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017-2019 Circle Internet Services, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/.circleci/scripts/circle-android b/.circleci/scripts/circle-android deleted file mode 100755 index 4524b60b..00000000 --- a/.circleci/scripts/circle-android +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python - -# See LICENSE file in this directory for copyright and license information -# Above LICENSE notice added 10/16/2019 by Gavin Whelan - -from sys import argv, exit, stdout -from time import sleep, time -from os import system -from subprocess import check_output, CalledProcessError -from threading import Thread, Event -from functools import partial - -class StoppableThread(Thread): - - def __init__(self): - super(StoppableThread, self).__init__() - self._stop_event = Event() - self.daemon = True - - def stopped(self): - return self._stop_event.is_set() - - def run(self): - while not self.stopped(): - stdout.write('.') - stdout.flush() - sleep(2) - - def stop(self): - self._stop_event.set() - -def shell_getprop(name): - try: - return check_output(['adb', 'shell', 'getprop', name]).strip() - except CalledProcessError as e: - return '' - -start_time = time() - -def wait_for(name, fn): - stdout.write('Waiting for %s' % name) - spinner = StoppableThread() - spinner.start() - stdout.flush() - while True: - if fn(): - spinner.stop() - time_taken = int(time() - start_time) - print('\n%s is ready after %d seconds' % (name, time_taken)) - break - sleep(1) - -def device_ready(): - return system('adb wait-for-device') == 0 - -def shell_ready(): - return system('adb shell true &> /dev/null') == 0 - -def prop_has_value(prop, value): - return shell_getprop(prop) == value - -def wait_for_sys_prop(name, prop, value): - # return shell_getprop('init.svc.bootanim') == 'stopped' - wait_for(name, partial(prop_has_value, prop, value)) - -usage = """ -%s, a collection of tools for CI with android. - -Usage: - %s wait-for-boot - wait for a device to fully boot. - (adb wait-for-device only waits for it to be ready for shell access). -""" - -if __name__ == "__main__": - - if len(argv) != 2 or argv[1] != 'wait-for-boot': - print(usage % (argv[0], argv[0])) - exit(0) - - wait_for('Device', device_ready) - wait_for('Shell', shell_ready) - wait_for_sys_prop('Boot animation complete', 'init.svc.bootanim', 'stopped') - wait_for_sys_prop('Boot animation exited', 'service.bootanim.exit', '1') - wait_for_sys_prop('System boot complete', 'sys.boot_completed', '1') - wait_for_sys_prop('GSM Ready', 'gsm.sim.state', 'READY') - #wait_for_sys_prop('init.svc.clear-bcb' ,'init.svc.clear-bcb', 'stopped') - - diff --git a/.circleci/scripts/macos-install-android-sdk.sh b/.circleci/scripts/macos-install-android-sdk.sh deleted file mode 100755 index 854f47ff..00000000 --- a/.circleci/scripts/macos-install-android-sdk.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -# Standard steps for installing the desired version(s) of the Android SDK tools on -# a MacOS system. This is used both in our CI build and in releases. -# -# This updates the path and other variables in $BASH_ENV; to get the new values, -# run "source $BASH_ENV" (not necessary in CircleCI because CircleCI starts a new -# shell for each step). - -# Usage: -# macos-install-android-sdk.sh 27 - installs Android API 27 -# macos-install-android-sdk.sh 27 28 - installs Android API 27 & 28... etc. - -set -e - -if [ -z "$1" ]; then - echo "must specify at least one Android API version" >&2 - exit 1 -fi - -ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL=https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip -ANDROID_BUILD_TOOLS_VERSION=26.0.2 - -if [ -z "$ANDROID_HOME" ]; then - export ANDROID_HOME=/usr/local/share/android-sdk - echo "export ANDROID_HOME=$ANDROID_HOME" >> $BASH_ENV -fi -if [ -z "$ANDROID_SDK_HOME" ]; then - export ANDROID_SDK_HOME=$ANDROID_HOME - echo "export ANDROID_SDK_HOME=$ANDROID_SDK_HOME" >> $BASH_ENV -fi -if [ -z "$ANDROID_SDK_ROOT" ]; then - export ANDROID_SDK_ROOT=$ANDROID_HOME - echo "export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $BASH_ENV -fi - -for addpath in \ - /usr/local/share/android-sdk/cmdline-tools/latest/bin \ - /usr/local/share/android-sdk/tools/bin \ - /usr/local/share/android-sdk/platform-tools -do - echo "export PATH=\$PATH:$addpath" >> $BASH_ENV -done -source $BASH_ENV - -# Download the core Android SDK command-line tools - we don't use Homebrew for this, -# because the version they had (as of March 2021) was out of date and incompatible -# with Java 11. -mkdir -p $ANDROID_HOME -mkdir -p $ANDROID_HOME/cmdline-tools -sdk_temp_dir=/tmp/android-sdk-download -rm -rf $sdk_temp_dir -mkdir -p $sdk_temp_dir -echo "Downloading Android tools from $ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL" -curl "$ANDROID_SDK_CMDLINE_TOOLS_DOWNLOAD_URL" >$sdk_temp_dir/android-sdk.zip -cd $sdk_temp_dir -unzip android-sdk.zip -mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest - -sdkmanager_args="platform-tools emulator" -sdkmanager_args="$sdkmanager_args build-tools;$ANDROID_BUILD_TOOLS_VERSION" -for apiver in "$@"; do - sdkmanager_args="$sdkmanager_args platforms;android-$apiver" - sdkmanager_args="$sdkmanager_args system-images;android-$apiver;default;x86" -done - -echo "Installing Android SDK packages: $sdkmanager_args" -yes | sdkmanager $sdkmanager_args | grep -v = || true -yes | sdkmanager --licenses >/dev/null diff --git a/.circleci/scripts/macos-install-xamarin.sh b/.circleci/scripts/macos-install-xamarin.sh deleted file mode 100755 index 35bbdde1..00000000 --- a/.circleci/scripts/macos-install-xamarin.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -# Standard steps for setting up Xamarin command-line build tools on a MacOS system, -# without using the interactive Visual Studio installer. This is used both in our CI -# build and in releases. -# -# This updates the path and other variables in $BASH_ENV; to get the new values, -# run "source $BASH_ENV" (not necessary in CircleCI because CircleCI starts a new -# shell for each step). - -# Formerly, we used Homebrew to install the .NET SDK, Mono, and Xamarin. This had the -# advantage of having stable package identifiers rather than download URLs. However, -# these Homebrew installers seem to be no longer maintained. So we are now downloading -# the lower-level installer packages directly from their current locations online, -# which will undoubtedly need to be updated in the future. - -# Usage: -# macos-install-xamarin.sh android - installs Xamarin.Android -# macos-install-xamarin.sh ios - installs Xamarin.iOS -# macos-install-xamarin.sh android ios - installs both - -set -e - -# The .NET SDK 6.0 installer is pinned to a specific version -# See: https://dotnet.microsoft.com/download/dotnet/6.0 -DOTNET_SDK_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/85962c99-0139-47f0-aa4c-b566b809b6a1/2008bf2ed757e67f382fe92c0fcca583/dotnet-sdk-6.0.406-osx-x64.pkg - -# Currently we are also pinning the rest of the installers to specific version URLs. -# Alternately, we could use the "latest stable" mode of boots: -# boots --stable Mono -# boots --stable Xamarin.Android -# boots --stable Xamarin.iOS -# Download URLs were found here: -# https://www.mono-project.com/download/stable/ -# https://github.com/xamarin/xamarin-android -# https://github.com/xamarin/xamarin-macios -MONO_INSTALLER_URL=https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.122.macos10.xamarin.universal.pkg -XAMARIN_ANDROID_INSTALLER_URL=https://aka.ms/xamarin-android-commercial-d16-8-macos -XAMARIN_IOS_INSTALLER_URL=https://download.visualstudio.microsoft.com/download/pr/7b60a920-c8b1-4798-b660-ae1a7294eb6d/bbdc2a9c6705520fd0a6d04f71e5ed3e/xamarin.ios-14.2.0.12.pkg - -for addpath in \ - /Library/Frameworks/Mono.framework/Commands \ - /usr/local/share/dotnet \ - $HOME/.dotnet/tools -do - echo "export PATH=\$PATH:$addpath" >> $BASH_ENV -done -source $BASH_ENV - -# Install .NET SDK -curl -s "$DOTNET_SDK_INSTALLER_URL" >/tmp/dotnet-sdk.pkg -sudo installer -package /tmp/dotnet-sdk.pkg -target / -rm /tmp/dotnet-sdk.pkg - -# The "boots" tool is a shortcut for downloading and running package installers. Since -# it is a .NET tool, we can't install it until we've already installed .NET above. -dotnet tool install --global boots - -# Install the basic Mono tools (including msbuild) -boots --url "$MONO_INSTALLER_URL" - -for arg in "$@" -do - case "$arg" in - android) - boots --url "$XAMARIN_ANDROID_INSTALLER_URL" - ;; - ios) - boots --url "$XAMARIN_IOS_INSTALLER_URL" - ;; - *) - echo "unsupported parameter: $arg" >&2 - exit 1 - esac -done - -# In the CircleCI environment, /Applications/Xcode.app might be a symlink. The build -# tools can be confused by this (https://github.com/xamarin/xamarin-macios/issues/11006) -# so we'll just move things around so that is a real file. - -if [[ -L /Applications/Xcode.app ]]; then - cd /Applications - real_xcode=`readlink Xcode.app` - rm Xcode.app - mv "$real_xcode" Xcode.app - sudo xcode-select --switch /Applications/Xcode.app -fi

_h+WLdolH)SU+J!dJi%kN41dyIj(P)fDrP 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 ZhJ?r43db^pa@tc**7&QYm&m`y{U4PhDpUXf literal 0 HcmV?d00001 diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Images/dotnet_bot.svg b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Images/dotnet_bot.svg new file mode 100644 index 00000000..51b1c330 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Images/dotnet_bot.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Raw/AboutAssets.txt b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Raw/AboutAssets.txt new file mode 100644 index 00000000..50b8a7b8 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with you package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Splash/splash.svg b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Splash/splash.svg new file mode 100644 index 00000000..62d66d7a --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Colors.xaml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Colors.xaml new file mode 100644 index 00000000..d183ec43 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Colors.xaml @@ -0,0 +1,44 @@ + + + + + #512BD4 + #DFD8F7 + #2B0B98 + White + Black + #E1E1E1 + #C8C8C8 + #ACACAC + #919191 + #6E6E6E + #404040 + #212121 + #141414 + + + + + + + + + + + + + + + #F7B548 + #FFD590 + #FFE5B9 + #28C2D1 + #7BDDEF + #C3F2F4 + #3E8EED + #72ACF1 + #A7CBF6 + + \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Styles.xaml b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Styles.xaml new file mode 100644 index 00000000..050b36c8 --- /dev/null +++ b/tests/LaunchDarkly.ClientSdk.Device.Tests/Resources/Styles/Styles.xaml @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs index 401aa7d3..48a11034 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Integrations/HttpConfigurationBuilderTest.cs @@ -81,7 +81,7 @@ public void UseReport() public void UserAgentHeader() { var config = Components.HttpConfiguration().CreateHttpConfiguration(mobileKey, applicationInfo); - Assert.Equal("XamarinClient/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)), + Assert.Equal("DotnetClientSide/" + AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)), HeadersAsMap(config.DefaultHeaders)["user-agent"]); // not configurable } diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs index 27b02681..59f8f776 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/FeatureFlagRequestorTests.cs @@ -74,7 +74,7 @@ string expectedQuery } // REPORT mode is known to fail in Android (ch47341) -#if !__ANDROID__ +#if !ANDROID [Theory] [InlineData("", false, "/msdk/evalx/context", "")] [InlineData("", true, "/msdk/evalx/context", "?withReasons=true")] diff --git a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs index 5a8ee515..2f43f4dc 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs +++ b/tests/LaunchDarkly.ClientSdk.Tests/Internal/DataSources/StreamingDataSourceTest.cs @@ -1,28 +1,28 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using LaunchDarkly.Sdk.Client.Subsystems; -using LaunchDarkly.Sdk.Internal.Concurrent; -using LaunchDarkly.Sdk.Internal.Events; -using LaunchDarkly.Sdk.Json; -using LaunchDarkly.TestHelpers.HttpTest; -using Xunit; -using Xunit.Abstractions; - +using LaunchDarkly.Sdk.Internal.Concurrent; +using LaunchDarkly.Sdk.Internal.Events; +using LaunchDarkly.Sdk.Json; +using LaunchDarkly.TestHelpers.HttpTest; +using Xunit; +using Xunit.Abstractions; + using static LaunchDarkly.Sdk.Client.MockResponses; using static LaunchDarkly.Sdk.Client.TestHttpUtils; -using static LaunchDarkly.TestHelpers.JsonAssertions; - -namespace LaunchDarkly.Sdk.Client.Internal.DataSources -{ - public class StreamingDataSourceTest : BaseTest +using static LaunchDarkly.TestHelpers.JsonAssertions; + +namespace LaunchDarkly.Sdk.Client.Internal.DataSources +{ + public class StreamingDataSourceTest : BaseTest { private static readonly TimeSpan BriefReconnectDelay = TimeSpan.FromMilliseconds(10); - - private readonly Context simpleUser = Context.New("me"); - - private MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); - + + private readonly Context simpleUser = Context.New("me"); + + private MockDataSourceUpdateSink _updateSink = new MockDataSourceUpdateSink(); + public StreamingDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { } private IDataSource MakeDataSource(Uri baseUri, Context context, Action modConfig = null) @@ -56,21 +56,21 @@ private void WithDataSourceAndServer(Handler responseHandler, Action initTask, TimeSpan.FromSeconds(1))); Assert.False(initTask.IsFaulted); Assert.True(dataSource.Initialized); - }); + }); } [Fact] @@ -177,17 +177,17 @@ public void DeleteDeletesFlag() Assert.Equal(2, receivedItem.Version); }); } - - [Fact] - public void PingCausesPoll() - { + + [Fact] + public void PingCausesPoll() + { var data = new DataSetBuilder() .Add("flag1", 1, LdValue.Of(true), 0) .Build(); var streamWithPing = Handlers.SSE.Start() .Then(PingEvent) - .Then(Handlers.SSE.LeaveOpen()); - + .Then(Handlers.SSE.LeaveOpen()); + using (var pollingServer = HttpServer.Start(PollingResponse(data))) { using (var streamingServer = HttpServer.Start(streamWithPing)) @@ -208,7 +208,7 @@ public void PingCausesPoll() Assert.True(dataSource.Initialized); } } - } + } } [Theory] @@ -345,6 +345,6 @@ private void DoTestAfterEmptyPut(Handler contentHandler, Action acti action(server); }); - } - } -} + } + } +} diff --git a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj index effb9ef5..0b044929 100644 --- a/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj +++ b/tests/LaunchDarkly.ClientSdk.Tests/LaunchDarkly.ClientSdk.Tests.csproj @@ -4,7 +4,7 @@ single framework that we are testing; this allows us to test with older SDK versions that would error out if they saw any newer target frameworks listed here, even if we weren't running those. --> - netcoreapp3.1 + net7.0 $(TESTFRAMEWORK) LaunchDarkly.ClientSdk.Tests LaunchDarkly.Sdk.Client diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs b/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs deleted file mode 100644 index ac8e1478..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/AppDelegate.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using Foundation; -using LaunchDarkly.Sdk.Client.Tests; -using UIKit; -using Xunit.Runner; -using Xunit.Sdk; - -namespace LaunchDarkly.Sdk.Client.iOS.Tests -{ - // This is based on code that was generated automatically by the xunit.runner.devices package. - // It configures the test-runner app that is implemented by that package, telling it where to - // find the tests, and also configuring it to run them immediately and then quit rather than - // waiting for user input. Output from the test run goes to the system log. - - [Register("AppDelegate")] - public partial class AppDelegate : RunnerAppDelegate - { - public AppDelegate() - { - ResultChannel = new XunitConsoleLoggingResultChannel(); - } - - public override bool FinishedLaunching(UIApplication app, NSDictionary options) - { - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - AddTestAssembly(Assembly.GetExecutingAssembly()); - - AutoStart = true; // this is necessary in order for the CI test job to work - - return base.FinishedLaunching(app, options); - } - } -} \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 98f4d035..00000000 --- a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "images": [ - { - "scale": "2x", - "size": "20x20", - "idiom": "iphone", - "filename": "Icon40.png" - }, - { - "scale": "3x", - "size": "20x20", - "idiom": "iphone", - "filename": "Icon60.png" - }, - { - "scale": "2x", - "size": "29x29", - "idiom": "iphone", - "filename": "Icon58.png" - }, - { - "scale": "3x", - "size": "29x29", - "idiom": "iphone", - "filename": "Icon87.png" - }, - { - "scale": "2x", - "size": "40x40", - "idiom": "iphone", - "filename": "Icon80.png" - }, - { - "scale": "3x", - "size": "40x40", - "idiom": "iphone", - "filename": "Icon120.png" - }, - { - "scale": "2x", - "size": "60x60", - "idiom": "iphone", - "filename": "Icon120.png" - }, - { - "scale": "3x", - "size": "60x60", - "idiom": "iphone", - "filename": "Icon180.png" - }, - { - "scale": "1x", - "size": "20x20", - "idiom": "ipad", - "filename": "Icon20.png" - }, - { - "scale": "2x", - "size": "20x20", - "idiom": "ipad", - "filename": "Icon40.png" - }, - { - "scale": "1x", - "size": "29x29", - "idiom": "ipad", - "filename": "Icon29.png" - }, - { - "scale": "2x", - "size": "29x29", - "idiom": "ipad", - "filename": "Icon58.png" - }, - { - "scale": "1x", - "size": "40x40", - "idiom": "ipad", - "filename": "Icon40.png" - }, - { - "scale": "2x", - "size": "40x40", - "idiom": "ipad", - "filename": "Icon80.png" - }, - { - "scale": "1x", - "size": "76x76", - "idiom": "ipad", - "filename": "Icon76.png" - }, - { - "scale": "2x", - "size": "76x76", - "idiom": "ipad", - "filename": "Icon152.png" - }, - { - "scale": "2x", - "size": "83.5x83.5", - "idiom": "ipad", - "filename": "Icon167.png" - }, - { - "scale": "1x", - "size": "1024x1024", - "idiom": "ios-marketing", - "filename": "Icon1024.png" - } - ], - "properties": {}, - "info": { - "version": 1, - "author": "xcode" - } -} \ No newline at end of file diff --git a/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png b/tests/LaunchDarkly.ClientSdk.iOS.Tests/Assets.xcassets/AppIcon.appiconset/Icon1024.png deleted file mode 100644 index 9174c989a9c8b8a5ca133228f4ed7c173fffd2ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70429 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 z zCn)b8&=oMu6ygwwJfdasJX|@L6?m1Dv0X9t>JAWO^UIj0#&(3UrHx;vP^3g= zL{(XT!?`D*pP8)WoGHYEZZc$!odTzb8n)q0|88*>6P z`?6&CSv_W7r2yF0beQ2*?V^_%pKktVAo`)T^26X@NpK_*-ni{D7{Sp{C0A<|16l(; zOL*xGW|*sKsiwHvE!h3QXe@^a#6W3}8!DQu-h?A_4gkeRYkt4NC~GR5P8eyp;9kVQ8$QG$5ad7Fo23Z~ak1jY~RXG{v?3G$RarFe`XePu3X{R+=mBOw&X zks)|Sc$RcG-jhn!`~-x|vg!&DA&@}QH^RNdyy9nq56yrU$^qAaS+F_NOaeFb)CVaH z?!UvPajgrK&zqdAs>&Def#wkcG_UhmYOVw^M`VZz@+4IWAVzK%`+za9rm2SD9={u@ zlx5D6UDL;lc7#9`+%vnlP3PescU=N`DHQPt_N55GNBMkVCRMR4?fvp zAFsvcHN4c9rb>J@{*IH>RTr9de%9i4Gd(cbFa9SP4anhoP;TA0!oZyB8?lNMDHPHK zCaOaFU9?x2A!o>p>mCF9r+hKs9Czu_P1l$LWU%}q#)=T3p`ZnYyeHmsewqw`}L^4LuHqfo+CG6<2n7#l^3;H^^!1 zsaieYFnN)Kc7Mv}^xE)4kXUw8<9I+jMB@QV9T9I8haLDt1Ne#exWUfGYG$4uMoEu& zo81#2up18Y40h%tIsOZglp(ltVsE*j1~$lVd|;rN)&${~o~-%KZnJp&3|OFR{^8E9 zJ;fCu53Ysw%}@VYWE*z7r)&4P=^B-SF%a@>*9g84<4aFUZT7x)qdsS+#2tu5NbpU@ zg;EwV)l-#sK>#r9>(0Figx{9lKm>KvRj;y<8 zc8SxMW4<11(s@QMV_}n9MRzA*62->vzxmHh1)GVASEJY7LVtRw`Rv{v`(Fuc00(&o z%m>gS2aJekmdNQ4p<{pD3HqZ-%4hdU1__xYhLi9mTJXD|E zE`t6SX)}l_DY5vO0Xrs#O6_DKtPKn0f+e~SprDYmJL_`<053iA5P`zn z4<5etc%aF58sHFr#M;U-9|=;l)J#Q2vS!Q9(d(EX6fubL%uA_lqa2%!cpNIv78QZ}Ayo(>C(ZpsRtKhzD--fpuoCch87cX-Bna9_{z%$b*dHM0?+T&Hk!+^UM`r|vq z2Id$??bX^|tfYaE+h#Nik(ZcN+wt)28q^gWe!y8jDCXrD<2qV#49x@5$8&Zrd5NTs zNYcix;9fe#PQQ;T?!6hG>9K{K+RCPqiGc9z%t{=`QaX>7O{l(+#7mJ1>Rae^J?82e z6cLqLypskTCyu>uc~$0-XZ^1Qvhwr+pKQ#CKImhGu*MGM*ZrROuAHWuT*yM$ieEy8*KLFMMdLZL|D+yDmy@3_PELTEVMI6nwfcYA3ZQ9wwKdtkT z;`;z7fU{U6>CS7kr3=A-()_G*G(Mjf2wXKe